static void ip6gre_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
		       u8 type, u8 code, int offset, __be32 info)
{
	const struct gre_base_hdr *greh;
	const struct ipv6hdr *ipv6h;
	int grehlen = sizeof(*greh);
	struct ip6_tnl *t;
	int key_off = 0;
	__be16 flags;
	__be32 key;

	if (!pskb_may_pull(skb, offset + grehlen))
		return;
	greh = (const struct gre_base_hdr *)(skb->data + offset);
	flags = greh->flags;
	if (flags & (GRE_VERSION | GRE_ROUTING))
		return;
	if (flags & GRE_CSUM)
		grehlen += 4;
	if (flags & GRE_KEY) {
		key_off = grehlen + offset;
		grehlen += 4;
	}

	if (!pskb_may_pull(skb, offset + grehlen))
		return;
	ipv6h = (const struct ipv6hdr *)skb->data;
	greh = (const struct gre_base_hdr *)(skb->data + offset);
	key = key_off ? *(__be32 *)(skb->data + key_off) : 0;

	t = ip6gre_tunnel_lookup(skb->dev, &ipv6h->daddr, &ipv6h->saddr,
				 key, greh->protocol);
	if (!t)
		return;

	switch (type) {
		__u32 teli;
		struct ipv6_tlv_tnl_enc_lim *tel;
		__u32 mtu;
	case ICMPV6_DEST_UNREACH:
		net_dbg_ratelimited("%s: Path to destination invalid or inactive!\n",
				    t->parms.name);
		break;
	case ICMPV6_TIME_EXCEED:
		if (code == ICMPV6_EXC_HOPLIMIT) {
			net_dbg_ratelimited("%s: Too small hop limit or routing loop in tunnel!\n",
					    t->parms.name);
		}
		break;
	case ICMPV6_PARAMPROB:
		teli = 0;
		if (code == ICMPV6_HDR_FIELD)
			teli = ip6_tnl_parse_tlv_enc_lim(skb, skb->data);

		if (teli && teli == be32_to_cpu(info) - 2) {
			tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli];
			if (tel->encap_limit == 0) {
				net_dbg_ratelimited("%s: Too small encapsulation limit or routing loop in tunnel!\n",
						    t->parms.name);
			}
		} else {
			net_dbg_ratelimited("%s: Recipient unable to parse tunneled packet!\n",
					    t->parms.name);
		}
		break;
	case ICMPV6_PKT_TOOBIG:
		mtu = be32_to_cpu(info) - offset;
		if (mtu < IPV6_MIN_MTU)
			mtu = IPV6_MIN_MTU;
		t->dev->mtu = mtu;
		break;
	}

	if (time_before(jiffies, t->err_time + IP6TUNNEL_ERR_TIMEO))
		t->err_count++;
	else
		t->err_count = 1;
	t->err_time = jiffies;
}


static int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
{
	const struct ipv6hdr *ipv6h;
	struct ip6_tnl *tunnel;

	ipv6h = ipv6_hdr(skb);
	tunnel = ip6gre_tunnel_lookup(skb->dev,
				      &ipv6h->saddr, &ipv6h->daddr, tpi->key,
				      tpi->proto);
	if (tunnel) {
		ip6_tnl_rcv(tunnel, skb, tpi, NULL, false);

		return PACKET_RCVD;
	}

	return PACKET_REJECT;
}


static void __net_exit ip6gre_exit_net(struct net *net)
{
	LIST_HEAD(list);

	rtnl_lock();
	ip6gre_destroy_tunnels(net, &list);
	unregister_netdevice_many(&list);
	rtnl_unlock();
}


static void __exit ip6gre_fini(void)
{
	rtnl_link_unregister(&ip6gre_tap_ops);
	rtnl_link_unregister(&ip6gre_link_ops);
	inet6_del_protocol(&ip6gre_protocol, IPPROTO_GRE);
	unregister_pernet_device(&ip6gre_net_ops);
}


static int ip6gre_changelink(struct net_device *dev, struct nlattr *tb[],
			    struct nlattr *data[])
{
	struct ip6_tnl *t, *nt = netdev_priv(dev);
	struct net *net = nt->net;
	struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
	struct __ip6_tnl_parm p;
	struct ip_tunnel_encap ipencap;

	if (dev == ign->fb_tunnel_dev)
		return -EINVAL;

	if (ip6gre_netlink_encap_parms(data, &ipencap)) {
		int err = ip6_tnl_encap_setup(nt, &ipencap);

		if (err < 0)
			return err;
	}

	ip6gre_netlink_parms(data, &p);

	t = ip6gre_tunnel_locate(net, &p, 0);

	if (t) {
		if (t->dev != dev)
			return -EEXIST;
	} else {
		t = nt;
	}

	ip6gre_tunnel_unlink(ign, t);
	ip6gre_tnl_change(t, &p, !tb[IFLA_MTU]);
	ip6gre_tunnel_link(ign, t);
	return 0;
}


static int check_nnp_nosuid(const struct linux_binprm *bprm,
			    const struct task_security_struct *old_tsec,
			    const struct task_security_struct *new_tsec)
{
	int nnp = (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS);
	int nosuid = !mnt_may_suid(bprm->file->f_path.mnt);
	int rc;

	if (!nnp && !nosuid)
		return 0; /* neither NNP nor nosuid */

	if (new_tsec->sid == old_tsec->sid)
		return 0; /* No change in credentials */

	/*
	 * The only transitions we permit under NNP or nosuid
	 * are transitions to bounded SIDs, i.e. SIDs that are
	 * guaranteed to only be allowed a subset of the permissions
	 * of the current SID.
	 */
	rc = security_bounded_transition(old_tsec->sid, new_tsec->sid);
	if (rc) {
		/*
		 * On failure, preserve the errno values for NNP vs nosuid.
		 * NNP:  Operation not permitted for caller.
		 * nosuid:  Permission denied to file.
		 */
		if (nnp)
			return -EPERM;
		else
			return -EACCES;
	}
	return 0;
}


static int selinux_file_open(struct file *file, const struct cred *cred)
{
	struct file_security_struct *fsec;
	struct inode_security_struct *isec;

	fsec = file->f_security;
	isec = inode_security(file_inode(file));
	/*
	 * Save inode label and policy sequence number
	 * at open-time so that selinux_file_permission
	 * can determine whether revalidation is necessary.
	 * Task label is already saved in the file security
	 * struct as its SID.
	 */
	fsec->isid = isec->sid;
	fsec->pseqno = avc_policy_seqno();
	/*
	 * Since the inode label or policy seqno may have changed
	 * between the selinux_inode_permission check and the saving
	 * of state above, recheck that access is still permitted.
	 * Otherwise, access might never be revalidated against the
	 * new inode label or new policy.
	 * This check is not redundant - do not remove.
	 */
	return file_path_has_perm(cred, file, open_file_to_av(file));
}


static int file_has_perm(const struct cred *cred,
			 struct file *file,
			 u32 av)
{
	struct file_security_struct *fsec = file->f_security;
	struct inode *inode = file_inode(file);
	struct common_audit_data ad;
	u32 sid = cred_sid(cred);
	int rc;

	ad.type = LSM_AUDIT_DATA_FILE;
	ad.u.file = file;

	if (sid != fsec->sid) {
		rc = avc_has_perm(sid, fsec->sid,
				  SECCLASS_FD,
				  FD__USE,
				  &ad);
		if (rc)
			goto out;
	}

	/* av is zero if only checking access to the descriptor. */
	rc = 0;
	if (av)
		rc = inode_has_perm(cred, inode, av, &ad);

out:
	return rc;
}


static int selinux_capable(const struct cred *cred, struct user_namespace *ns,
			   int cap, int audit)
{
	return cred_has_capability(cred, cap, audit, ns == &init_user_ns);
}


static void selinux_inode_invalidate_secctx(struct inode *inode)
{
	struct inode_security_struct *isec = inode->i_security;

	spin_lock(&isec->lock);
	isec->initialized = LABEL_INVALID;
	spin_unlock(&isec->lock);
}


shell_expand_line (count, ignore)
     int count, ignore;
{
  char *new_line;
  WORD_LIST *expanded_string;

  new_line = 0;
#if defined (BANG_HISTORY)
  new_line = history_expand_line_internal (rl_line_buffer);
#endif

#if defined (ALIAS)
  if (new_line)
    {
      char *alias_line;

      alias_line = alias_expand (new_line);
      free (new_line);
      new_line = alias_line;
    }
#endif /* ALIAS */

  if (new_line)
    {
      int old_point = rl_point;
      int at_end = rl_point == rl_end;

      /* If the line was history and alias expanded, then make that
	 be one thing to undo. */
      maybe_make_readline_line (new_line);
      free (new_line);

      /* If there is variable expansion to perform, do that as a separate
	 operation to be undone. */
      new_line = savestring (rl_line_buffer);
      expanded_string = expand_string (new_line, 0);
      FREE (new_line);
      if (expanded_string == 0)
	{
	  new_line = (char *)xmalloc (1);
	  new_line[0] = '\0';
	}
      else
	{
	  new_line = string_list (expanded_string);
	  dispose_words (expanded_string);
	}

      maybe_make_readline_line (new_line);
      free (new_line);

      /* Place rl_point where we think it should go. */
      if (at_end)
	rl_point = rl_end;
      else if (old_point < rl_end)
	{
	  rl_point = old_point;
	  if (!whitespace (rl_line_buffer[rl_point]))
	    rl_forward_word (1, 0);
	}
      return 0;
    }
  else
    {
      cleanup_expansion_error ();
      return 1;
    }
}


