584 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			584 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* alias.c -- Not a full alias, but just the kind that we use in the
 | |
|    shell.  Csh style alias is somewhere else (`over there, in a box'). */
 | |
| 
 | |
| /* Copyright (C) 1987-2015 Free Software Foundation, Inc.
 | |
| 
 | |
|    This file is part of GNU Bash, the Bourne Again SHell.
 | |
| 
 | |
|    Bash is free software: you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation, either version 3 of the License, or
 | |
|    (at your option) any later version.
 | |
| 
 | |
|    Bash is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with Bash.  If not, see <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #if defined (ALIAS)
 | |
| 
 | |
| #if defined (HAVE_UNISTD_H)
 | |
| #  ifdef _MINIX
 | |
| #    include <sys/types.h>
 | |
| #  endif
 | |
| #  include <unistd.h>
 | |
| #endif
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include "chartypes.h"
 | |
| #include "bashansi.h"
 | |
| #include "command.h"
 | |
| #include "general.h"
 | |
| #include "externs.h"
 | |
| #include "alias.h"
 | |
| 
 | |
| #if defined (PROGRAMMABLE_COMPLETION)
 | |
| #  include "pcomplete.h"
 | |
| #endif
 | |
| 
 | |
| #if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
 | |
| #  include <mbstr.h>		/* mbschr */
 | |
| #endif
 | |
| 
 | |
| #define ALIAS_HASH_BUCKETS	64	/* must be power of two */
 | |
| 
 | |
| typedef int sh_alias_map_func_t __P((alias_t *));
 | |
| 
 | |
| static void free_alias_data __P((PTR_T));
 | |
| static alias_t **map_over_aliases __P((sh_alias_map_func_t *));
 | |
| static void sort_aliases __P((alias_t **));
 | |
| static int qsort_alias_compare __P((alias_t **, alias_t **));
 | |
| 
 | |
| #if defined (READLINE)
 | |
| static int skipquotes __P((char *, int));
 | |
| static int skipws __P((char *, int));
 | |
| static int rd_token __P((char *, int));
 | |
| #endif
 | |
| 
 | |
| /* Non-zero means expand all words on the line.  Otherwise, expand
 | |
|    after first expansion if the expansion ends in a space. */
 | |
| int alias_expand_all = 0;
 | |
| 
 | |
| /* The list of aliases that we have. */
 | |
| HASH_TABLE *aliases = (HASH_TABLE *)NULL;
 | |
| 
 | |
| void
 | |
| initialize_aliases ()
 | |
| {
 | |
|   if (aliases == 0)
 | |
|     aliases = hash_create (ALIAS_HASH_BUCKETS);
 | |
| }
 | |
| 
 | |
| /* Scan the list of aliases looking for one with NAME.  Return NULL
 | |
|    if the alias doesn't exist, else a pointer to the alias_t. */
 | |
| alias_t *
 | |
| find_alias (name)
 | |
|      char *name;
 | |
| {
 | |
|   BUCKET_CONTENTS *al;
 | |
| 
 | |
|   if (aliases == 0)
 | |
|     return ((alias_t *)NULL);
 | |
| 
 | |
|   al = hash_search (name, aliases, 0);
 | |
|   return (al ? (alias_t *)al->data : (alias_t *)NULL);
 | |
| }
 | |
| 
 | |
| /* Return the value of the alias for NAME, or NULL if there is none. */
 | |
| char *
 | |
| get_alias_value (name)
 | |
|      char *name;
 | |
| {
 | |
|   alias_t *alias;
 | |
| 
 | |
|   if (aliases == 0)
 | |
|     return ((char *)NULL);
 | |
| 
 | |
|   alias = find_alias (name);
 | |
|   return (alias ? alias->value : (char *)NULL);
 | |
| }
 | |
| 
 | |
| /* Make a new alias from NAME and VALUE.  If NAME can be found,
 | |
|    then replace its value. */
 | |
| void
 | |
| add_alias (name, value)
 | |
|      char *name, *value;
 | |
| {
 | |
|   BUCKET_CONTENTS *elt;
 | |
|   alias_t *temp;
 | |
|   int n;
 | |
| 
 | |
|   if (aliases == 0)
 | |
|     {
 | |
|       initialize_aliases ();
 | |
|       temp = (alias_t *)NULL;
 | |
|     }
 | |
|   else
 | |
|     temp = find_alias (name);
 | |
| 
 | |
|   if (temp)
 | |
|     {
 | |
|       free (temp->value);
 | |
|       temp->value = savestring (value);
 | |
|       temp->flags &= ~AL_EXPANDNEXT;
 | |
|       n = value[strlen (value) - 1];
 | |
|       if (n == ' ' || n == '\t')
 | |
| 	temp->flags |= AL_EXPANDNEXT;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       temp = (alias_t *)xmalloc (sizeof (alias_t));
 | |
|       temp->name = savestring (name);
 | |
|       temp->value = savestring (value);
 | |
|       temp->flags = 0;
 | |
| 
 | |
|       n = value[strlen (value) - 1];
 | |
|       if (n == ' ' || n == '\t')
 | |
| 	temp->flags |= AL_EXPANDNEXT;
 | |
| 
 | |
|       elt = hash_insert (savestring (name), aliases, HASH_NOSRCH);
 | |
|       elt->data = temp;
 | |
| #if defined (PROGRAMMABLE_COMPLETION)
 | |
|       set_itemlist_dirty (&it_aliases);
 | |
| #endif
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Delete a single alias structure. */
 | |
| static void
 | |
| free_alias_data (data)
 | |
|      PTR_T data;
 | |
| {
 | |
|   register alias_t *a;
 | |
| 
 | |
|   a = (alias_t *)data;
 | |
|   free (a->value);
 | |
|   free (a->name);
 | |
|   free (data);
 | |
| }
 | |
| 
 | |
| /* Remove the alias with name NAME from the alias table.  Returns
 | |
|    the number of aliases left in the table, or -1 if the alias didn't
 | |
|    exist. */
 | |
| int
 | |
| remove_alias (name)
 | |
|      char *name;
 | |
| {
 | |
|   BUCKET_CONTENTS *elt;
 | |
| 
 | |
|   if (aliases == 0)
 | |
|     return (-1);
 | |
| 
 | |
|   elt = hash_remove (name, aliases, 0);
 | |
|   if (elt)
 | |
|     {
 | |
|       free_alias_data (elt->data);
 | |
|       free (elt->key);		/* alias name */
 | |
|       free (elt);		/* XXX */
 | |
| #if defined (PROGRAMMABLE_COMPLETION)
 | |
|       set_itemlist_dirty (&it_aliases);
 | |
| #endif
 | |
|       return (aliases->nentries);
 | |
|     }
 | |
|   return (-1);
 | |
| }
 | |
| 
 | |
| /* Delete all aliases. */
 | |
| void
 | |
| delete_all_aliases ()
 | |
| {
 | |
|   if (aliases == 0)
 | |
|     return;
 | |
| 
 | |
|   hash_flush (aliases, free_alias_data);
 | |
|   hash_dispose (aliases);
 | |
|   aliases = (HASH_TABLE *)NULL;
 | |
| #if defined (PROGRAMMABLE_COMPLETION)
 | |
|   set_itemlist_dirty (&it_aliases);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /* Return an array of aliases that satisfy the conditions tested by FUNCTION.
 | |
|    If FUNCTION is NULL, return all aliases. */
 | |
| static alias_t **
 | |
| map_over_aliases (function)
 | |
|      sh_alias_map_func_t *function;
 | |
| {
 | |
|   register int i;
 | |
|   register BUCKET_CONTENTS *tlist;
 | |
|   alias_t *alias, **list;
 | |
|   int list_index;
 | |
| 
 | |
|   i = HASH_ENTRIES (aliases);
 | |
|   if (i == 0)
 | |
|     return ((alias_t **)NULL);
 | |
| 
 | |
|   list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *));
 | |
|   for (i = list_index = 0; i < aliases->nbuckets; i++)
 | |
|     {
 | |
|       for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next)
 | |
| 	{
 | |
| 	  alias = (alias_t *)tlist->data;
 | |
| 
 | |
| 	  if (!function || (*function) (alias))
 | |
| 	    {
 | |
| 	      list[list_index++] = alias;
 | |
| 	      list[list_index] = (alias_t *)NULL;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|   return (list);
 | |
| }
 | |
| 
 | |
| static void
 | |
| sort_aliases (array)
 | |
|      alias_t **array;
 | |
| {
 | |
|   qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare);
 | |
| }
 | |
| 
 | |
| static int
 | |
| qsort_alias_compare (as1, as2)
 | |
|      alias_t **as1, **as2;
 | |
| {
 | |
|   int result;
 | |
| 
 | |
|   if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0)
 | |
|     result = strcmp ((*as1)->name, (*as2)->name);
 | |
| 
 | |
|   return (result);
 | |
| }
 | |
| 
 | |
| /* Return a sorted list of all defined aliases */
 | |
| alias_t **
 | |
| all_aliases ()
 | |
| {
 | |
|   alias_t **list;
 | |
| 
 | |
|   if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
 | |
|     return ((alias_t **)NULL);
 | |
| 
 | |
|   list = map_over_aliases ((sh_alias_map_func_t *)NULL);
 | |
|   if (list)
 | |
|     sort_aliases (list);
 | |
|   return (list);
 | |
| }
 | |
| 
 | |
| char *
 | |
| alias_expand_word (s)
 | |
|      char *s;
 | |
| {
 | |
|   alias_t *r;
 | |
| 
 | |
|   r = find_alias (s);
 | |
|   return (r ? savestring (r->value) : (char *)NULL);
 | |
| }
 | |
| 
 | |
| /* Readline support functions -- expand all aliases in a line. */
 | |
| 
 | |
| #if defined (READLINE)
 | |
| 
 | |
| /* Return non-zero if CHARACTER is a member of the class of characters
 | |
|    that are self-delimiting in the shell (this really means that these
 | |
|    characters delimit tokens). */
 | |
| #define self_delimiting(character) (member ((character), " \t\n\r;|&()"))
 | |
| 
 | |
| /* Return non-zero if CHARACTER is a member of the class of characters
 | |
|    that delimit commands in the shell. */
 | |
| #define command_separator(character) (member ((character), "\r\n;|&("))
 | |
| 
 | |
| /* If this is 1, we are checking the next token read for alias expansion
 | |
|    because it is the first word in a command. */
 | |
| static int command_word;
 | |
| 
 | |
| /* This is for skipping quoted strings in alias expansions. */
 | |
| #define quote_char(c)  (((c) == '\'') || ((c) == '"'))
 | |
| 
 | |
| /* Consume a quoted string from STRING, starting at string[START] (so
 | |
|    string[START] is the opening quote character), and return the index
 | |
|    of the closing quote character matching the opening quote character.
 | |
|    This handles single matching pairs of unquoted quotes; it could afford
 | |
|    to be a little smarter... This skips words between balanced pairs of
 | |
|    quotes, words where the first character is quoted with a `\', and other
 | |
|    backslash-escaped characters. */
 | |
| 
 | |
| static int
 | |
| skipquotes (string, start)
 | |
|      char *string;
 | |
|      int start;
 | |
| {
 | |
|   register int i;
 | |
|   int delimiter = string[start];
 | |
| 
 | |
|   /* i starts at START + 1 because string[START] is the opening quote
 | |
|      character. */
 | |
|   for (i = start + 1 ; string[i] ; i++)
 | |
|     {
 | |
|       if (string[i] == '\\')
 | |
| 	{
 | |
| 	  i++;		/* skip backslash-quoted quote characters, too */
 | |
| 	  if (string[i] == 0)
 | |
| 	    break;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       if (string[i] == delimiter)
 | |
| 	return i;
 | |
|     }
 | |
|   return (i);
 | |
| }
 | |
| 
 | |
| /* Skip the white space and any quoted characters in STRING, starting at
 | |
|    START.  Return the new index into STRING, after zero or more characters
 | |
|    have been skipped. */
 | |
| static int
 | |
| skipws (string, start)
 | |
|      char *string;
 | |
|      int start;
 | |
| {
 | |
|   register int i;
 | |
|   int pass_next, backslash_quoted_word;
 | |
|   unsigned char peekc;
 | |
| 
 | |
|   /* skip quoted strings, in ' or ", and words in which a character is quoted
 | |
|      with a `\'. */
 | |
|   i = backslash_quoted_word = pass_next = 0;
 | |
| 
 | |
|   /* Skip leading whitespace (or separator characters), and quoted words.
 | |
|      But save it in the output.  */
 | |
| 
 | |
|   for (i = start; string[i]; i++)
 | |
|     {
 | |
|       if (pass_next)
 | |
| 	{
 | |
| 	  pass_next = 0;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       if (whitespace (string[i]))
 | |
| 	{
 | |
| 	  backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       if (string[i] == '\\')
 | |
| 	{
 | |
| 	  peekc = string[i+1];
 | |
| 	  if (peekc == 0)
 | |
| 	    break;
 | |
| 	  if (ISLETTER (peekc))
 | |
| 	    backslash_quoted_word++;	/* this is a backslash-quoted word */
 | |
| 	  else
 | |
| 	    pass_next++;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       /* This only handles single pairs of non-escaped quotes.  This
 | |
| 	 overloads backslash_quoted_word to also mean that a word like
 | |
| 	 ""f is being scanned, so that the quotes will inhibit any expansion
 | |
| 	 of the word. */
 | |
|       if (quote_char(string[i]))
 | |
| 	{
 | |
| 	  i = skipquotes (string, i);
 | |
| 	  /* This could be a line that contains a single quote character,
 | |
| 	     in which case skipquotes () terminates with string[i] == '\0'
 | |
| 	     (the end of the string).  Check for that here. */
 | |
| 	  if (string[i] == '\0')
 | |
| 	    break;
 | |
| 
 | |
| 	  peekc = string[i + 1];
 | |
| 	  if (ISLETTER (peekc))
 | |
| 	    backslash_quoted_word++;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       /* If we're in the middle of some kind of quoted word, let it
 | |
| 	 pass through. */
 | |
|       if (backslash_quoted_word)
 | |
| 	continue;
 | |
| 
 | |
|       /* If this character is a shell command separator, then set a hint for
 | |
| 	 alias_expand that the next token is the first word in a command. */
 | |
| 
 | |
|       if (command_separator (string[i]))
 | |
| 	{
 | |
| 	  command_word++;
 | |
| 	  continue;
 | |
| 	}
 | |
|       break;
 | |
|     }
 | |
|   return (i);
 | |
| }
 | |
| 
 | |
| /* Characters that may appear in a token.  Basically, anything except white
 | |
|    space and a token separator. */
 | |
| #define token_char(c)	(!((whitespace (string[i]) || self_delimiting (string[i]))))
 | |
| 
 | |
| /* Read from START in STRING until the next separator character, and return
 | |
|    the index of that separator.  Skip backslash-quoted characters.  Call
 | |
|    skipquotes () for quoted strings in the middle or at the end of tokens,
 | |
|    so all characters show up (e.g. foo'' and foo""bar) */
 | |
| static int
 | |
| rd_token (string, start)
 | |
|      char *string;
 | |
|      int start;
 | |
| {
 | |
|   register int i;
 | |
| 
 | |
|   /* From here to next separator character is a token. */
 | |
|   for (i = start; string[i] && token_char (string[i]); i++)
 | |
|     {
 | |
|       if (string[i] == '\\')
 | |
| 	{
 | |
| 	  i++;	/* skip backslash-escaped character */
 | |
| 	  if (string[i] == 0)
 | |
| 	    break;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       /* If this character is a quote character, we want to call skipquotes
 | |
| 	 to get the whole quoted portion as part of this word.  That word
 | |
| 	 will not generally match an alias, even if te unquoted word would
 | |
| 	 have.  The presence of the quotes in the token serves then to
 | |
| 	 inhibit expansion. */
 | |
|       if (quote_char (string[i]))
 | |
| 	{
 | |
| 	  i = skipquotes (string, i);
 | |
| 	  /* This could be a line that contains a single quote character,
 | |
| 	     in which case skipquotes () terminates with string[i] == '\0'
 | |
| 	     (the end of the string).  Check for that here. */
 | |
| 	  if (string[i] == '\0')
 | |
| 	    break;
 | |
| 
 | |
| 	  /* Now string[i] is the matching quote character, and the
 | |
| 	     quoted portion of the token has been scanned. */
 | |
| 	  continue;
 | |
| 	}
 | |
|     }
 | |
|   return (i);
 | |
| }
 | |
| 
 | |
| /* Return a new line, with any aliases substituted. */
 | |
| char *
 | |
| alias_expand (string)
 | |
|      char *string;
 | |
| {
 | |
|   register int i, j, start;
 | |
|   char *line, *token;
 | |
|   int line_len, tl, real_start, expand_next, expand_this_token;
 | |
|   alias_t *alias;
 | |
| 
 | |
|   line_len = strlen (string) + 1;
 | |
|   line = (char *)xmalloc (line_len);
 | |
|   token = (char *)xmalloc (line_len);
 | |
| 
 | |
|   line[0] = i = 0;
 | |
|   expand_next = 0;
 | |
|   command_word = 1; /* initialized to expand the first word on the line */
 | |
| 
 | |
|   /* Each time through the loop we find the next word in line.  If it
 | |
|      has an alias, substitute the alias value.  If the value ends in ` ',
 | |
|      then try again with the next word.  Else, if there is no value, or if
 | |
|      the value does not end in space, we are done. */
 | |
| 
 | |
|   for (;;)
 | |
|     {
 | |
| 
 | |
|       token[0] = 0;
 | |
|       start = i;
 | |
| 
 | |
|       /* Skip white space and quoted characters */
 | |
|       i = skipws (string, start);
 | |
| 
 | |
|       if (start == i && string[i] == '\0')
 | |
| 	{
 | |
| 	  free (token);
 | |
| 	  return (line);
 | |
| 	}
 | |
| 
 | |
|       /* copy the just-skipped characters into the output string,
 | |
| 	 expanding it if there is not enough room. */
 | |
|       j = strlen (line);
 | |
|       tl = i - start;	/* number of characters just skipped */
 | |
|       RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50));
 | |
|       strncpy (line + j, string + start, tl);
 | |
|       line[j + tl] = '\0';
 | |
| 
 | |
|       real_start = i;
 | |
| 
 | |
|       command_word = command_word || (command_separator (string[i]));
 | |
|       expand_this_token = (command_word || expand_next);
 | |
|       expand_next = 0;
 | |
| 
 | |
|       /* Read the next token, and copy it into TOKEN. */
 | |
|       start = i;
 | |
|       i = rd_token (string, start);
 | |
| 
 | |
|       tl = i - start;	/* token length */
 | |
| 
 | |
|       /* If tl == 0, but we're not at the end of the string, then we have a
 | |
| 	 single-character token, probably a delimiter */
 | |
|       if (tl == 0 && string[i] != '\0')
 | |
| 	{
 | |
| 	  tl = 1;
 | |
| 	  i++;		/* move past it */
 | |
| 	}
 | |
| 
 | |
|       strncpy (token, string + start, tl);
 | |
|       token [tl] = '\0';
 | |
| 
 | |
|       /* If there is a backslash-escaped character quoted in TOKEN,
 | |
| 	 then we don't do alias expansion.  This should check for all
 | |
| 	 other quoting characters, too. */
 | |
|       if (mbschr (token, '\\'))
 | |
| 	expand_this_token = 0;
 | |
| 
 | |
|       /* If we should be expanding here, if we are expanding all words, or if
 | |
| 	 we are in a location in the string where an expansion is supposed to
 | |
| 	 take place, see if this word has a substitution.  If it does, then do
 | |
| 	 the expansion.  Note that we defer the alias value lookup until we
 | |
| 	 are sure we are expanding this token. */
 | |
| 
 | |
|       if ((token[0]) &&
 | |
| 	  (expand_this_token || alias_expand_all) &&
 | |
| 	  (alias = find_alias (token)))
 | |
| 	{
 | |
| 	  char *v;
 | |
| 	  int vlen, llen;
 | |
| 
 | |
| 	  v = alias->value;
 | |
| 	  vlen = strlen (v);
 | |
| 	  llen = strlen (line);
 | |
| 
 | |
| 	  /* +3 because we possibly add one more character below. */
 | |
| 	  RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50));
 | |
| 
 | |
| 	  strcpy (line + llen, v);
 | |
| 
 | |
| 	  if ((expand_this_token && vlen && whitespace (v[vlen - 1])) ||
 | |
| 	      alias_expand_all)
 | |
| 	    expand_next = 1;
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  int llen, tlen;
 | |
| 
 | |
| 	  llen = strlen (line);
 | |
| 	  tlen = i - real_start; /* tlen == strlen(token) */
 | |
| 
 | |
| 	  RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50));
 | |
| 
 | |
| 	  strncpy (line + llen, string + real_start, tlen);
 | |
| 	  line[llen + tlen] = '\0';
 | |
| 	}
 | |
|       command_word = 0;
 | |
|     }
 | |
| }
 | |
| #endif /* READLINE */
 | |
| #endif /* ALIAS */
 | 
