1/* alias.c -- Not a full alias, but just the kind that we use in the
2   shell.  Csh style alias is somewhere else (`over there, in a box'). */
3
4/* Copyright (C) 1987-2002 Free Software Foundation, Inc.
5
6   This file is part of GNU Bash, the Bourne Again SHell.
7
8   Bash is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2, or (at your option)
11   any later version.
12
13   Bash is distributed in the hope that it will be useful, but WITHOUT
14   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
16   License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with Bash; see the file COPYING.  If not, write to the Free
20   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
21
22#include "config.h"
23
24#if defined (ALIAS)
25
26#if defined (HAVE_UNISTD_H)
27#  ifdef _MINIX
28#    include <sys/types.h>
29#  endif
30#  include <unistd.h>
31#endif
32
33#include <stdio.h>
34#include "chartypes.h"
35#include "bashansi.h"
36#include "command.h"
37#include "general.h"
38#include "externs.h"
39#include "alias.h"
40
41#if defined (PROGRAMMABLE_COMPLETION)
42#  include "pcomplete.h"
43#endif
44
45#define ALIAS_HASH_BUCKETS	16	/* must be power of two */
46
47typedef int sh_alias_map_func_t __P((alias_t *));
48
49static void free_alias_data __P((PTR_T));
50static alias_t **map_over_aliases __P((sh_alias_map_func_t *));
51static void sort_aliases __P((alias_t **));
52static int qsort_alias_compare __P((alias_t **, alias_t **));
53
54#if defined (READLINE)
55static int skipquotes __P((char *, int));
56static int skipws __P((char *, int));
57static int rd_token __P((char *, int));
58#endif
59
60/* Non-zero means expand all words on the line.  Otherwise, expand
61   after first expansion if the expansion ends in a space. */
62int alias_expand_all = 0;
63
64/* The list of aliases that we have. */
65HASH_TABLE *aliases = (HASH_TABLE *)NULL;
66
67void
68initialize_aliases ()
69{
70  if (!aliases)
71    aliases = hash_create (ALIAS_HASH_BUCKETS);
72}
73
74/* Scan the list of aliases looking for one with NAME.  Return NULL
75   if the alias doesn't exist, else a pointer to the alias_t. */
76alias_t *
77find_alias (name)
78     char *name;
79{
80  BUCKET_CONTENTS *al;
81
82  if (aliases == 0)
83    return ((alias_t *)NULL);
84
85  al = hash_search (name, aliases, 0);
86  return (al ? (alias_t *)al->data : (alias_t *)NULL);
87}
88
89/* Return the value of the alias for NAME, or NULL if there is none. */
90char *
91get_alias_value (name)
92     char *name;
93{
94  alias_t *alias;
95
96  if (aliases == 0)
97    return ((char *)NULL);
98
99  alias = find_alias (name);
100  return (alias ? alias->value : (char *)NULL);
101}
102
103/* Make a new alias from NAME and VALUE.  If NAME can be found,
104   then replace its value. */
105void
106add_alias (name, value)
107     char *name, *value;
108{
109  BUCKET_CONTENTS *elt;
110  alias_t *temp;
111  int n;
112
113  if (!aliases)
114    {
115      initialize_aliases ();
116      temp = (alias_t *)NULL;
117    }
118  else
119    temp = find_alias (name);
120
121  if (temp)
122    {
123      free (temp->value);
124      temp->value = savestring (value);
125      temp->flags &= ~AL_EXPANDNEXT;
126      n = value[strlen (value) - 1];
127      if (n == ' ' || n == '\t')
128	temp->flags |= AL_EXPANDNEXT;
129    }
130  else
131    {
132      temp = (alias_t *)xmalloc (sizeof (alias_t));
133      temp->name = savestring (name);
134      temp->value = savestring (value);
135      temp->flags = 0;
136
137      n = value[strlen (value) - 1];
138      if (n == ' ' || n == '\t')
139	temp->flags |= AL_EXPANDNEXT;
140
141      elt = hash_insert (savestring (name), aliases, HASH_NOSRCH);
142      elt->data = temp;
143#if defined (PROGRAMMABLE_COMPLETION)
144      set_itemlist_dirty (&it_aliases);
145#endif
146    }
147}
148
149/* Delete a single alias structure. */
150static void
151free_alias_data (data)
152     PTR_T data;
153{
154  register alias_t *a;
155
156  a = (alias_t *)data;
157  free (a->value);
158  free (a->name);
159  free (data);
160}
161
162/* Remove the alias with name NAME from the alias table.  Returns
163   the number of aliases left in the table, or -1 if the alias didn't
164   exist. */
165int
166remove_alias (name)
167     char *name;
168{
169  BUCKET_CONTENTS *elt;
170
171  if (aliases == 0)
172    return (-1);
173
174  elt = hash_remove (name, aliases, 0);
175  if (elt)
176    {
177      free_alias_data (elt->data);
178      free (elt->key);		/* alias name */
179      free (elt);		/* XXX */
180#if defined (PROGRAMMABLE_COMPLETION)
181      set_itemlist_dirty (&it_aliases);
182#endif
183      return (aliases->nentries);
184    }
185  return (-1);
186}
187
188/* Delete all aliases. */
189void
190delete_all_aliases ()
191{
192  if (aliases == 0)
193    return;
194
195  hash_flush (aliases, free_alias_data);
196  hash_dispose (aliases);
197  aliases = (HASH_TABLE *)NULL;
198#if defined (PROGRAMMABLE_COMPLETION)
199  set_itemlist_dirty (&it_aliases);
200#endif
201}
202
203/* Return an array of aliases that satisfy the conditions tested by FUNCTION.
204   If FUNCTION is NULL, return all aliases. */
205static alias_t **
206map_over_aliases (function)
207     sh_alias_map_func_t *function;
208{
209  register int i;
210  register BUCKET_CONTENTS *tlist;
211  alias_t *alias, **list;
212  int list_index;
213
214  i = HASH_ENTRIES (aliases);
215  if (i == 0)
216    return ((alias_t **)NULL);
217
218  list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *));
219  for (i = list_index = 0; i < aliases->nbuckets; i++)
220    {
221      for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next)
222	{
223	  alias = (alias_t *)tlist->data;
224
225	  if (!function || (*function) (alias))
226	    {
227	      list[list_index++] = alias;
228	      list[list_index] = (alias_t *)NULL;
229	    }
230	}
231    }
232  return (list);
233}
234
235static void
236sort_aliases (array)
237     alias_t **array;
238{
239  qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare);
240}
241
242static int
243qsort_alias_compare (as1, as2)
244     alias_t **as1, **as2;
245{
246  int result;
247
248  if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0)
249    result = strcmp ((*as1)->name, (*as2)->name);
250
251  return (result);
252}
253
254/* Return a sorted list of all defined aliases */
255alias_t **
256all_aliases ()
257{
258  alias_t **list;
259
260  if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
261    return ((alias_t **)NULL);
262
263  list = map_over_aliases ((sh_alias_map_func_t *)NULL);
264  if (list)
265    sort_aliases (list);
266  return (list);
267}
268
269char *
270alias_expand_word (s)
271     char *s;
272{
273  alias_t *r;
274
275  r = find_alias (s);
276  return (r ? savestring (r->value) : (char *)NULL);
277}
278
279/* Readline support functions -- expand all aliases in a line. */
280
281#if defined (READLINE)
282
283/* Return non-zero if CHARACTER is a member of the class of characters
284   that are self-delimiting in the shell (this really means that these
285   characters delimit tokens). */
286#define self_delimiting(character) (member ((character), " \t\n\r;|&()"))
287
288/* Return non-zero if CHARACTER is a member of the class of characters
289   that delimit commands in the shell. */
290#define command_separator(character) (member ((character), "\r\n;|&("))
291
292/* If this is 1, we are checking the next token read for alias expansion
293   because it is the first word in a command. */
294static int command_word;
295
296/* This is for skipping quoted strings in alias expansions. */
297#define quote_char(c)  (((c) == '\'') || ((c) == '"'))
298
299/* Consume a quoted string from STRING, starting at string[START] (so
300   string[START] is the opening quote character), and return the index
301   of the closing quote character matching the opening quote character.
302   This handles single matching pairs of unquoted quotes; it could afford
303   to be a little smarter... This skips words between balanced pairs of
304   quotes, words where the first character is quoted with a `\', and other
305   backslash-escaped characters. */
306
307static int
308skipquotes (string, start)
309     char *string;
310     int start;
311{
312  register int i;
313  int delimiter = string[start];
314
315  /* i starts at START + 1 because string[START] is the opening quote
316     character. */
317  for (i = start + 1 ; string[i] ; i++)
318    {
319      if (string[i] == '\\')
320	{
321	  i++;		/* skip backslash-quoted quote characters, too */
322	  continue;
323	}
324
325      if (string[i] == delimiter)
326	return i;
327    }
328  return (i);
329}
330
331/* Skip the white space and any quoted characters in STRING, starting at
332   START.  Return the new index into STRING, after zero or more characters
333   have been skipped. */
334static int
335skipws (string, start)
336     char *string;
337     int start;
338{
339  register int i;
340  int pass_next, backslash_quoted_word;
341  unsigned char peekc;
342
343  /* skip quoted strings, in ' or ", and words in which a character is quoted
344     with a `\'. */
345  i = backslash_quoted_word = pass_next = 0;
346
347  /* Skip leading whitespace (or separator characters), and quoted words.
348     But save it in the output.  */
349
350  for (i = start; string[i]; i++)
351    {
352      if (pass_next)
353	{
354	  pass_next = 0;
355	  continue;
356	}
357
358      if (whitespace (string[i]))
359	{
360	  backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */
361	  continue;
362	}
363
364      if (string[i] == '\\')
365	{
366	  peekc = string[i+1];
367	  if (ISLETTER (peekc))
368	    backslash_quoted_word++;	/* this is a backslash-quoted word */
369	  else
370	    pass_next++;
371	  continue;
372	}
373
374      /* This only handles single pairs of non-escaped quotes.  This
375	 overloads backslash_quoted_word to also mean that a word like
376	 ""f is being scanned, so that the quotes will inhibit any expansion
377	 of the word. */
378      if (quote_char(string[i]))
379	{
380	  i = skipquotes (string, i);
381	  /* This could be a line that contains a single quote character,
382	     in which case skipquotes () terminates with string[i] == '\0'
383	     (the end of the string).  Check for that here. */
384	  if (string[i] == '\0')
385	    break;
386
387	  peekc = string[i + 1];
388	  if (ISLETTER (peekc))
389	    backslash_quoted_word++;
390	  continue;
391	}
392
393      /* If we're in the middle of some kind of quoted word, let it
394	 pass through. */
395      if (backslash_quoted_word)
396	continue;
397
398      /* If this character is a shell command separator, then set a hint for
399	 alias_expand that the next token is the first word in a command. */
400
401      if (command_separator (string[i]))
402	{
403	  command_word++;
404	  continue;
405	}
406      break;
407    }
408  return (i);
409}
410
411/* Characters that may appear in a token.  Basically, anything except white
412   space and a token separator. */
413#define token_char(c)	(!((whitespace (string[i]) || self_delimiting (string[i]))))
414
415/* Read from START in STRING until the next separator character, and return
416   the index of that separator.  Skip backslash-quoted characters.  Call
417   skipquotes () for quoted strings in the middle or at the end of tokens,
418   so all characters show up (e.g. foo'' and foo""bar) */
419static int
420rd_token (string, start)
421     char *string;
422     int start;
423{
424  register int i;
425
426  /* From here to next separator character is a token. */
427  for (i = start; string[i] && token_char (string[i]); i++)
428    {
429      if (string[i] == '\\')
430	{
431	  i++;	/* skip backslash-escaped character */
432	  continue;
433	}
434
435      /* If this character is a quote character, we want to call skipquotes
436	 to get the whole quoted portion as part of this word.  That word
437	 will not generally match an alias, even if te unquoted word would
438	 have.  The presence of the quotes in the token serves then to
439	 inhibit expansion. */
440      if (quote_char (string[i]))
441	{
442	  i = skipquotes (string, i);
443	  /* This could be a line that contains a single quote character,
444	     in which case skipquotes () terminates with string[i] == '\0'
445	     (the end of the string).  Check for that here. */
446	  if (string[i] == '\0')
447	    break;
448
449	  /* Now string[i] is the matching quote character, and the
450	     quoted portion of the token has been scanned. */
451	  continue;
452	}
453    }
454  return (i);
455}
456
457/* Return a new line, with any aliases substituted. */
458char *
459alias_expand (string)
460     char *string;
461{
462  register int i, j, start;
463  char *line, *token;
464  int line_len, tl, real_start, expand_next, expand_this_token;
465  alias_t *alias;
466
467  line_len = strlen (string) + 1;
468  line = (char *)xmalloc (line_len);
469  token = (char *)xmalloc (line_len);
470
471  line[0] = i = 0;
472  expand_next = 0;
473  command_word = 1; /* initialized to expand the first word on the line */
474
475  /* Each time through the loop we find the next word in line.  If it
476     has an alias, substitute the alias value.  If the value ends in ` ',
477     then try again with the next word.  Else, if there is no value, or if
478     the value does not end in space, we are done. */
479
480  for (;;)
481    {
482
483      token[0] = 0;
484      start = i;
485
486      /* Skip white space and quoted characters */
487      i = skipws (string, start);
488
489      if (start == i && string[i] == '\0')
490	{
491	  free (token);
492	  return (line);
493	}
494
495      /* copy the just-skipped characters into the output string,
496	 expanding it if there is not enough room. */
497      j = strlen (line);
498      tl = i - start;	/* number of characters just skipped */
499      RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50));
500      strncpy (line + j, string + start, tl);
501      line[j + tl] = '\0';
502
503      real_start = i;
504
505      command_word = command_word || (command_separator (string[i]));
506      expand_this_token = (command_word || expand_next);
507      expand_next = 0;
508
509      /* Read the next token, and copy it into TOKEN. */
510      start = i;
511      i = rd_token (string, start);
512
513      tl = i - start;	/* token length */
514
515      /* If tl == 0, but we're not at the end of the string, then we have a
516	 single-character token, probably a delimiter */
517      if (tl == 0 && string[i] != '\0')
518	{
519	  tl = 1;
520	  i++;		/* move past it */
521	}
522
523      strncpy (token, string + start, tl);
524      token [tl] = '\0';
525
526      /* If there is a backslash-escaped character quoted in TOKEN,
527	 then we don't do alias expansion.  This should check for all
528	 other quoting characters, too. */
529      if (xstrchr (token, '\\'))
530	expand_this_token = 0;
531
532      /* If we should be expanding here, if we are expanding all words, or if
533	 we are in a location in the string where an expansion is supposed to
534	 take place, see if this word has a substitution.  If it does, then do
535	 the expansion.  Note that we defer the alias value lookup until we
536	 are sure we are expanding this token. */
537
538      if ((token[0]) &&
539	  (expand_this_token || alias_expand_all) &&
540	  (alias = find_alias (token)))
541	{
542	  char *v;
543	  int vlen, llen;
544
545	  v = alias->value;
546	  vlen = strlen (v);
547	  llen = strlen (line);
548
549	  /* +3 because we possibly add one more character below. */
550	  RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50));
551
552	  strcpy (line + llen, v);
553
554	  if ((expand_this_token && vlen && whitespace (v[vlen - 1])) ||
555	      alias_expand_all)
556	    expand_next = 1;
557	}
558      else
559	{
560	  int llen, tlen;
561
562	  llen = strlen (line);
563	  tlen = i - real_start; /* tlen == strlen(token) */
564
565	  RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50));
566
567	  strncpy (line + llen, string + real_start, tlen);
568	  line[llen + tlen] = '\0';
569	}
570      command_word = 0;
571    }
572}
573#endif /* READLINE */
574#endif /* ALIAS */
575