static int
valid_length_expression (name)
     char *name;
{
  return (name[1] == '\0' ||					/* ${#} */
	  ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') ||  /* special param */
	  (DIGIT (name[1]) && all_digits (name + 1)) ||	/* ${#11} */
#if defined (ARRAY_VARS)
	  valid_array_reference (name + 1, 0) ||		/* ${#a[7]} */
#endif
	  legal_identifier (name + 1));				/* ${#PS1} */


static WORD_DESC *
parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
     char *name;
     int var_is_special, quoted, pflags;
     arrayind_t *indp;
{
  WORD_DESC *ret;
  char *temp, *tt;
  intmax_t arg_index;
  SHELL_VAR *var;
  int atype, rflags;
  arrayind_t ind;

  ret = 0;
  temp = 0;
  rflags = 0;

  if (indp)
    *indp = INTMAX_MIN;

  /* Handle multiple digit arguments, as in ${11}. */
  if (legal_number (name, &arg_index))
    {
      tt = get_dollar_var_value (arg_index);
      if (tt)
 	temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
 		  ? quote_string (tt)
 		  : quote_escapes (tt);
      else
        temp = (char *)NULL;
      FREE (tt);
    }
  else if (var_is_special)      /* ${@} */
    {
      int sindex;
      tt = (char *)xmalloc (2 + strlen (name));
      tt[sindex = 0] = '$';
      strcpy (tt + 1, name);

      ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL,
			  (int *)NULL, (int *)NULL, pflags);
      free (tt);
    }
#if defined (ARRAY_VARS)
  else if (valid_array_reference (name, 0))
    {
expand_arrayref:
      if (pflags & PF_ASSIGNRHS)
	{
	  var = array_variable_part (name, &tt, (int *)0);
	  if (ALL_ELEMENT_SUB (tt[0]) && tt[1] == ']')
	    {
	      /* Only treat as double quoted if array variable */
	      if (var && (array_p (var) || assoc_p (var)))
	        /* XXX - bash-4.4/bash-5.0 pass AV_ASSIGNRHS */
		temp = array_value (name, quoted|Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &atype, &ind);
	      else
		temp = array_value (name, quoted, 0, &atype, &ind);
	    }
	  else
	    temp = array_value (name, quoted, 0, &atype, &ind);
	}
      else
	temp = array_value (name, quoted, 0, &atype, &ind);
      if (atype == 0 && temp)
	{
	  temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
		    ? quote_string (temp)
		    : quote_escapes (temp);
	  rflags |= W_ARRAYIND;
	  if (indp)
	    *indp = ind;
	}
      else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
	rflags |= W_HASQUOTEDNULL;
    }
#endif
  else if (var = find_variable (name))
    {
      if (var_isset (var) && invisible_p (var) == 0)
	{
#if defined (ARRAY_VARS)
	  if (assoc_p (var))
	    temp = assoc_reference (assoc_cell (var), "0");
	  else if (array_p (var))
	    temp = array_reference (array_cell (var), 0);
	  else
	    temp = value_cell (var);
#else
	  temp = value_cell (var);
#endif

	  if (temp)
	    temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
		      ? quote_string (temp)
		      : quote_escapes (temp);
	}
      else
	temp = (char *)NULL;
    }
  else if (var = find_variable_last_nameref (name, 0))
    {
      temp = nameref_cell (var);
#if defined (ARRAY_VARS)
      /* Handle expanding nameref whose value is x[n] */
      if (temp && *temp && valid_array_reference (temp, 0))
	{
	  name = temp;
	  goto expand_arrayref;
	}
      else
#endif
      /* y=2 ; typeset -n x=y; echo ${x} is not the same as echo ${2} in ksh */
      if (temp && *temp && legal_identifier (temp) == 0)
        {
	  last_command_exit_value = EXECUTION_FAILURE;
	  report_error (_("%s: invalid variable name for name reference"), temp);
	  temp = &expand_param_error;
        }
      else
	temp = (char *)NULL;
    }
  else
    temp = (char *)NULL;

  if (ret == 0)
    {
      ret = alloc_word_desc ();
      ret->word = temp;
      ret->flags |= rflags;
    }
  return ret;


void
unlink_fifo_list ()
{
  register int i;

  if (nfds == 0)
    return;

  for (i = 0; nfds && i < totfds; i++)
    unlink_fifo (i);

  nfds = 0;


char *
remove_quoted_escapes (string)
     char *string;
{
  char *t;

  if (string)
    {
      t = dequote_escapes (string);
      strcpy (string, t);
      free (t);
    }

  return (string);


WORD_LIST *
list_string (string, separators, quoted)
     register char *string, *separators;
     int quoted;
{
  WORD_LIST *result;
  WORD_DESC *t;
  char *current_word, *s;
  int sindex, sh_style_split, whitesep, xflags;
  size_t slen;

  if (!string || !*string)
    return ((WORD_LIST *)NULL);

  sh_style_split = separators && separators[0] == ' ' &&
				 separators[1] == '\t' &&
				 separators[2] == '\n' &&
				 separators[3] == '\0';
  for (xflags = 0, s = ifs_value; s && *s; s++)
    {
      if (*s == CTLESC) xflags |= SX_NOCTLESC;
      else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
    }

  slen = 0;
  /* Remove sequences of whitespace at the beginning of STRING, as
     long as those characters appear in IFS.  Do not do this if
     STRING is quoted or if there are no separator characters. */
  if (!quoted || !separators || !*separators)
    {
      for (s = string; *s && spctabnl (*s) && issep (*s); s++);

      if (!*s)
	return ((WORD_LIST *)NULL);

      string = s;
    }

  /* OK, now STRING points to a word that does not begin with white space.
     The splitting algorithm is:
	extract a word, stopping at a separator
	skip sequences of spc, tab, or nl as long as they are separators
     This obeys the field splitting rules in Posix.2. */
  slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 1;
  for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; )
    {
      /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
	 unless multibyte chars are possible. */
      current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags);
      if (current_word == 0)
	break;

      /* If we have a quoted empty string, add a quoted null argument.  We
	 want to preserve the quoted null character iff this is a quoted
	 empty string; otherwise the quoted null characters are removed
	 below. */
      if (QUOTED_NULL (current_word))
	{
	  t = alloc_word_desc ();
	  t->word = make_quoted_char ('\0');
	  t->flags |= W_QUOTED|W_HASQUOTEDNULL;
	  result = make_word_list (t, result);
	}
      else if (current_word[0] != '\0')
	{
	  /* If we have something, then add it regardless.  However,
	     perform quoted null character removal on the current word. */
	  remove_quoted_nulls (current_word);
	  result = add_string_to_list (current_word, result);
	  result->word->flags &= ~W_HASQUOTEDNULL;	/* just to be sure */
	  if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
	    result->word->flags |= W_QUOTED;
	}

      /* If we're not doing sequences of separators in the traditional
	 Bourne shell style, then add a quoted null argument. */
      else if (!sh_style_split && !spctabnl (string[sindex]))
	{
	  t = alloc_word_desc ();
	  t->word = make_quoted_char ('\0');
	  t->flags |= W_QUOTED|W_HASQUOTEDNULL;
	  result = make_word_list (t, result);
	}

      free (current_word);

      /* Note whether or not the separator is IFS whitespace, used later. */
      whitesep = string[sindex] && spctabnl (string[sindex]);

      /* Move past the current separator character. */
      if (string[sindex])
	{
	  DECLARE_MBSTATE;
	  ADVANCE_CHAR (string, slen, sindex);
	}

      /* Now skip sequences of space, tab, or newline characters if they are
	 in the list of separators. */
      while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex]))
	sindex++;

      /* If the first separator was IFS whitespace and the current character
	 is a non-whitespace IFS character, it should be part of the current
	 field delimiter, not a separate delimiter that would result in an
	 empty field.  Look at POSIX.2, 3.6.5, (3)(b). */
      if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex]))
	{
	  sindex++;
	  /* An IFS character that is not IFS white space, along with any
	     adjacent IFS white space, shall delimit a field. (SUSv3) */
	  while (string[sindex] && spctabnl (string[sindex]) && isifs (string[sindex]))
	    sindex++;
	}
    }
  return (REVERSE_LIST (result, WORD_LIST *));


char *
get_word_from_string (stringp, separators, endptr)
     char **stringp, *separators, **endptr;
{
  register char *s;
  char *current_word;
  int sindex, sh_style_split, whitesep, xflags;
  size_t slen;

  if (!stringp || !*stringp || !**stringp)
    return ((char *)NULL);

  sh_style_split = separators && separators[0] == ' ' &&
				 separators[1] == '\t' &&
				 separators[2] == '\n' &&
				 separators[3] == '\0';
  for (xflags = 0, s = ifs_value; s && *s; s++)
    {
      if (*s == CTLESC) xflags |= SX_NOCTLESC;
      if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
    }

  s = *stringp;
  slen = 0;

  /* Remove sequences of whitespace at the beginning of STRING, as
     long as those characters appear in IFS. */
  if (sh_style_split || !separators || !*separators)
    {
      for (; *s && spctabnl (*s) && isifs (*s); s++);

      /* If the string is nothing but whitespace, update it and return. */
      if (!*s)
	{
	  *stringp = s;
	  if (endptr)
	    *endptr = s;
	  return ((char *)NULL);
	}
    }

  /* OK, S points to a word that does not begin with white space.
     Now extract a word, stopping at a separator, save a pointer to
     the first character after the word, then skip sequences of spc,
     tab, or nl as long as they are separators.

     This obeys the field splitting rules in Posix.2. */
  sindex = 0;
  /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
     unless multibyte chars are possible. */
  slen = (MB_CUR_MAX > 1) ? STRLEN (s) : 1;
  current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags);

  /* Set ENDPTR to the first character after the end of the word. */
  if (endptr)
    *endptr = s + sindex;

  /* Note whether or not the separator is IFS whitespace, used later. */
  whitesep = s[sindex] && spctabnl (s[sindex]);

  /* Move past the current separator character. */
  if (s[sindex])
    {
      DECLARE_MBSTATE;
      ADVANCE_CHAR (s, slen, sindex);
    }

  /* Now skip sequences of space, tab, or newline characters if they are
     in the list of separators. */
  while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
    sindex++;

  /* If the first separator was IFS whitespace and the current character is
     a non-whitespace IFS character, it should be part of the current field
     delimiter, not a separate delimiter that would result in an empty field.
     Look at POSIX.2, 3.6.5, (3)(b). */
  if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex]))
    {
      sindex++;
      /* An IFS character that is not IFS white space, along with any adjacent
	 IFS white space, shall delimit a field. */
      while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
	sindex++;
    }

  /* Update STRING to point to the next field. */
  *stringp = s + sindex;
  return (current_word);


static WORD_LIST *
expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something)
     WORD_DESC *word;
     int quoted, isexp;
     int *contains_dollar_at;
     int *expanded_something;
{
  WORD_LIST *list;
  WORD_DESC *tword;

  /* The intermediate string that we build while expanding. */
  char *istring;

  /* The current size of the above object. */
  size_t istring_size;

  /* Index into ISTRING. */
  int istring_index;

  /* Temporary string storage. */
  char *temp, *temp1;

  /* The text of WORD. */
  register char *string;

  /* The size of STRING. */
  size_t string_size;

  /* The index into STRING. */
  int sindex;

  /* This gets 1 if we see a $@ while quoted. */
  int quoted_dollar_at;

  /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on
     whether WORD contains no quoting characters, a partially quoted
     string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */
  int quoted_state;

  /* State flags */
  int had_quoted_null;
  int has_dollar_at, temp_has_dollar_at;
  int split_on_spaces;
  int tflag;
  int pflags;			/* flags passed to param_expand */
  int mb_cur_max;

  int assignoff;		/* If assignment, offset of `=' */

  register unsigned char c;	/* Current character. */
  int t_index;			/* For calls to string_extract_xxx. */

  char twochars[2];

  DECLARE_MBSTATE;

  /* OK, let's see if we can optimize a common idiom: "$@" */
  if (STREQ (word->word, "\"$@\"") &&
      (word->flags == (W_HASDOLLAR|W_QUOTED)) &&
      dollar_vars[1])		/* XXX - check IFS here as well? */
    {
      if (contains_dollar_at)
	*contains_dollar_at = 1;
      if (expanded_something)
	*expanded_something = 1;
      if (cached_quoted_dollar_at)
	return (copy_word_list (cached_quoted_dollar_at));
      list = list_rest_of_args ();
      list = quote_list (list);
      cached_quoted_dollar_at = copy_word_list (list);
      return (list);
    }

  istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
  istring[istring_index = 0] = '\0';
  quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
  split_on_spaces = 0;
  quoted_state = UNQUOTED;

  string = word->word;
  if (string == 0)
    goto finished_with_string;
  mb_cur_max = MB_CUR_MAX;

  /* Don't need the string length for the SADD... and COPY_ macros unless
     multibyte characters are possible. */
  string_size = (mb_cur_max > 1) ? strlen (string) : 1;

  if (contains_dollar_at)
    *contains_dollar_at = 0;

  assignoff = -1;

  /* Begin the expansion. */

  for (sindex = 0; ;)
    {
      c = string[sindex];

      /* Case on top-level character. */
      switch (c)
	{
	case '\0':
	  goto finished_with_string;

	case CTLESC:
	  sindex++;
#if HANDLE_MULTIBYTE
	  if (mb_cur_max > 1 && string[sindex])
	    {
	      SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
	    }
	  else
#endif
	    {
	      temp = (char *)xmalloc (3);
	      temp[0] = CTLESC;
	      temp[1] = c = string[sindex];
	      temp[2] = '\0';
	    }

dollar_add_string:
	  if (string[sindex])
	    sindex++;

add_string:
	  if (temp)
	    {
	      istring = sub_append_string (temp, istring, &istring_index, &istring_size);
	      temp = (char *)0;
	    }

	  break;

#if defined (PROCESS_SUBSTITUTION)
	  /* Process substitution. */
	case '<':
	case '>':
	  {
	    /* bash-4.4/bash-5.0
	       XXX - technically this should only be expanded at the start
	       of a word */
	    if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct)
	      {
		sindex--;	/* add_character: label increments sindex */
		goto add_character;
	      }
	    else
	      t_index = sindex + 1; /* skip past both '<' and LPAREN */

	    temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index, 0); /*))*/
	    sindex = t_index;

	    /* If the process substitution specification is `<()', we want to
	       open the pipe for writing in the child and produce output; if
	       it is `>()', we want to open the pipe for reading in the child
	       and consume input. */
	    temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0;

	    FREE (temp1);

	    goto dollar_add_string;
	  }
#endif /* PROCESS_SUBSTITUTION */

	case '=':
	  /* Posix.2 section 3.6.1 says that tildes following `=' in words
	     which are not assignment statements are not expanded.  If the
	     shell isn't in posix mode, though, we perform tilde expansion
	     on `likely candidate' unquoted assignment statements (flags
	     include W_ASSIGNMENT but not W_QUOTED).  A likely candidate
	     contains an unquoted :~ or =~.  Something to think about: we
	     now have a flag that says  to perform tilde expansion on arguments
	     to `assignment builtins' like declare and export that look like
	     assignment statements.  We now do tilde expansion on such words
	     even in POSIX mode. */
	  if (word->flags & (W_ASSIGNRHS|W_NOTILDE))
	    {
	      if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
		goto add_ifs_character;
	      else
		goto add_character;
	    }
	  /* If we're not in posix mode or forcing assignment-statement tilde
	     expansion, note where the `=' appears in the word and prepare to
	     do tilde expansion following the first `='. */
	  if ((word->flags & W_ASSIGNMENT) &&
	      (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
	      assignoff == -1 && sindex > 0)
	    assignoff = sindex;
	  if (sindex == assignoff && string[sindex+1] == '~')	/* XXX */
	    word->flags |= W_ITILDE;
#if 0
	  else if ((word->flags & W_ASSIGNMENT) &&
		   (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
		   string[sindex+1] == '~')
	    word->flags |= W_ITILDE;
#endif

	  /* XXX - bash-4.4/bash-5.0 */
	  if (word->flags & W_ASSIGNARG)
	    word->flags |= W_ASSIGNRHS;		/* affects $@ */

	  if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
	    goto add_ifs_character;
	  else
	    goto add_character;

	case ':':
	  if (word->flags & W_NOTILDE)
	    {
	      if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
		goto add_ifs_character;
	      else
		goto add_character;
	    }

	  if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) &&
	      string[sindex+1] == '~')
	    word->flags |= W_ITILDE;

	  if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
	    goto add_ifs_character;
	  else
	    goto add_character;

	case '~':
	  /* If the word isn't supposed to be tilde expanded, or we're not
	     at the start of a word or after an unquoted : or = in an
	     assignment statement, we don't do tilde expansion.  If we don't want
	     tilde expansion when expanding words to be passed to the arithmetic
	     evaluator, remove the check for Q_ARITH. */
	  if ((word->flags & (W_NOTILDE|W_DQUOTE)) ||
	      (sindex > 0 && ((word->flags & W_ITILDE) == 0)) ||
	      ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && ((quoted & Q_ARITH) == 0)))
	    {
	      word->flags &= ~W_ITILDE;
	      if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
		goto add_ifs_character;
	      else
		goto add_character;
	    }

	  if (word->flags & W_ASSIGNRHS)
	    tflag = 2;
	  else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP))
	    tflag = 1;
	  else
	    tflag = 0;

	  temp = bash_tilde_find_word (string + sindex, tflag, &t_index);

	  word->flags &= ~W_ITILDE;

	  if (temp && *temp && t_index > 0)
	    {
	      temp1 = bash_tilde_expand (temp, tflag);
	      if  (temp1 && *temp1 == '~' && STREQ (temp, temp1))
		{
		  FREE (temp);
		  FREE (temp1);
		  goto add_character;		/* tilde expansion failed */
		}
	      free (temp);
	      temp = temp1;
	      sindex += t_index;
	      goto add_quoted_string;		/* XXX was add_string */
	    }
	  else
	    {
	      FREE (temp);
	      goto add_character;
	    }

	case '$':
	  if (expanded_something)
	    *expanded_something = 1;

	  temp_has_dollar_at = 0;
	  pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
	  if (word->flags & W_NOSPLIT2)
	    pflags |= PF_NOSPLIT2;
	  if (word->flags & W_ASSIGNRHS)
	    pflags |= PF_ASSIGNRHS;
	  if (word->flags & W_COMPLETE)
	    pflags |= PF_COMPLETE;
	  tword = param_expand (string, &sindex, quoted, expanded_something,
			       &temp_has_dollar_at, &quoted_dollar_at,
			       &had_quoted_null, pflags);
	  has_dollar_at += temp_has_dollar_at;
	  split_on_spaces += (tword->flags & W_SPLITSPACE);

	  if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
	    {
	      free (string);
	      free (istring);
	      return ((tword == &expand_wdesc_error) ? &expand_word_error
						     : &expand_word_fatal);
	    }
	  if (contains_dollar_at && has_dollar_at)
	    *contains_dollar_at = 1;

	  if (tword && (tword->flags & W_HASQUOTEDNULL))
	    had_quoted_null = 1;

	  temp = tword ? tword->word : (char *)NULL;
	  dispose_word_desc (tword);

	  /* Kill quoted nulls; we will add them back at the end of
	     expand_word_internal if nothing else in the string */
	  if (had_quoted_null && temp && QUOTED_NULL (temp))
	    {
	      FREE (temp);
	      temp = (char *)NULL;
	    }

	  goto add_string;
	  break;

	case '`':		/* Backquoted command substitution. */
	  {
	    t_index = sindex++;

	    temp = string_extract (string, &sindex, "`", SX_REQMATCH);
	    /* The test of sindex against t_index is to allow bare instances of
	       ` to pass through, for backwards compatibility. */
	    if (temp == &extract_string_error || temp == &extract_string_fatal)
	      {
		if (sindex - 1 == t_index)
		  {
		    sindex = t_index;
		    goto add_character;
		  }
		last_command_exit_value = EXECUTION_FAILURE;
		report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
		free (string);
		free (istring);
		return ((temp == &extract_string_error) ? &expand_word_error
							: &expand_word_fatal);
	      }

	    if (expanded_something)
	      *expanded_something = 1;

	    if (word->flags & W_NOCOMSUB)
	      /* sindex + 1 because string[sindex] == '`' */
	      temp1 = substring (string, t_index, sindex + 1);
	    else
	      {
		de_backslash (temp);
		tword = command_substitute (temp, quoted);
		temp1 = tword ? tword->word : (char *)NULL;
		if (tword)
		  dispose_word_desc (tword);
	      }
	    FREE (temp);
	    temp = temp1;
	    goto dollar_add_string;
	  }

	case '\\':
	  if (string[sindex + 1] == '\n')
	    {
	      sindex += 2;
	      continue;
	    }

	  c = string[++sindex];

	  if (quoted & Q_HERE_DOCUMENT)
	    tflag = CBSHDOC;
	  else if (quoted & Q_DOUBLE_QUOTES)
	    tflag = CBSDQUOTE;
	  else
	    tflag = 0;

	  /* From Posix discussion on austin-group list:  Backslash escaping
	     a } in ${...} is removed.  Issue 0000221 */
	  if ((quoted & Q_DOLBRACE) && c == RBRACE)
	    {
	      SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
	    }
	  /* This is the fix for " $@\ " */
	  else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0) && isexp == 0 && isifs (c))
	    {
	      RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
				      DEFAULT_ARRAY_SIZE);
	      istring[istring_index++] = CTLESC;
	      istring[istring_index++] = '\\';
	      istring[istring_index] = '\0';

	      SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
	    }
	  else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
	    {
	      SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size);
	    }
	  else if (c == 0)
	    {
	      c = CTLNUL;
	      sindex--;		/* add_character: label increments sindex */
	      goto add_character;
	    }
	  else
	    {
	      SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
	    }

	  sindex++;
