1This file is hash.def, from which is created hash.c.
2It implements the builtin "hash" in Bash.
3
4Copyright (C) 1987-2020 Free Software Foundation, Inc.
5
6This file is part of GNU Bash, the Bourne Again SHell.
7
8Bash is free software: you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation, either version 3 of the License, or
11(at your option) any later version.
12
13Bash is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with Bash.  If not, see <http://www.gnu.org/licenses/>.
20
21$PRODUCES hash.c
22
23$BUILTIN hash
24$FUNCTION hash_builtin
25$SHORT_DOC hash [-lr] [-p pathname] [-dt] [name ...]
26Remember or display program locations.
27
28Determine and remember the full pathname of each command NAME.  If
29no arguments are given, information about remembered commands is displayed.
30
31Options:
32  -d	forget the remembered location of each NAME
33  -l	display in a format that may be reused as input
34  -p pathname	use PATHNAME as the full pathname of NAME
35  -r	forget all remembered locations
36  -t	print the remembered location of each NAME, preceding
37		each location with the corresponding NAME if multiple
38		NAMEs are given
39Arguments:
40  NAME	Each NAME is searched for in $PATH and added to the list
41		of remembered commands.
42
43Exit Status:
44Returns success unless NAME is not found or an invalid option is given.
45$END
46
47#include <config.h>
48
49#include <stdio.h>
50
51#include "../bashtypes.h"
52
53#if defined (HAVE_UNISTD_H)
54#  include <unistd.h>
55#endif
56
57#include <errno.h>
58
59#include "../bashansi.h"
60#include "../bashintl.h"
61
62#include "../shell.h"
63#include "../builtins.h"
64#include "../execute_cmd.h"
65#include "../flags.h"
66#include "../findcmd.h"
67#include "../hashcmd.h"
68#include "common.h"
69#include "bashgetopt.h"
70
71extern int dot_found_in_search;
72
73static int add_hashed_command PARAMS((char *, int));
74static int print_hash_info PARAMS((BUCKET_CONTENTS *));
75static int print_portable_hash_info PARAMS((BUCKET_CONTENTS *));
76static int print_hashed_commands PARAMS((int));
77static int list_hashed_filename_targets PARAMS((WORD_LIST *, int));
78
79/* Print statistics on the current state of hashed commands.  If LIST is
80   not empty, then rehash (or hash in the first place) the specified
81   commands. */
82int
83hash_builtin (list)
84     WORD_LIST *list;
85{
86  int expunge_hash_table, list_targets, list_portably, delete, opt;
87  char *w, *pathname;
88
89  if (hashing_enabled == 0)
90    {
91      builtin_error (_("hashing disabled"));
92      return (EXECUTION_FAILURE);
93    }
94
95  expunge_hash_table = list_targets = list_portably = delete = 0;
96  pathname = (char *)NULL;
97  reset_internal_getopt ();
98  while ((opt = internal_getopt (list, "dlp:rt")) != -1)
99    {
100      switch (opt)
101	{
102	case 'd':
103	  delete = 1;
104	  break;
105	case 'l':
106	  list_portably = 1;
107	  break;
108	case 'p':
109	  pathname = list_optarg;
110	  break;
111	case 'r':
112	  expunge_hash_table = 1;
113	  break;
114	case 't':
115	  list_targets = 1;
116	  break;
117	CASE_HELPOPT;
118	default:
119	  builtin_usage ();
120	  return (EX_USAGE);
121	}
122    }
123  list = loptend;
124
125  /* hash -t requires at least one argument. */
126  if (list == 0 && (delete || list_targets))
127    {
128      sh_needarg (delete ? "-d" : "-t");
129      return (EXECUTION_FAILURE);
130    }
131
132  /* We want hash -r to be silent, but hash -- to print hashing info, so
133     we test expunge_hash_table. */
134  if (list == 0 && expunge_hash_table == 0)
135    {
136      opt = print_hashed_commands (list_portably);
137      if (opt == 0 && posixly_correct == 0 &&
138	    (list_portably == 0 || shell_compatibility_level <= 50))
139	printf (_("%s: hash table empty\n"), this_command_name);
140
141      return (EXECUTION_SUCCESS);
142    }
143
144  if (expunge_hash_table)
145    phash_flush ();
146
147  /* If someone runs `hash -r -t xyz' he will be disappointed. */
148  if (list_targets)
149    return (list_hashed_filename_targets (list, list_portably));
150
151#if defined (RESTRICTED_SHELL)
152  if (restricted && pathname)
153    {
154      if (strchr (pathname, '/'))
155	{
156          sh_restricted (pathname);
157          return (EXECUTION_FAILURE);
158	}
159      /* If we are changing the hash table in a restricted shell, make sure the
160	 target pathname can be found using a $PATH search. */
161      w = find_user_command (pathname);
162      if (w == 0 || *w == 0 || executable_file (w) == 0)
163	{
164	  sh_notfound (pathname);
165	  free (w);
166	  return (EXECUTION_FAILURE);
167	}
168      free (w);
169    }
170#endif
171
172  for (opt = EXECUTION_SUCCESS; list; list = list->next)
173    {
174      /* Add, remove or rehash the specified commands. */
175      w = list->word->word;
176      if (absolute_program (w))
177	continue;
178      else if (pathname)
179	{
180	  if (is_directory (pathname))
181	    {
182#ifdef EISDIR
183	      builtin_error ("%s: %s", pathname, strerror (EISDIR));
184#else
185	      builtin_error (_("%s: is a directory"), pathname);
186#endif
187	      opt = EXECUTION_FAILURE;
188	    }
189	  else
190	    phash_insert (w, pathname, 0, 0);
191	}
192      else if (delete)
193	{
194	  if (phash_remove (w))
195	    {
196	      sh_notfound (w);
197	      opt = EXECUTION_FAILURE;
198	    }
199	}
200      else if (add_hashed_command (w, 0))
201	opt = EXECUTION_FAILURE;
202    }
203
204  fflush (stdout);
205  return (opt);
206}
207
208static int
209add_hashed_command (w, quiet)
210     char *w;
211     int quiet;
212{
213  int rv;
214  char *full_path;
215
216  rv = 0;
217  if (find_function (w) == 0 && find_shell_builtin (w) == 0)
218    {
219      phash_remove (w);
220      full_path = find_user_command (w);
221      if (full_path && executable_file (full_path))
222	phash_insert (w, full_path, dot_found_in_search, 0);
223      else
224	{
225	  if (quiet == 0)
226	    sh_notfound (w);
227	  rv++;
228	}
229      FREE (full_path);
230    }
231  return (rv);
232}
233
234/* Print information about current hashed info. */
235static int
236print_hash_info (item)
237     BUCKET_CONTENTS *item;
238{
239  printf ("%4d\t%s\n", item->times_found, pathdata(item)->path);
240  return 0;
241}
242
243static int
244print_portable_hash_info (item)
245     BUCKET_CONTENTS *item;
246{
247  char *fp, *fn;
248
249  fp = printable_filename (pathdata(item)->path, 1);
250  fn = printable_filename (item->key, 1);
251  printf ("builtin hash -p %s %s\n", fp, fn);
252  if (fp != pathdata(item)->path)
253    free (fp);
254  if (fn != item->key)
255    free (fn);
256  return 0;
257}
258
259static int
260print_hashed_commands (fmt)
261     int fmt;
262{
263  if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0)
264    return (0);
265
266  if (fmt == 0)
267    printf (_("hits\tcommand\n"));
268  hash_walk (hashed_filenames, fmt ? print_portable_hash_info : print_hash_info);
269  return (1);
270}
271
272static int
273list_hashed_filename_targets (list, fmt)
274     WORD_LIST *list;
275     int fmt;
276{
277  int all_found, multiple;
278  char *target;
279  WORD_LIST *l;
280
281  all_found = 1;
282  multiple = list->next != 0;
283
284  for (l = list; l; l = l->next)
285    {
286      target = phash_search (l->word->word);
287      if (target == 0)
288	{
289	  all_found = 0;
290	  sh_notfound (l->word->word);
291	  continue;
292	}
293      if (fmt)
294	printf ("builtin hash -p %s %s\n", target, l->word->word);
295      else
296	{
297	  if (multiple)
298	    printf ("%s\t", l->word->word);
299	  printf ("%s\n", target);
300	}
301      free (target);
302    }
303
304  return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
305}
306