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