add_twochars:
	  /* BEFORE jumping here, we need to increment sindex if appropriate */
	  RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
				  DEFAULT_ARRAY_SIZE);
	  istring[istring_index++] = twochars[0];
	  istring[istring_index++] = twochars[1];
	  istring[istring_index] = '\0';

	  break;

	case '"':
	  if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && ((quoted & Q_ARITH) == 0))
	    goto add_character;

	  t_index = ++sindex;
	  temp = string_extract_double_quoted (string, &sindex, (word->flags & W_COMPLETE) ? SX_COMPLETE : 0);

	  /* If the quotes surrounded the entire string, then the
	     whole word was quoted. */
	  quoted_state = (t_index == 1 && string[sindex] == '\0')
			    ? WHOLLY_QUOTED
			    : PARTIALLY_QUOTED;

	  if (temp && *temp)
	    {
	      tword = alloc_word_desc ();
	      tword->word = temp;

	      /* XXX - bash-4.4/bash-5.0 */
	      if (word->flags & W_ASSIGNARG)
		tword->flags |= word->flags & (W_ASSIGNARG|W_ASSIGNRHS);	/* affects $@ */
	      if (word->flags & W_COMPLETE)
		tword->flags |= W_COMPLETE;	/* for command substitutions */
	      if (word->flags & W_NOCOMSUB)
		tword->flags |= W_NOCOMSUB;
	      if (word->flags & W_NOPROCSUB)
		tword->flags |= W_NOPROCSUB;

	      temp = (char *)NULL;

	      temp_has_dollar_at = 0;	/* XXX */
	      /* Need to get W_HASQUOTEDNULL flag through this function. */
	      list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &temp_has_dollar_at, (int *)NULL);
	      has_dollar_at += temp_has_dollar_at;

	      if (list == &expand_word_error || list == &expand_word_fatal)
		{
		  free (istring);
		  free (string);
		  /* expand_word_internal has already freed temp_word->word
		     for us because of the way it prints error messages. */
		  tword->word = (char *)NULL;
		  dispose_word (tword);
		  return list;
		}

	      dispose_word (tword);

	      /* "$@" (a double-quoted dollar-at) expands into nothing,
		 not even a NULL word, when there are no positional
		 parameters. */
	      if (list == 0 && temp_has_dollar_at)	/* XXX - was has_dollar_at */
		{
		  quoted_dollar_at++;
		  break;
		}

	      /* If we get "$@", we know we have expanded something, so we
		 need to remember it for the final split on $IFS.  This is
		 a special case; it's the only case where a quoted string
		 can expand into more than one word.  It's going to come back
		 from the above call to expand_word_internal as a list with
		 a single word, in which all characters are quoted and
		 separated by blanks.  What we want to do is to turn it back
		 into a list for the next piece of code. */
	      if (list)
		dequote_list (list);

	      if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
		had_quoted_null = 1;		/* XXX */

	      if (temp_has_dollar_at)		/* XXX - was has_dollar_at */
		{
		  quoted_dollar_at++;
		  if (contains_dollar_at)
		    *contains_dollar_at = 1;
		  if (expanded_something)
		    *expanded_something = 1;
		}
	    }
	  else
	    {
	      /* What we have is "".  This is a minor optimization. */
	      FREE (temp);
	      list = (WORD_LIST *)NULL;
	    }

	  /* The code above *might* return a list (consider the case of "$@",
	     where it returns "$1", "$2", etc.).  We can't throw away the
	     rest of the list, and we have to make sure each word gets added
	     as quoted.  We test on tresult->next:  if it is non-NULL, we
	     quote the whole list, save it to a string with string_list, and
	     add that string. We don't need to quote the results of this
	     (and it would be wrong, since that would quote the separators
	     as well), so we go directly to add_string. */
	  if (list)
	    {
	      if (list->next)
		{
		  /* Testing quoted_dollar_at makes sure that "$@" is
		     split correctly when $IFS does not contain a space. */
		  temp = quoted_dollar_at
				? string_list_dollar_at (list, Q_DOUBLE_QUOTES, 0)
				: string_list (quote_list (list));
		  dispose_words (list);
		  goto add_string;
		}
	      else
		{
		  temp = savestring (list->word->word);
		  tflag = list->word->flags;
		  dispose_words (list);

		  /* If the string is not a quoted null string, we want
		     to remove any embedded unquoted CTLNUL characters.
		     We do not want to turn quoted null strings back into
		     the empty string, though.  We do this because we
		     want to remove any quoted nulls from expansions that
		     contain other characters.  For example, if we have
		     x"$*"y or "x$*y" and there are no positional parameters,
		     the $* should expand into nothing. */
		  /* We use the W_HASQUOTEDNULL flag to differentiate the
		     cases:  a quoted null character as above and when
		     CTLNUL is contained in the (non-null) expansion
		     of some variable.  We use the had_quoted_null flag to
		     pass the value through this function to its caller. */
		  if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0)
		    remove_quoted_nulls (temp);	/* XXX */
		}
	    }
	  else
	    temp = (char *)NULL;

	  /* We do not want to add quoted nulls to strings that are only
	     partially quoted; we can throw them away.  The exception to
	     this is when we are going to be performing word splitting,
	     since we have to preserve a null argument if the next character
	     will cause word splitting. */
	  if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
	    continue;

	add_quoted_string:

	  if (temp)
	    {
	      temp1 = temp;
	      temp = quote_string (temp);
	      free (temp1);
	      goto add_string;
	    }
	  else
	    {
	      /* Add NULL arg. */
	      c = CTLNUL;
	      sindex--;		/* add_character: label increments sindex */
	      goto add_character;
	    }

	  /* break; */

	case '\'':
	  if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
	    goto add_character;

	  t_index = ++sindex;
	  temp = string_extract_single_quoted (string, &sindex);

	  /* If the entire STRING was surrounded by single quotes,
	     then the string is wholly quoted. */
	  quoted_state = (t_index == 1 && string[sindex] == '\0')
			    ? WHOLLY_QUOTED
			    : PARTIALLY_QUOTED;

	  /* If all we had was '', it is a null expansion. */
	  if (*temp == '\0')
	    {
	      free (temp);
	      temp = (char *)NULL;
	    }
	  else
	    remove_quoted_escapes (temp);	/* ??? */

	  /* We do not want to add quoted nulls to strings that are only
	     partially quoted; such nulls are discarded.  See above for the
	     exception, which is when the string is going to be split. */
	  if (temp == 0 && (quoted_state == PARTIALLY_QUOTED) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
	    continue;

	  /* If we have a quoted null expansion, add a quoted NULL to istring. */
	  if (temp == 0)
	    {
	      c = CTLNUL;
	      sindex--;		/* add_character: label increments sindex */
	      goto add_character;
	    }
	  else
	    goto add_quoted_string;

	  /* break; */

	default:
	  /* This is the fix for " $@ " */
	add_ifs_character:
	  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0))
	    {
	      if (string[sindex])	/* from old goto dollar_add_string */
		sindex++;
	      if (c == 0)
		{
		  c = CTLNUL;
		  goto add_character;
		}
	      else
		{
#if HANDLE_MULTIBYTE
		  if (mb_cur_max > 1)
		    sindex--;

		  if (mb_cur_max > 1)
		    {
		      SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
		    }
		  else
#endif
		    {
		      twochars[0] = CTLESC;
		      twochars[1] = c;
		      goto add_twochars;
		    }
		}
	    }

	  SADD_MBCHAR (temp, string, sindex, string_size);

	add_character:
	  RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size,
				  DEFAULT_ARRAY_SIZE);
	  istring[istring_index++] = c;
	  istring[istring_index] = '\0';

	  /* Next character. */
	  sindex++;
	}
    }

finished_with_string:
  /* OK, we're ready to return.  If we have a quoted string, and
     quoted_dollar_at is not set, we do no splitting at all; otherwise
     we split on ' '.  The routines that call this will handle what to
     do if nothing has been expanded. */

  /* Partially and wholly quoted strings which expand to the empty
     string are retained as an empty arguments.  Unquoted strings
     which expand to the empty string are discarded.  The single
     exception is the case of expanding "$@" when there are no
     positional parameters.  In that case, we discard the expansion. */

  /* Because of how the code that handles "" and '' in partially
     quoted strings works, we need to make ISTRING into a QUOTED_NULL
     if we saw quoting characters, but the expansion was empty.
     "" and '' are tossed away before we get to this point when
     processing partially quoted strings.  This makes "" and $xxx""
     equivalent when xxx is unset.  We also look to see whether we
     saw a quoted null from a ${} expansion and add one back if we
     need to. */

  /* If we expand to nothing and there were no single or double quotes
     in the word, we throw it away.  Otherwise, we return a NULL word.
     The single exception is for $@ surrounded by double quotes when
     there are no positional parameters.  In that case, we also throw
     the word away. */

  if (*istring == '\0')
    {
      if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED))
	{
	  istring[0] = CTLNUL;
	  istring[1] = '\0';
	  tword = make_bare_word (istring);
	  tword->flags |= W_HASQUOTEDNULL;		/* XXX */
	  list = make_word_list (tword, (WORD_LIST *)NULL);
	  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
	    tword->flags |= W_QUOTED;
	}
      /* According to sh, ksh, and Posix.2, if a word expands into nothing
	 and a double-quoted "$@" appears anywhere in it, then the entire
	 word is removed. */
      /* XXX - exception appears to be that quoted null strings result in
	 null arguments */
      else  if (quoted_state == UNQUOTED || quoted_dollar_at)
	list = (WORD_LIST *)NULL;
#if 0
      else
	{
	  tword = make_bare_word (istring);
	  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
	    tword->flags |= W_QUOTED;
	  list = make_word_list (tword, (WORD_LIST *)NULL);
	}
#else
      else
	list = (WORD_LIST *)NULL;
#endif
    }
  else if (word->flags & W_NOSPLIT)
    {
      tword = make_bare_word (istring);
      if (word->flags & W_ASSIGNMENT)
	tword->flags |= W_ASSIGNMENT;	/* XXX */
      if (word->flags & W_COMPASSIGN)
	tword->flags |= W_COMPASSIGN;	/* XXX */
      if (word->flags & W_NOGLOB)
	tword->flags |= W_NOGLOB;	/* XXX */
      if (word->flags & W_NOBRACE)
	tword->flags |= W_NOBRACE;	/* XXX */
      if (word->flags & W_NOEXPAND)
	tword->flags |= W_NOEXPAND;	/* XXX */
      if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
	tword->flags |= W_QUOTED;
      if (had_quoted_null && QUOTED_NULL (istring))
	tword->flags |= W_HASQUOTEDNULL;
      list = make_word_list (tword, (WORD_LIST *)NULL);
    }
  else
    {
      char *ifs_chars;
      char *tstring;

      ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL;

      /* If we have $@, we need to split the results no matter what.  If
	 IFS is unset or NULL, string_list_dollar_at has separated the
	 positional parameters with a space, so we split on space (we have
	 set ifs_chars to " \t\n" above if ifs is unset).  If IFS is set,
	 string_list_dollar_at has separated the positional parameters
	 with the first character of $IFS, so we split on $IFS.  If
	 SPLIT_ON_SPACES is set, we expanded $* (unquoted) with IFS either
	 unset or null, and we want to make sure that we split on spaces
	 regardless of what else has happened to IFS since the expansion,
	 or we expanded "$@" with IFS null and we need to split the positional
	 parameters into separate words. */
      if (split_on_spaces)
	list = list_string (istring, " ", 1);	/* XXX quoted == 1? */
      /* If we have $@ (has_dollar_at != 0) and we are in a context where we
	 don't want to split the result (W_NOSPLIT2), and we are not quoted,
	 we have already separated the arguments with the first character of
	 $IFS.  In this case, we want to return a list with a single word
	 with the separator possibly replaced with a space (it's what other
	 shells seem to do).
	 quoted_dollar_at is internal to this function and is set if we are
	 passed an argument that is unquoted (quoted == 0) but we encounter a
	 double-quoted $@ while expanding it. */
      else if (has_dollar_at && quoted_dollar_at == 0 && ifs_chars && quoted == 0 && (word->flags & W_NOSPLIT2))
	{
	  /* Only split and rejoin if we have to */
	  if (*ifs_chars && *ifs_chars != ' ')
	    {
	      list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
	      tstring = string_list (list);
	    }
	  else
	    tstring = istring;
	  tword = make_bare_word (tstring);
	  if (tstring != istring)
	    free (tstring);
	  goto set_word_flags;
	}
      /* This is the attempt to make $* in an assignment context (a=$*) and
	 array variables subscripted with * in an assignment context (a=${foo[*]})
	 behave similarly.  It has side effects that, though they increase
	 compatibility with other shells, are not backwards compatible. */
#if 0
      else if (has_dollar_at && quoted == 0 && ifs_chars && (word->flags & W_ASSIGNRHS))
	{
	  tword = make_bare_word (istring);
	  goto set_word_flags;
	}
#endif
      else if (has_dollar_at && ifs_chars)
	list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
      else
	{
	  tword = make_bare_word (istring);
set_word_flags:
	  if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED))
	    tword->flags |= W_QUOTED;
	  if (word->flags & W_ASSIGNMENT)
	    tword->flags |= W_ASSIGNMENT;
	  if (word->flags & W_COMPASSIGN)
	    tword->flags |= W_COMPASSIGN;
	  if (word->flags & W_NOGLOB)
	    tword->flags |= W_NOGLOB;
	  if (word->flags & W_NOBRACE)
	    tword->flags |= W_NOBRACE;
	  if (word->flags & W_NOEXPAND)
	    tword->flags |= W_NOEXPAND;
	  if (had_quoted_null && QUOTED_NULL (istring))
	    tword->flags |= W_HASQUOTEDNULL;	/* XXX */
	  list = make_word_list (tword, (WORD_LIST *)NULL);
	}
    }

  free (istring);
  return (list);


static int
chk_arithsub (s, len)
     const char *s;
     int len;
{
  int i, count;
  DECLARE_MBSTATE;

  i = count = 0;
  while (i < len)
    {
      if (s[i] == LPAREN)
	count++;
      else if (s[i] == RPAREN)
	{
	  count--;
	  if (count < 0)
	    return 0;
	}

      switch (s[i])
	{
	default:
	  ADVANCE_CHAR (s, len, i);
	  break;

	case '\\':
	  i++;
	  if (s[i])
	    ADVANCE_CHAR (s, len, i);
	  break;

	case '\'':
	  i = skip_single_quoted (s, len, ++i, 0);
	  break;

	case '"':
	  i = skip_double_quoted ((char *)s, len, ++i, 0);
	  break;
	}
    }

  return (count == 0);


WORD_LIST *
dequote_list (list)
     WORD_LIST *list;
{
  register char *s;
  register WORD_LIST *tlist;

  for (tlist = list; tlist; tlist = tlist->next)
    {
      s = dequote_string (tlist->word->word);
      if (QUOTED_NULL (tlist->word->word))
	tlist->word->flags &= ~W_HASQUOTEDNULL;
      free (tlist->word->word);
      tlist->word->word = s;
    }
  return list;


static WORD_LIST *
expand_word_list_internal (list, eflags)
     WORD_LIST *list;
     int eflags;
{
  WORD_LIST *new_list, *temp_list;
  int tint;
  char *savecmd;

  tempenv_assign_error = 0;
  if (list == 0)
    return ((WORD_LIST *)NULL);

  garglist = new_list = copy_word_list (list);
  if (eflags & WEXP_VARASSIGN)
    {
      garglist = new_list = separate_out_assignments (new_list);
      if (new_list == 0)
	{
	  if (subst_assign_varlist)
	    {
	      /* All the words were variable assignments, so they are placed
		 into the shell's environment. */
	      for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
		{
		  savecmd = this_command_name;
		  this_command_name = (char *)NULL;	/* no arithmetic errors */
		  tint = do_word_assignment (temp_list->word, 0);
		  this_command_name = savecmd;
		  /* Variable assignment errors in non-interactive shells
		     running in Posix.2 mode cause the shell to exit. */
		  if (tint == 0)
		    {
		      last_command_exit_value = EXECUTION_FAILURE;
		      if (interactive_shell == 0 && posixly_correct)
			exp_jump_to_top_level (FORCE_EOF);
		      else
			exp_jump_to_top_level (DISCARD);
		    }
		}
	      dispose_words (subst_assign_varlist);
	      subst_assign_varlist = (WORD_LIST *)NULL;
	    }
	  return ((WORD_LIST *)NULL);
	}
    }

  /* Begin expanding the words that remain.  The expansions take place on
     things that aren't really variable assignments. */

#if defined (BRACE_EXPANSION)
  /* Do brace expansion on this word if there are any brace characters
     in the string. */
  if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list)
    new_list = brace_expand_word_list (new_list, eflags);
#endif /* BRACE_EXPANSION */

  /* Perform the `normal' shell expansions: tilde expansion, parameter and
     variable substitution, command substitution, arithmetic expansion,
     and word splitting. */
  new_list = shell_expand_word_list (new_list, eflags);

  /* Okay, we're almost done.  Now let's just do some filename
     globbing. */
  if (new_list)
    {
      if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0)
	/* Glob expand the word list unless globbing has been disabled. */
	new_list = glob_expand_word_list (new_list, eflags);
      else
	/* Dequote the words, because we're not performing globbing. */
	new_list = dequote_list (new_list);
    }

  if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
    {
      sh_wassign_func_t *assign_func;
      int is_special_builtin, is_builtin_or_func;

      /* If the remainder of the words expand to nothing, Posix.2 requires
	 that the variable and environment assignments affect the shell's
	 environment. */
      assign_func = new_list ? assign_in_env : do_word_assignment;
      tempenv_assign_error = 0;

      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
      /* Posix says that special builtins exit if a variable assignment error
	 occurs in an assignment preceding it. */
      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));

      for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
	{
	  savecmd = this_command_name;
	  this_command_name = (char *)NULL;
	  assigning_in_environment = (assign_func == assign_in_env);
	  tint = (*assign_func) (temp_list->word, is_builtin_or_func);
	  assigning_in_environment = 0;
	  this_command_name = savecmd;
	  /* Variable assignment errors in non-interactive shells running
	     in Posix.2 mode cause the shell to exit. */
	  if (tint == 0)
	    {
	      if (assign_func == do_word_assignment)
		{
		  last_command_exit_value = EXECUTION_FAILURE;
		  if (interactive_shell == 0 && posixly_correct && is_special_builtin)
		    exp_jump_to_top_level (FORCE_EOF);
		  else
		    exp_jump_to_top_level (DISCARD);
		}
	      else
		tempenv_assign_error++;
	    }
	}

      dispose_words (subst_assign_varlist);
      subst_assign_varlist = (WORD_LIST *)NULL;
    }

  return (new_list);


init_unix_command_map ()
{
  cmd_xmap = rl_make_bare_keymap ();
}


static char *
list_remove_pattern (list, pattern, patspec, itype, quoted)
     WORD_LIST *list;
     char *pattern;
     int patspec, itype, quoted;
{
  WORD_LIST *new, *l;
  WORD_DESC *w;
  char *tword;

  for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
    {
      tword = remove_pattern (l->word->word, pattern, patspec);
      w = alloc_word_desc ();
      w->word = tword ? tword : savestring ("");
      new = make_word_list (w, new);
    }

  l = REVERSE_LIST (new, WORD_LIST *);
  tword = string_list_pos_params (itype, l, quoted);
  dispose_words (l);

  return (tword);


void
unlink_fifo (fd)
     int fd;
{
  if (dev_fd_list[fd])
    {
      close (fd);
      dev_fd_list[fd] = 0;
      nfds--;
    }


void
unlink_fifo_list ()
{
  int saved, i, j;

  if (nfifo == 0)
    return;

  for (i = saved = 0; i < nfifo; i++)
    {
      if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
	{
	  unlink (fifo_list[i].file);
	  free (fifo_list[i].file);
	  fifo_list[i].file = (char *)NULL;
	  fifo_list[i].proc = -1;
	}
      else
	saved++;
    }

  /* If we didn't remove some of the FIFOs, compact the list. */
  if (saved)
    {
      for (i = j = 0; i < nfifo; i++)
	if (fifo_list[i].file)
	  {
	    fifo_list[j].file = fifo_list[i].file;
	    fifo_list[j].proc = fifo_list[i].proc;
	    j++;
	  }
      nfifo = j;
    }
  else
    nfifo = 0;


char *
dequote_escapes (string)
     char *string;
{
  register char *s, *t, *s1;
  size_t slen;
  char *result, *send;
  int quote_spaces;
  DECLARE_MBSTATE;

  if (string == 0)
    return string;

  slen = strlen (string);
  send = string + slen;

  t = result = (char *)xmalloc (slen + 1);

  if (strchr (string, CTLESC) == 0)
    return (strcpy (result, string));

  quote_spaces = (ifs_value && *ifs_value == 0);

  s = string;
  while (*s)
    {
      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' ')))
	{
	  s++;
	  if (*s == '\0')
	    break;
	}
      COPY_CHAR_P (t, s, send);
    }
  *t = '\0';

  return result;


char *
extract_process_subst (string, starter, sindex, xflags)
     char *string;
     char *starter;
     int *sindex;
     int xflags;
{
#if 0
  return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND));
#else
  xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
  return (xparse_dolparen (string, string+*sindex, sindex, xflags));
#endif


static SHELL_VAR *
do_compound_assignment (name, value, flags)
     char *name, *value;
     int flags;
{
  SHELL_VAR *v;
  int mklocal, mkassoc, mkglobal;
  WORD_LIST *list;

  mklocal = flags & ASS_MKLOCAL;
  mkassoc = flags & ASS_MKASSOC;
  mkglobal = flags & ASS_MKGLOBAL;

  if (mklocal && variable_context)
    {
      v = find_variable (name);
      if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v)))
	{
	  if (v && readonly_p (v))
	    err_readonly (name);
	  return (v);	/* XXX */
	}
      list = expand_compound_array_assignment (v, value, flags);
      if (mkassoc)
	v = make_local_assoc_variable (name);
      else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
        v = make_local_array_variable (name, 0);
      if (v)
	assign_compound_array_list (v, list, flags);
      if (list)
	dispose_words (list);
    }
  /* In a function but forcing assignment in global context */
  else if (mkglobal && variable_context)
    {
      v = find_global_variable (name);
      if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v)))
	{
	  if (v && readonly_p (v))
	    err_readonly (name);
	  return (v);	/* XXX */
	}
      list = expand_compound_array_assignment (v, value, flags);
      if (v == 0 && mkassoc)
	v = make_new_assoc_variable (name);
      else if (v && mkassoc && assoc_p (v) == 0)
	v = convert_var_to_assoc (v);
      else if (v == 0)
	v = make_new_array_variable (name);
      else if (v && mkassoc == 0 && array_p (v) == 0)
	v = convert_var_to_array (v);
      if (v)
	assign_compound_array_list (v, list, flags);
      if (list)
	dispose_words (list);
    }
  else
    {
      v = assign_array_from_string (name, value, flags);
      if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v)))
	{
	  if (v && readonly_p (v))
	    err_readonly (name);
	  return (v);	/* XXX */
	}
    }

  return (v);


WORD_LIST *
expand_string (string, quoted)
     char *string;
     int quoted;
{
  WORD_LIST *result;

  if (string == 0 || *string == '\0')
    return ((WORD_LIST *)NULL);

  result = expand_string_leave_quoted (string, quoted);
  return (result ? dequote_list (result) : result);


static void test_long_data_str1()
{
  MYSQL_STMT *stmt;
  int        rc, i;
  char       data[255];
  long       length;
  ulong      max_blob_length, blob_length, length1;
  my_bool    true_value;
  MYSQL_RES  *result;
  MYSQL_BIND my_bind[2];
  MYSQL_FIELD *field;
  char query[MAX_TEST_QUERY_LENGTH];

  myheader("test_long_data_str1");

  rc= mysql_autocommit(mysql, TRUE);
  myquery(rc);

  rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_long_data_str");
  myquery(rc);

  rc= mysql_query(mysql, "CREATE TABLE test_long_data_str(longstr long varchar, blb long varbinary)");
  myquery(rc);

  strmov(query, "INSERT INTO test_long_data_str VALUES(?, ?)");
  stmt= mysql_simple_prepare(mysql, query);
  check_stmt(stmt);

  verify_param_count(stmt, 2);

  /* Always memset all members of bind parameter */
  memset(my_bind, 0, sizeof(my_bind));

  my_bind[0].buffer= data;            /* string data */
  my_bind[0].buffer_length= sizeof(data);
  my_bind[0].length= &length1;
  my_bind[0].buffer_type= MYSQL_TYPE_STRING;
  length1= 0;

  my_bind[1]= my_bind[0];
  my_bind[1].buffer_type= MYSQL_TYPE_BLOB;

  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_execute(stmt, rc);
  length= sprintf(data, "MySQL AB");

  /* supply data in pieces */
  for (i= 0; i < 3; i++)
  {
    rc= mysql_stmt_send_long_data(stmt, 0, data, length);
    check_execute(stmt, rc);

    rc= mysql_stmt_send_long_data(stmt, 1, data, 2);
    check_execute(stmt, rc);
  }

  /* execute */
  rc= mysql_stmt_execute(stmt);
  if (!opt_silent)
    fprintf(stdout, " mysql_stmt_execute() returned %d\n", rc);
  check_execute(stmt, rc);

  mysql_stmt_close(stmt);

  rc= mysql_commit(mysql);
  myquery(rc);

  /* now fetch the results ..*/
  rc= mysql_query(mysql, "SELECT LENGTH(longstr), longstr, LENGTH(blb), blb FROM test_long_data_str");
  myquery(rc);

  /* get the result */
  result= mysql_store_result(mysql);

  mysql_field_seek(result, 1);
  field= mysql_fetch_field(result);
  max_blob_length= field->max_length;

  mytest(result);

  rc= my_process_result_set(result);
  DIE_UNLESS(rc == 1);
  mysql_free_result(result);

  sprintf(data, "%ld", (long)i*length);
  verify_col_data("test_long_data_str", "length(longstr)", data);

  sprintf(data, "%d", i*2);
  verify_col_data("test_long_data_str", "length(blb)", data);

  /* Test length of field->max_length */
  stmt= mysql_simple_prepare(mysql, "SELECT * from test_long_data_str");
  check_stmt(stmt);
  verify_param_count(stmt, 0);

  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);

  rc= mysql_stmt_store_result(stmt);
  check_execute(stmt, rc);

  result= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_fields(result);

  /* First test what happens if STMT_ATTR_UPDATE_MAX_LENGTH is not used */
  DIE_UNLESS(field->max_length == 0);
  mysql_free_result(result);

  /* Enable updating of field->max_length */
  true_value= 1;
  mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &true_value);
  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);

  rc= mysql_stmt_store_result(stmt);
  check_execute(stmt, rc);

  result= mysql_stmt_result_metadata(stmt);
  field= mysql_fetch_fields(result);

  DIE_UNLESS(field->max_length == max_blob_length);

  /* Fetch results into a data buffer that is smaller than data */
  memset(my_bind, 0, sizeof(*my_bind));
  my_bind[0].buffer_type= MYSQL_TYPE_BLOB;
  my_bind[0].buffer= (void *) &data; /* this buffer won't be altered */
  my_bind[0].buffer_length= 16;
  my_bind[0].length= &blob_length;
  my_bind[0].error= &my_bind[0].error_value;
  rc= mysql_stmt_bind_result(stmt, my_bind);
  data[16]= 0;

  rc= mysql_stmt_fetch(stmt);
  DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
  DIE_UNLESS(my_bind[0].error_value);
  DIE_UNLESS(strlen(data) == 16);
  DIE_UNLESS(blob_length == max_blob_length);

  /* Fetch all data */
  memset((my_bind+1), 0, sizeof(*my_bind));
  my_bind[1].buffer_type= MYSQL_TYPE_BLOB;
  my_bind[1].buffer= (void *) &data; /* this buffer won't be altered */
  my_bind[1].buffer_length= sizeof(data);
  my_bind[1].length= &blob_length;
  memset(data, 0, sizeof(data));
  mysql_stmt_fetch_column(stmt, my_bind+1, 0, 0);
  DIE_UNLESS(strlen(data) == max_blob_length);

  mysql_free_result(result);
  mysql_stmt_close(stmt);

  /* Drop created table */
  rc= mysql_query(mysql, "DROP TABLE test_long_data_str");
  myquery(rc);
}


static void test_bug17512527()
{
  MYSQL *conn1, *conn2;
  MYSQL_STMT *stmt1, *stmt2;
  const char *stmt1_txt= "SELECT NOW();";
  const char *stmt2_txt= "SELECT 1;";
  unsigned long thread_id;
  char query[MAX_TEST_QUERY_LENGTH];
  int rc;

  conn1= client_connect(0, MYSQL_PROTOCOL_DEFAULT, 1);
  conn2= client_connect(0, MYSQL_PROTOCOL_DEFAULT, 0);

  stmt1 = mysql_stmt_init(conn1);
  check_stmt(stmt1);
  rc= mysql_stmt_prepare(stmt1, stmt1_txt, strlen(stmt1_txt));
  check_execute(stmt1, rc);

  thread_id= mysql_thread_id(conn1);
  sprintf(query, "KILL %lu", thread_id);
  if (thread_query(query))
    exit(1);

  /*
    After the connection is killed, the connection is
    re-established due to the reconnect flag.
  */
  stmt2 = mysql_stmt_init(conn1);
  check_stmt(stmt2);

  rc= mysql_stmt_prepare(stmt2, stmt2_txt, strlen(stmt2_txt));
  check_execute(stmt1, rc);

  mysql_stmt_close(stmt2);
  mysql_stmt_close(stmt1);

  mysql_close(conn1);
  mysql_close(conn2);
}


static void test_datetime_ranges()
{
  const char *stmt_text;
  int rc, i;
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[6];
  MYSQL_TIME tm[6];

  myheader("test_datetime_ranges");

  stmt_text= "drop table if exists t1";
  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);

  stmt_text= "create table t1 (year datetime, month datetime, day datetime, "
                              "hour datetime, min datetime, sec datetime)";
  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);

  stmt= mysql_simple_prepare(mysql,
                             "INSERT INTO t1 VALUES (?, ?, ?, ?, ?, ?)");
  check_stmt(stmt);
  verify_param_count(stmt, 6);

  memset(my_bind, 0, sizeof(my_bind));
  for (i= 0; i < 6; i++)
  {
    my_bind[i].buffer_type= MYSQL_TYPE_DATETIME;
    my_bind[i].buffer= &tm[i];
  }
  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_execute(stmt, rc);

  tm[0].year= 2004; tm[0].month= 11; tm[0].day= 10;
  tm[0].hour= 12; tm[0].minute= 30; tm[0].second= 30;
  tm[0].second_part= 0; tm[0].neg= 0;

  tm[5]= tm[4]= tm[3]= tm[2]= tm[1]= tm[0];
  tm[0].year= 10000;  tm[1].month= 13; tm[2].day= 32;
  tm[3].hour= 24; tm[4].minute= 60; tm[5].second= 60;

  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);
  my_process_warnings(mysql, 12);

  verify_col_data("t1", "year", "0000-00-00 00:00:00");
  verify_col_data("t1", "month", "0000-00-00 00:00:00");
  verify_col_data("t1", "day", "0000-00-00 00:00:00");
  verify_col_data("t1", "hour", "0000-00-00 00:00:00");
  verify_col_data("t1", "min", "0000-00-00 00:00:00");
  verify_col_data("t1", "sec", "0000-00-00 00:00:00");

  mysql_stmt_close(stmt);

  stmt_text= "delete from t1";
  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);

  stmt= mysql_simple_prepare(mysql, "INSERT INTO t1 (year, month, day) "
                                    "VALUES (?, ?, ?)");
  check_stmt(stmt);
  verify_param_count(stmt, 3);

  /*
    We reuse contents of bind and tm arrays left from previous part of test.
  */
  for (i= 0; i < 3; i++)
    my_bind[i].buffer_type= MYSQL_TYPE_DATE;

  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_execute(stmt, rc);

  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);
  my_process_warnings(mysql, 6);

  verify_col_data("t1", "year", "0000-00-00 00:00:00");
  verify_col_data("t1", "month", "0000-00-00 00:00:00");
  verify_col_data("t1", "day", "0000-00-00 00:00:00");

  mysql_stmt_close(stmt);

  stmt_text= "drop table t1";
  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);

  stmt_text= "create table t1 (day_ovfl time, day time, hour time, min time, sec time)";
  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);

  stmt= mysql_simple_prepare(mysql,
                             "INSERT INTO t1 VALUES (?, ?, ?, ?, ?)");
  check_stmt(stmt);
  verify_param_count(stmt, 5);

  /*
    Again we reuse what we can from previous part of test.
  */
  for (i= 0; i < 5; i++)
    my_bind[i].buffer_type= MYSQL_TYPE_TIME;

  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_execute(stmt, rc);

  tm[0].year= 0; tm[0].month= 0; tm[0].day= 10;
  tm[0].hour= 12; tm[0].minute= 30; tm[0].second= 30;
  tm[0].second_part= 0; tm[0].neg= 0;

  tm[4]= tm[3]= tm[2]= tm[1]= tm[0];
  tm[0].day= 35; tm[1].day= 34; tm[2].hour= 30; tm[3].minute= 60; tm[4].second= 60;

  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);
  my_process_warnings(mysql, 2);

  verify_col_data("t1", "day_ovfl", "838:59:59");
  verify_col_data("t1", "day", "828:30:30");
  verify_col_data("t1", "hour", "270:30:30");
  verify_col_data("t1", "min", "00:00:00");
  verify_col_data("t1", "sec", "00:00:00");

  mysql_stmt_close(stmt);

  stmt_text= "drop table t1";
  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);
}


static HANDLE create_shared_memory(MYSQL *mysql, NET *net,
                                   DWORD connect_timeout)
{
  ulong smem_buffer_length = shared_memory_buffer_length + 4;
  /*
    event_connect_request is event object for start connection actions
    event_connect_answer is event object for confirm, that server put data
    handle_connect_file_map is file-mapping object, use for create shared
    memory
    handle_connect_map is pointer on shared memory
    handle_map is pointer on shared memory for client
    event_server_wrote,
    event_server_read,
    event_client_wrote,
    event_client_read are events for transfer data between server and client
    handle_file_map is file-mapping object, use for create shared memory
  */
  HANDLE event_connect_request = NULL;
  HANDLE event_connect_answer = NULL;
  HANDLE handle_connect_file_map = NULL;
  char *handle_connect_map = NULL;

  char *handle_map = NULL;
  HANDLE event_server_wrote = NULL;
  HANDLE event_server_read = NULL;
  HANDLE event_client_wrote = NULL;
  HANDLE event_client_read = NULL;
  HANDLE event_conn_closed = NULL;
  HANDLE handle_file_map = NULL;
  ulong connect_number;
  char connect_number_char[22], *p;
  char *tmp= NULL;
  char *suffix_pos;
  DWORD error_allow = 0;
  DWORD error_code = 0;
  DWORD event_access_rights= SYNCHRONIZE | EVENT_MODIFY_STATE;
  char *shared_memory_base_name = mysql->options.shared_memory_base_name;
  static const char *name_prefixes[] = {"","Global\\"};
  const char *prefix;
  int i;

  /*
    If this is NULL, somebody freed the MYSQL* options.  mysql_close()
    is a good candidate.  We don't just silently (re)set it to
    def_shared_memory_base_name as that would create really confusing/buggy
    behavior if the user passed in a different name on the command-line or
    in a my.cnf.
  */
  DBUG_ASSERT(shared_memory_base_name != NULL);

  /*
     get enough space base-name + '_' + longest suffix we might ever send
   */
  if (!(tmp= (char *)my_malloc(strlen(shared_memory_base_name) + 32L, MYF(MY_FAE))))
    goto err;

  /*
    The name of event and file-mapping events create agree next rule:
    shared_memory_base_name+unique_part
    Where:
    shared_memory_base_name is unique value for each server
    unique_part is uniquel value for each object (events and file-mapping)
  */
  for (i = 0; i< array_elements(name_prefixes); i++)
  {
    prefix= name_prefixes[i];
    suffix_pos = strxmov(tmp, prefix , shared_memory_base_name, "_", NullS);
    strmov(suffix_pos, "CONNECT_REQUEST");
    event_connect_request= OpenEvent(event_access_rights, FALSE, tmp);
    if (event_connect_request)
    {
      break;
    }
  }
  if (!event_connect_request)
  {
    error_allow = CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR;
    goto err;
  }
  strmov(suffix_pos, "CONNECT_ANSWER");
  if (!(event_connect_answer= OpenEvent(event_access_rights,FALSE,tmp)))
  {
    error_allow = CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR;
    goto err;
  }
  strmov(suffix_pos, "CONNECT_DATA");
  if (!(handle_connect_file_map= OpenFileMapping(FILE_MAP_WRITE,FALSE,tmp)))
  {
    error_allow = CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR;
    goto err;
  }
  if (!(handle_connect_map= MapViewOfFile(handle_connect_file_map,
					  FILE_MAP_WRITE,0,0,sizeof(DWORD))))
  {
    error_allow = CR_SHARED_MEMORY_CONNECT_MAP_ERROR;
    goto err;
  }

  /* Send to server request of connection */
  if (!SetEvent(event_connect_request))
  {
    error_allow = CR_SHARED_MEMORY_CONNECT_SET_ERROR;
    goto err;
  }

  /* Wait of answer from server */
  if (WaitForSingleObject(event_connect_answer, connect_timeout) !=
      WAIT_OBJECT_0)
  {
    error_allow = CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR;
    goto err;
  }

  /* Get number of connection */
  connect_number = uint4korr(handle_connect_map);/*WAX2*/
  p= int10_to_str(connect_number, connect_number_char, 10);

  /*
    The name of event and file-mapping events create agree next rule:
    shared_memory_base_name+unique_part+number_of_connection

    Where:
    shared_memory_base_name is uniquel value for each server
    unique_part is uniquel value for each object (events and file-mapping)
    number_of_connection is number of connection between server and client
  */
  suffix_pos = strxmov(tmp, prefix , shared_memory_base_name, "_", connect_number_char,
		       "_", NullS);
  strmov(suffix_pos, "DATA");
  if ((handle_file_map = OpenFileMapping(FILE_MAP_WRITE,FALSE,tmp)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_FILE_MAP_ERROR;
    goto err2;
  }
  if ((handle_map = MapViewOfFile(handle_file_map,FILE_MAP_WRITE,0,0,
				  smem_buffer_length)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_MAP_ERROR;
    goto err2;
  }

  strmov(suffix_pos, "SERVER_WROTE");
  if ((event_server_wrote = OpenEvent(event_access_rights,FALSE,tmp)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_EVENT_ERROR;
    goto err2;
  }

  strmov(suffix_pos, "SERVER_READ");
  if ((event_server_read = OpenEvent(event_access_rights,FALSE,tmp)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_EVENT_ERROR;
    goto err2;
  }

  strmov(suffix_pos, "CLIENT_WROTE");
  if ((event_client_wrote = OpenEvent(event_access_rights,FALSE,tmp)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_EVENT_ERROR;
    goto err2;
  }

  strmov(suffix_pos, "CLIENT_READ");
  if ((event_client_read = OpenEvent(event_access_rights,FALSE,tmp)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_EVENT_ERROR;
    goto err2;
  }

  strmov(suffix_pos, "CONNECTION_CLOSED");
  if ((event_conn_closed = OpenEvent(event_access_rights,FALSE,tmp)) == NULL)
  {
    error_allow = CR_SHARED_MEMORY_EVENT_ERROR;
    goto err2;
  }
  /*
    Set event that server should send data
  */
  SetEvent(event_server_read);

err2:
  if (error_allow == 0)
  {
    net->vio= vio_new_win32shared_memory(handle_file_map,handle_map,
                                         event_server_wrote,
                                         event_server_read,event_client_wrote,
                                         event_client_read,event_conn_closed);
  }
  else
  {
    error_code = GetLastError();
    if (event_server_read)
      CloseHandle(event_server_read);
    if (event_server_wrote)
      CloseHandle(event_server_wrote);
    if (event_client_read)
      CloseHandle(event_client_read);
    if (event_client_wrote)
      CloseHandle(event_client_wrote);
    if (event_conn_closed)
      CloseHandle(event_conn_closed);
    if (handle_map)
      UnmapViewOfFile(handle_map);
    if (handle_file_map)
      CloseHandle(handle_file_map);
  }
err:
  my_free(tmp);
  if (error_allow)
    error_code = GetLastError();
  if (event_connect_request)
    CloseHandle(event_connect_request);
  if (event_connect_answer)
    CloseHandle(event_connect_answer);
  if (handle_connect_map)
    UnmapViewOfFile(handle_connect_map);
  if (handle_connect_file_map)
    CloseHandle(handle_connect_file_map);
  if (error_allow)
  {
    if (error_allow == CR_SHARED_MEMORY_EVENT_ERROR)
      set_mysql_extended_error(mysql, error_allow, unknown_sqlstate,
                               ER(error_allow), suffix_pos, error_code);
    else
      set_mysql_extended_error(mysql, error_allow, unknown_sqlstate,
                               ER(error_allow), error_code);
    return(INVALID_HANDLE_VALUE);
  }
  return(handle_map);
}


static void test_bug6049()
{
  MYSQL_STMT *stmt;
  MYSQL_BIND my_bind[1];
  MYSQL_RES *res;
  MYSQL_ROW row;
  const char *stmt_text;
  char buffer[30];
  ulong length;
  int rc;

  myheader("test_bug6049");

  stmt_text= "SELECT MAKETIME(-25, 12, 12)";

  rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
  myquery(rc);
  res= mysql_store_result(mysql);
  row= mysql_fetch_row(res);

  stmt= mysql_stmt_init(mysql);
  rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text));
  check_execute(stmt, rc);
  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);

  memset(my_bind, 0, sizeof(my_bind));
  my_bind[0].buffer_type    = MYSQL_TYPE_STRING;
  my_bind[0].buffer         = &buffer;
  my_bind[0].buffer_length  = sizeof(buffer);
  my_bind[0].length         = &length;

  mysql_stmt_bind_result(stmt, my_bind);
  rc= mysql_stmt_fetch(stmt);
  DIE_UNLESS(rc == 0);

  if (!opt_silent)
  {
    printf("Result from query: %s\n", row[0]);
    printf("Result from prepared statement: %s\n", (char*) buffer);
  }

  DIE_UNLESS(strcmp(row[0], (char*) buffer) == 0);

  mysql_free_result(res);
  mysql_stmt_close(stmt);
}


static void test_manual_sample()
{
  unsigned int param_count;
  MYSQL_STMT   *stmt;
  short        small_data;
  int          int_data;
  int          rc;
  char         str_data[50];
  ulonglong    affected_rows;
  MYSQL_BIND   my_bind[3];
  my_bool      is_null;
  char query[MAX_TEST_QUERY_LENGTH];

  myheader("test_manual_sample");

  /*
    Sample which is incorporated directly in the manual under Prepared
    statements section (Example from mysql_stmt_execute()
  */

  mysql_autocommit(mysql, 1);
  if (mysql_query(mysql, "DROP TABLE IF EXISTS test_table"))
  {
    fprintf(stderr, "\n drop table failed");
    fprintf(stderr, "\n %s", mysql_error(mysql));
    exit(1);
  }
  if (mysql_query(mysql, "CREATE TABLE test_table(col1 int, col2 varchar(50), \
                                                 col3 smallint, \
                                                 col4 timestamp)"))
  {
    fprintf(stderr, "\n create table failed");
    fprintf(stderr, "\n %s", mysql_error(mysql));
    exit(1);
  }

  /* Prepare a insert query with 3 parameters */
  strmov(query, "INSERT INTO test_table(col1, col2, col3) values(?, ?, ?)");
  if (!(stmt= mysql_simple_prepare(mysql, query)))
  {
    fprintf(stderr, "\n prepare, insert failed");
    fprintf(stderr, "\n %s", mysql_error(mysql));
    exit(1);
  }
  if (!opt_silent)
    fprintf(stdout, "\n prepare, insert successful");

  /* Get the parameter count from the statement */
  param_count= mysql_stmt_param_count(stmt);

  if (!opt_silent)
    fprintf(stdout, "\n total parameters in insert: %d", param_count);
  if (param_count != 3) /* validate parameter count */
  {
    fprintf(stderr, "\n invalid parameter count returned by MySQL");
    exit(1);
  }

  /* Bind the data for the parameters */

  /*
    We need to memset bind structure because mysql_stmt_bind_param checks all
    its members.
  */
  memset(my_bind, 0, sizeof(my_bind));

  /* INTEGER PART */
  my_bind[0].buffer_type= MYSQL_TYPE_LONG;
  my_bind[0].buffer= (void *)&int_data;

  /* STRING PART */
  my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING;
  my_bind[1].buffer= (void *)str_data;
  my_bind[1].buffer_length= sizeof(str_data);

  /* SMALLINT PART */
  my_bind[2].buffer_type= MYSQL_TYPE_SHORT;
  my_bind[2].buffer= (void *)&small_data;
  my_bind[2].is_null= &is_null;
  is_null= 0;

  /* Bind the buffers */
  if (mysql_stmt_bind_param(stmt, my_bind))
  {
    fprintf(stderr, "\n param bind failed");
    fprintf(stderr, "\n %s", mysql_stmt_error(stmt));
    exit(1);
  }

  /* Specify the data */
  int_data= 10;             /* integer */
  strmov(str_data, "MySQL"); /* string  */

  /* INSERT SMALLINT data as NULL */
  is_null= 1;

  /* Execute the insert statement - 1*/
  if (mysql_stmt_execute(stmt))
  {
    fprintf(stderr, "\n execute 1 failed");
    fprintf(stderr, "\n %s", mysql_stmt_error(stmt));
    exit(1);
  }

  /* Get the total rows affected */
  affected_rows= mysql_stmt_affected_rows(stmt);

  if (!opt_silent)
    fprintf(stdout, "\n total affected rows: %ld", (ulong) affected_rows);
  if (affected_rows != 1) /* validate affected rows */
  {
    fprintf(stderr, "\n invalid affected rows by MySQL");
    exit(1);
  }

  /* Re-execute the insert, by changing the values */
  int_data= 1000;
  strmov(str_data, "The most popular open source database");
  small_data= 1000;         /* smallint */
  is_null= 0;               /* reset */

  /* Execute the insert statement - 2*/
  if (mysql_stmt_execute(stmt))
  {
    fprintf(stderr, "\n execute 2 failed");
    fprintf(stderr, "\n %s", mysql_stmt_error(stmt));
    exit(1);
  }

  /* Get the total rows affected */
  affected_rows= mysql_stmt_affected_rows(stmt);

  if (!opt_silent)
    fprintf(stdout, "\n total affected rows: %ld", (ulong) affected_rows);
  if (affected_rows != 1) /* validate affected rows */
  {
    fprintf(stderr, "\n invalid affected rows by MySQL");
    exit(1);
  }

  /* Close the statement */
  if (mysql_stmt_close(stmt))
  {
    fprintf(stderr, "\n failed while closing the statement");
    fprintf(stderr, "\n %s", mysql_stmt_error(stmt));
    exit(1);
  }
  rc= my_stmt_result("SELECT * FROM test_table");
  DIE_UNLESS(rc == 2);

  /* DROP THE TABLE */
  if (mysql_query(mysql, "DROP TABLE test_table"))
  {
    fprintf(stderr, "\n drop table failed");
    fprintf(stderr, "\n %s", mysql_error(mysql));
    exit(1);
  }
  if (!opt_silent)
    fprintf(stdout, "Success !!!");
}


static void test_double_compare()
{
  MYSQL_STMT *stmt;
  int        rc;
  char       real_data[10], tiny_data;
  double     double_data;
  MYSQL_RES  *result;
  MYSQL_BIND my_bind[3];
  ulong      length[3];
  char query[MAX_TEST_QUERY_LENGTH];

  myheader("test_double_compare");

  rc= mysql_autocommit(mysql, TRUE);
  myquery(rc);

  rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_double_compare");
  myquery(rc);

  rc= mysql_query(mysql, "CREATE TABLE test_double_compare(col1 tinyint, "
                         " col2 float, col3 double )");
  myquery(rc);

  rc= mysql_query(mysql, "INSERT INTO test_double_compare "
                         "VALUES (1, 10.2, 34.5)");
  myquery(rc);

  strmov(query, "UPDATE test_double_compare SET col1=100 "
                "WHERE col1 = ? AND col2 = ? AND COL3 = ?");
  stmt= mysql_simple_prepare(mysql, query);
  check_stmt(stmt);

  verify_param_count(stmt, 3);

  /* Always memset bind array because there can be internal members */
  memset(my_bind, 0, sizeof(my_bind));

  /* tinyint */
  my_bind[0].buffer_type= MYSQL_TYPE_TINY;
  my_bind[0].buffer= (void *)&tiny_data;

  /* string->float */
  my_bind[1].buffer_type= MYSQL_TYPE_STRING;
  my_bind[1].buffer= (void *)&real_data;
  my_bind[1].buffer_length= sizeof(real_data);
  my_bind[1].length= &length[1];
  length[1]= 10;

  /* double */
  my_bind[2].buffer_type= MYSQL_TYPE_DOUBLE;
  my_bind[2].buffer= (void *)&double_data;

  tiny_data= 1;
  strmov(real_data, "10.2");
  double_data= 34.5;
  rc= mysql_stmt_bind_param(stmt, my_bind);
  check_execute(stmt, rc);

  rc= mysql_stmt_execute(stmt);
  check_execute(stmt, rc);

  verify_affected_rows(0);

  mysql_stmt_close(stmt);

  /* now fetch the results ..*/
  rc= mysql_commit(mysql);
  myquery(rc);

  /* test the results now, only one row should exist */
  rc= mysql_query(mysql, "SELECT * FROM test_double_compare");
  myquery(rc);

  /* get the result */
  result= mysql_store_result(mysql);
  mytest(result);

  rc= my_process_result_set(result);
  DIE_UNLESS((int)tiny_data == rc);
  mysql_free_result(result);
}


static bool work_fixup_init(void *addr, enum debug_obj_state state)
{
	struct work_struct *work = addr;

	switch (state) {
	case ODEBUG_STATE_ACTIVE:
		cancel_work_sync(work);
		debug_object_init(work, &work_debug_descr);
		return true;
	default:
		return false;
	}
}


static inline int __hrtimer_hres_active(struct hrtimer_cpu_base *b) { return 0; }


static void pool_mayday_timeout(unsigned long __pool)
{
	struct worker_pool *pool = (void *)__pool;
	struct work_struct *work;

	spin_lock_irq(&pool->lock);
	spin_lock(&wq_mayday_lock);		/* for wq->maydays */

	if (need_to_create_worker(pool)) {
		/*
		 * We've been trying to create a new worker but
		 * haven't been successful.  We might be hitting an
		 * allocation deadlock.  Send distress signals to
		 * rescuers.
		 */
		list_for_each_entry(work, &pool->worklist, entry)
			send_mayday(work);
	}

	spin_unlock(&wq_mayday_lock);
	spin_unlock_irq(&pool->lock);

	mod_timer(&pool->mayday_timer, jiffies + MAYDAY_INTERVAL);
}


static void timer_list_stop(struct seq_file *seq, void *v)
{
}


static bool is_chained_work(struct workqueue_struct *wq)
{
	struct worker *worker;

	worker = current_wq_worker();
	/*
	 * Return %true iff I'm a worker execuing a work item on @wq.  If
	 * I'm @worker, it's safe to dereference it without locking.
	 */
	return worker && worker->current_pwq->wq == wq;
}


static void check_flush_dependency(struct workqueue_struct *target_wq,
				   struct work_struct *target_work)
{
	work_func_t target_func = target_work ? target_work->func : NULL;
	struct worker *worker;

	if (target_wq->flags & WQ_MEM_RECLAIM)
		return;

	worker = current_wq_worker();

	WARN_ONCE(current->flags & PF_MEMALLOC,
		  "workqueue: PF_MEMALLOC task %d(%s) is flushing !WQ_MEM_RECLAIM %s:%pf",
		  current->pid, current->comm, target_wq->name, target_func);
	WARN_ONCE(worker && ((worker->current_pwq->wq->flags &
			      (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM),
		  "workqueue: WQ_MEM_RECLAIM %s:%pf is flushing !WQ_MEM_RECLAIM %s:%pf",
		  worker->current_pwq->wq->name, worker->current_func,
		  target_wq->name, target_func);
}


struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
						    void *data, int node,
						    const char namefmt[],
						    va_list args)
{
	DECLARE_COMPLETION_ONSTACK(done);
	struct task_struct *task;
	struct kthread_create_info *create = kmalloc(sizeof(*create),
						     GFP_KERNEL);

	if (!create)
		return ERR_PTR(-ENOMEM);
	create->threadfn = threadfn;
	create->data = data;
	create->node = node;
	create->done = &done;

	spin_lock(&kthread_create_lock);
	list_add_tail(&create->list, &kthread_create_list);
	spin_unlock(&kthread_create_lock);

	wake_up_process(kthreadd_task);
	/*
	 * Wait for completion in killable state, for I might be chosen by
	 * the OOM killer while kthreadd is trying to allocate memory for
	 * new kernel thread.
	 */
	if (unlikely(wait_for_completion_killable(&done))) {
		/*
		 * If I was SIGKILLed before kthreadd (or new kernel thread)
		 * calls complete(), leave the cleanup of this structure to
		 * that thread.
		 */
		if (xchg(&create->done, NULL))
			return ERR_PTR(-EINTR);
		/*
		 * kthreadd (or new kernel thread) will call complete()
		 * shortly.
		 */
		wait_for_completion(&done);
	}
	task = create->result;
	if (!IS_ERR(task)) {
		static const struct sched_param param = { .sched_priority = 0 };

		vsnprintf(task->comm, sizeof(task->comm), namefmt, args);
		/*
		 * root may have changed our (kthreadd's) priority or CPU mask.
		 * The kernel thread should not inherit these properties.
		 */
		sched_setscheduler_nocheck(task, SCHED_NORMAL, &param);
		set_cpus_allowed_ptr(task, cpu_all_mask);
	}
	kfree(create);
	return task;
}


static void apply_wqattrs_unlock(void)
{
	mutex_unlock(&wq_pool_mutex);
	put_online_cpus();
}


static int workqueue_apply_unbound_cpumask(void)
{
	LIST_HEAD(ctxs);
	int ret = 0;
	struct workqueue_struct *wq;
	struct apply_wqattrs_ctx *ctx, *n;

	lockdep_assert_held(&wq_pool_mutex);

	list_for_each_entry(wq, &workqueues, list) {
		if (!(wq->flags & WQ_UNBOUND))
			continue;
		/* creating multiple pwqs breaks ordering guarantee */
		if (wq->flags & __WQ_ORDERED)
			continue;

		ctx = apply_wqattrs_prepare(wq, wq->unbound_attrs);
		if (!ctx) {
			ret = -ENOMEM;
			break;
		}

		list_add_tail(&ctx->list, &ctxs);
	}

	list_for_each_entry_safe(ctx, n, &ctxs, list) {
		if (!ret)
			apply_wqattrs_commit(ctx);
		apply_wqattrs_cleanup(ctx);
	}

	return ret;
}


int workqueue_prepare_cpu(unsigned int cpu)
{
	struct worker_pool *pool;

	for_each_cpu_worker_pool(pool, cpu) {
		if (pool->nr_workers)
			continue;
		if (!create_worker(pool))
			return -ENOMEM;
	}
	return 0;
}


static inline void debug_activate(struct hrtimer *timer)
{
	debug_hrtimer_activate(timer);
	trace_hrtimer_start(timer);
}


schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta,
			       const enum hrtimer_mode mode, int clock)
{
	struct hrtimer_sleeper t;

	/*
	 * Optimize when a zero timeout value is given. It does not
	 * matter whether this is an absolute or a relative time.
	 */
	if (expires && *expires == 0) {
		__set_current_state(TASK_RUNNING);
		return 0;
	}

	/*
	 * A NULL parameter means "infinite"
	 */
	if (!expires) {
		schedule();
		return -EINTR;
	}

	hrtimer_init_on_stack(&t.timer, clock, mode);
	hrtimer_set_expires_range_ns(&t.timer, *expires, delta);

	hrtimer_init_sleeper(&t, current);

	hrtimer_start_expires(&t.timer, mode);

	if (likely(t.task))
		schedule();

	hrtimer_cancel(&t.timer);
	destroy_hrtimer_on_stack(&t.timer);

	__set_current_state(TASK_RUNNING);

	return !t.task ? 0 : -EINTR;
}


void *kthread_probe_data(struct task_struct *task)
{
	struct kthread *kthread = to_kthread(task);
	void *data = NULL;

	probe_kernel_read(&data, &kthread->data, sizeof(data));
	return data;
}


int tsk_fork_get_node(struct task_struct *tsk)
{
#ifdef CONFIG_NUMA
	if (tsk == kthreadd_task)
		return tsk->pref_node_fork;
#endif
	return NUMA_NO_NODE;
}


static inline ktime_t hrtimer_update_base(struct hrtimer_cpu_base *base)
{
	ktime_t *offs_real = &base->clock_base[HRTIMER_BASE_REALTIME].offset;
	ktime_t *offs_boot = &base->clock_base[HRTIMER_BASE_BOOTTIME].offset;
	ktime_t *offs_tai = &base->clock_base[HRTIMER_BASE_TAI].offset;

	return ktime_get_update_offsets_now(&base->clock_was_set_seq,
					    offs_real, offs_boot, offs_tai);
}


static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long),
			  unsigned long data)
{
	int count = preempt_count();

#ifdef CONFIG_LOCKDEP
	/*
	 * It is permissible to free the timer from inside the
	 * function that is called from it, this we need to take into
	 * account for lockdep too. To avoid bogus "held lock freed"
	 * warnings as well as problems when looking into
	 * timer->lockdep_map, make a copy and use that here.
	 */
	struct lockdep_map lockdep_map;

	lockdep_copy_map(&lockdep_map, &timer->lockdep_map);
#endif
	/*
	 * Couple the lock chain with the lock chain at
	 * del_timer_sync() by acquiring the lock_map around the fn()
	 * call here and in del_timer_sync().
	 */
	lock_map_acquire(&lockdep_map);

	trace_timer_expire_entry(timer);
	fn(data);
	trace_timer_expire_exit(timer);

	lock_map_release(&lockdep_map);

	if (count != preempt_count()) {
		WARN_ONCE(1, "timer: %pF preempt leak: %08x -> %08x\n",
			  fn, count, preempt_count());
		/*
		 * Restore the preempt count. That gives us a decent
		 * chance to survive and extract information. If the
		 * callback kept a lock held, bad luck, but not worse
		 * than the BUG() we had.
		 */
		preempt_count_set(count);
	}
}


static void worker_attach_to_pool(struct worker *worker,
				   struct worker_pool *pool)
{
	mutex_lock(&pool->attach_mutex);

	/*
	 * set_cpus_allowed_ptr() will fail if the cpumask doesn't have any
	 * online CPUs.  It'll be re-applied when any of the CPUs come up.
	 */
	set_cpus_allowed_ptr(worker->task, pool->attrs->cpumask);

	/*
	 * The pool->attach_mutex ensures %POOL_DISASSOCIATED remains
	 * stable across this function.  See the comments above the
	 * flag definition for details.
	 */
	if (pool->flags & POOL_DISASSOCIATED)
		worker->flags |= WORKER_UNBOUND;

	list_add_tail(&worker->node, &pool->workers);

	mutex_unlock(&pool->attach_mutex);
}


static void wq_watchdog_timer_fn(unsigned long data)
{
	unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ;
	bool lockup_detected = false;
	struct worker_pool *pool;
	int pi;

	if (!thresh)
		return;

	rcu_read_lock();

	for_each_pool(pool, pi) {
		unsigned long pool_ts, touched, ts;

		if (list_empty(&pool->worklist))
			continue;

		/* get the latest of pool and touched timestamps */
		pool_ts = READ_ONCE(pool->watchdog_ts);
		touched = READ_ONCE(wq_watchdog_touched);

		if (time_after(pool_ts, touched))
			ts = pool_ts;
		else
			ts = touched;

		if (pool->cpu >= 0) {
			unsigned long cpu_touched =
				READ_ONCE(per_cpu(wq_watchdog_touched_cpu,
						  pool->cpu));
			if (time_after(cpu_touched, ts))
				ts = cpu_touched;
		}

		/* did we stall? */
		if (time_after(jiffies, ts + thresh)) {
			lockup_detected = true;
			pr_emerg("BUG: workqueue lockup - pool");
			pr_cont_pool_info(pool);
			pr_cont(" stuck for %us!\n",
				jiffies_to_msecs(jiffies - pool_ts) / 1000);
		}
	}

	rcu_read_unlock();

	if (lockup_detected)
		show_workqueue_state();

	wq_watchdog_reset_touched();
	mod_timer(&wq_watchdog_timer, jiffies + thresh);
}


u64 hrtimer_get_next_event(void)
{
	struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
	u64 expires = KTIME_MAX;
	unsigned long flags;

	raw_spin_lock_irqsave(&cpu_base->lock, flags);

	if (!__hrtimer_hres_active(cpu_base))
		expires = __hrtimer_get_next_event(cpu_base);

	raw_spin_unlock_irqrestore(&cpu_base->lock, flags);

	return expires;
}


int workqueue_online_cpu(unsigned int cpu)
{
	struct worker_pool *pool;
	struct workqueue_struct *wq;
	int pi;

	mutex_lock(&wq_pool_mutex);

	for_each_pool(pool, pi) {
		mutex_lock(&pool->attach_mutex);

		if (pool->cpu == cpu)
			rebind_workers(pool);
		else if (pool->cpu < 0)
			restore_unbound_workers_cpumask(pool, cpu);

		mutex_unlock(&pool->attach_mutex);
	}

	/* update NUMA affinity of unbound workqueues */
	list_for_each_entry(wq, &workqueues, list)
		wq_update_unbound_numa(wq, cpu, true);

	mutex_unlock(&wq_pool_mutex);
	return 0;
}


void run_local_timers(void)
{
	struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);

	hrtimer_run_queues();
	/* Raise the softirq only if required. */
	if (time_before(jiffies, base->clk)) {
		if (!IS_ENABLED(CONFIG_NO_HZ_COMMON) || !base->nohz_active)
			return;
		/* CPU is awake, so check the deferrable base. */
		base++;
		if (time_before(jiffies, base->clk))
			return;
	}
	raise_softirq(TIMER_SOFTIRQ);
}


void kthread_destroy_worker(struct kthread_worker *worker)
{
	struct task_struct *task;

	task = worker->task;
	if (WARN_ON(!task))
		return;

	kthread_flush_worker(worker);
	kthread_stop(task);
	WARN_ON(!list_empty(&worker->work_list));
	kfree(worker);
}


static void work_for_cpu_fn(struct work_struct *work)
{
	struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);

	wfc->ret = wfc->fn(wfc->arg);
}


static void __remove_hrtimer(struct hrtimer *timer,
			     struct hrtimer_clock_base *base,
			     u8 newstate, int reprogram)
{
	struct hrtimer_cpu_base *cpu_base = base->cpu_base;
	u8 state = timer->state;

	timer->state = newstate;
	if (!(state & HRTIMER_STATE_ENQUEUED))
		return;

	if (!timerqueue_del(&base->active, &timer->node))
		cpu_base->active_bases &= ~(1 << base->index);

#ifdef CONFIG_HIGH_RES_TIMERS
	/*
	 * Note: If reprogram is false we do not update
	 * cpu_base->next_timer. This happens when we remove the first
	 * timer on a remote cpu. No harm as we never dereference
	 * cpu_base->next_timer. So the worst thing what can happen is
	 * an superflous call to hrtimer_force_reprogram() on the
	 * remote cpu later on if the same timer gets enqueued again.
	 */
	if (reprogram && timer == cpu_base->next_timer)
		hrtimer_force_reprogram(cpu_base, 1);
#endif
}


static u32 wqattrs_hash(const struct workqueue_attrs *attrs)
{
	u32 hash = 0;

	hash = jhash_1word(attrs->nice, hash);
	hash = jhash(cpumask_bits(attrs->cpumask),
		     BITS_TO_LONGS(nr_cpumask_bits) * sizeof(long), hash);
	return hash;
}


void wq_watchdog_touch(int cpu)
{
	if (cpu >= 0)
		per_cpu(wq_watchdog_touched_cpu, cpu) = jiffies;
	else
		wq_watchdog_touched = jiffies;
}


int dccp_send_reset(struct sock *sk, enum dccp_reset_codes code)
{
	struct sk_buff *skb;
	/*
	 * FIXME: what if rebuild_header fails?
	 * Should we be doing a rebuild_header here?
	 */
	int err = inet_csk(sk)->icsk_af_ops->rebuild_header(sk);

	if (err != 0)
		return err;

	skb = sock_wmalloc(sk, sk->sk_prot->max_header, 1, GFP_ATOMIC);
	if (skb == NULL)
		return -ENOBUFS;

	/* Reserve space for headers and prepare control bits. */
	skb_reserve(skb, sk->sk_prot->max_header);
	DCCP_SKB_CB(skb)->dccpd_type	   = DCCP_PKT_RESET;
	DCCP_SKB_CB(skb)->dccpd_reset_code = code;

	return dccp_transmit_skb(sk, skb);
}


void dccp_write_xmit(struct sock *sk, int block)
{
	struct dccp_sock *dp = dccp_sk(sk);
	struct sk_buff *skb;

	while ((skb = skb_peek(&sk->sk_write_queue))) {
		int err = ccid_hc_tx_send_packet(dp->dccps_hc_tx_ccid, sk, skb);

		if (err > 0) {
			if (!block) {
				sk_reset_timer(sk, &dp->dccps_xmit_timer,
						msecs_to_jiffies(err)+jiffies);
				break;
			} else
				err = dccp_wait_for_ccid(sk, skb, err);
			if (err && err != -EINTR)
				DCCP_BUG("err=%d after dccp_wait_for_ccid", err);
		}

		skb_dequeue(&sk->sk_write_queue);
		if (err == 0) {
			struct dccp_skb_cb *dcb = DCCP_SKB_CB(skb);
			const int len = skb->len;

			if (sk->sk_state == DCCP_PARTOPEN) {
				/* See 8.1.5.  Handshake Completion */
				inet_csk_schedule_ack(sk);
				inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
						  inet_csk(sk)->icsk_rto,
						  DCCP_RTO_MAX);
				dcb->dccpd_type = DCCP_PKT_DATAACK;
			} else if (dccp_ack_pending(sk))
				dcb->dccpd_type = DCCP_PKT_DATAACK;
			else
				dcb->dccpd_type = DCCP_PKT_DATA;

			err = dccp_transmit_skb(sk, skb);
			ccid_hc_tx_packet_sent(dp->dccps_hc_tx_ccid, sk, 0, len);
			if (err)
				DCCP_BUG("err=%d after ccid_hc_tx_packet_sent",
					 err);
		} else {
			dccp_pr_debug("packet discarded due to err=%d\n", err);
			kfree_skb(skb);
		}
	}
}


VncJob *vnc_job_new(VncState *vs)
{
    VncJob *job = g_malloc0(sizeof(VncJob));

    job->vs = vs;
    vnc_lock_queue(queue);
    QLIST_INIT(&job->rectangles);
    vnc_unlock_queue(queue);
    return job;
}


static int vnc_server_info_put(QDict *qdict)
{
    if (vnc_server_addr_put(qdict, vnc_display->lsock) < 0) {
        return -1;
    }

    qdict_put(qdict, "auth", qstring_from_str(vnc_auth_name(vnc_display)));
    return 0;
}


static void vnc_listen_read(void *opaque)
{
    VncDisplay *vs = opaque;
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);

    /* Catch-up */
    vga_hw_update();

    int csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen);
    if (csock != -1) {
        vnc_connect(vs, csock, 0);
    }
}


void vnc_flush(VncState *vs)
{
    vnc_lock_output(vs);
    if (vs->csock != -1 && vs->output.offset) {
        vnc_client_write_locked(vs);
    }
    vnc_unlock_output(vs);
}


tight_detect_smooth_image(VncState *vs, int w, int h)
{
    unsigned int errors;
    int compression = vs->tight.compression;
    int quality = vs->tight.quality;

    if (!vs->vd->lossy) {
        return 0;
    }

    if (ds_get_bytes_per_pixel(vs->ds) == 1 ||
        vs->client_pf.bytes_per_pixel == 1 ||
        w < VNC_TIGHT_DETECT_MIN_WIDTH || h < VNC_TIGHT_DETECT_MIN_HEIGHT) {
        return 0;
    }

    if (vs->tight.quality != (uint8_t)-1) {
        if (w * h < VNC_TIGHT_JPEG_MIN_RECT_SIZE) {
            return 0;
        }
    } else {
        if (w * h < tight_conf[compression].gradient_min_rect_size) {
            return 0;
        }
    }

    if (vs->client_pf.bytes_per_pixel == 4) {
        if (vs->tight.pixel24) {
            errors = tight_detect_smooth_image24(vs, w, h);
            if (vs->tight.quality != (uint8_t)-1) {
                return (errors < tight_conf[quality].jpeg_threshold24);
            }
            return (errors < tight_conf[compression].gradient_threshold24);
        } else {
            errors = tight_detect_smooth_image32(vs, w, h);
        }
    } else {
        errors = tight_detect_smooth_image16(vs, w, h);
    }
    if (quality != -1) {
        return (errors < tight_conf[quality].jpeg_threshold);
    }
    return (errors < tight_conf[compression].gradient_threshold);
}


static void audio_add(VncState *vs)
{
    struct audio_capture_ops ops;

    if (vs->audio_cap) {
        monitor_printf(default_mon, "audio already running\n");
        return;
    }

    ops.notify = audio_capture_notify;
    ops.destroy = audio_capture_destroy;
    ops.capture = audio_capture;

    vs->audio_cap = AUD_add_capture(&vs->as, &ops, vs);
    if (!vs->audio_cap) {
        monitor_printf(default_mon, "Failed to add audio capture\n");
    }
}


void vnc_display_close(DisplayState *ds)
{
    VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;

    if (!vs)
        return;
    if (vs->display) {
        g_free(vs->display);
        vs->display = NULL;
    }
    if (vs->lsock != -1) {
        qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL);
        close(vs->lsock);
        vs->lsock = -1;
    }
    vs->auth = VNC_AUTH_INVALID;
#ifdef CONFIG_VNC_TLS
    vs->subauth = VNC_AUTH_INVALID;
    vs->tls.x509verify = 0;
#endif
}


static bool check_solid_tile(VncState *vs, int x, int y, int w, int h,
                             uint32_t* color, bool samecolor)
{
    switch (VNC_SERVER_FB_BYTES) {
    case 4:
        return check_solid_tile32(vs, x, y, w, h, color, samecolor);
    case 2:
        return check_solid_tile16(vs, x, y, w, h, color, samecolor);
    default:
        return check_solid_tile8(vs, x, y, w, h, color, samecolor);
    }
}


static VncClientInfo *qmp_query_vnc_client(const VncState *client)
{
    struct sockaddr_storage sa;
    socklen_t salen = sizeof(sa);
    char host[NI_MAXHOST];
    char serv[NI_MAXSERV];
    VncClientInfo *info;

    if (getpeername(client->csock, (struct sockaddr *)&sa, &salen) < 0) {
        return NULL;
    }

    if (getnameinfo((struct sockaddr *)&sa, salen,
                    host, sizeof(host),
                    serv, sizeof(serv),
                    NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
        return NULL;
    }

    info = g_malloc0(sizeof(*info));
    info->host = g_strdup(host);
    info->service = g_strdup(serv);
    info->family = g_strdup(inet_strfamily(sa.ss_family));

#ifdef CONFIG_VNC_TLS
    if (client->tls.session && client->tls.dname) {
        info->has_x509_dname = true;
        info->x509_dname = g_strdup(client->tls.dname);
    }
#endif
#ifdef CONFIG_VNC_SASL
    if (client->sasl.conn && client->sasl.username) {
        info->has_sasl_username = true;
        info->sasl_username = g_strdup(client->sasl.username);
    }
#endif

    return info;
}


static void vnc_dpy_resize(DisplayState *ds)
{
    VncDisplay *vd = ds->opaque;
    VncState *vs;

    vnc_abort_display_jobs(vd);

    /* server surface */
    qemu_pixman_image_unref(vd->server);
    vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT,
                                          ds_get_width(ds),
                                          ds_get_height(ds),
                                          NULL, 0);

    /* guest surface */
#if 0 /* FIXME */
    if (ds_get_bytes_per_pixel(ds) != vd->guest.ds->pf.bytes_per_pixel)
        console_color_init(ds);
#endif
    qemu_pixman_image_unref(vd->guest.fb);
    vd->guest.fb = pixman_image_ref(ds->surface->image);
    vd->guest.format = ds->surface->format;
    memset(vd->guest.dirty, 0xFF, sizeof(vd->guest.dirty));

    QTAILQ_FOREACH(vs, &vd->clients, next) {
        vnc_colordepth(vs);
        vnc_desktop_resize(vs);
        if (vs->vd->cursor) {
            vnc_cursor_define(vs);
        }
        memset(vs->dirty, 0xFF, sizeof(vs->dirty));
    }
}


static void vnc_client_cache_addr(VncState *client)
{
    QDict *qdict;

    qdict = qdict_new();
    if (vnc_qdict_remote_addr(qdict, client->csock) < 0) {
        QDECREF(qdict);
        /* XXX: how to report the error? */
        return;
    }

    client->info = QOBJECT(qdict);
}


static void kbd_leds(void *opaque, int ledstate)
{
    VncState *vs = opaque;
    int caps, num;

    caps = ledstate & QEMU_CAPS_LOCK_LED ? 1 : 0;
    num  = ledstate & QEMU_NUM_LOCK_LED  ? 1 : 0;

    if (vs->modifiers_state[0x3a] != caps) {
        vs->modifiers_state[0x3a] = caps;
    }
    if (vs->modifiers_state[0x45] != num) {
        vs->modifiers_state[0x45] = num;
    }
}