summaryrefslogtreecommitdiffstats
path: root/hashcmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'hashcmd.c')
-rw-r--r--hashcmd.c195
1 files changed, 195 insertions, 0 deletions
diff --git a/hashcmd.c b/hashcmd.c
new file mode 100644
index 0000000..da3af00
--- /dev/null
+++ b/hashcmd.c
@@ -0,0 +1,195 @@
+/* hashcmd.c - functions for managing a hash table mapping command names to
+ full pathnames. */
+
+/* Copyright (C) 1997-2020 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>
+
+#include "bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "bashansi.h"
+
+#include "shell.h"
+#include "flags.h"
+#include "findcmd.h"
+#include "hashcmd.h"
+
+HASH_TABLE *hashed_filenames = (HASH_TABLE *)NULL;
+
+static void phash_freedata PARAMS((PTR_T));
+
+void
+phash_create ()
+{
+ if (hashed_filenames == 0)
+ hashed_filenames = hash_create (FILENAME_HASH_BUCKETS);
+}
+
+static void
+phash_freedata (data)
+ PTR_T data;
+{
+ free (((PATH_DATA *)data)->path);
+ free (data);
+}
+
+void
+phash_flush ()
+{
+ if (hashed_filenames)
+ hash_flush (hashed_filenames, phash_freedata);
+}
+
+/* Remove FILENAME from the table of hashed commands. */
+int
+phash_remove (filename)
+ const char *filename;
+{
+ register BUCKET_CONTENTS *item;
+
+ if (hashing_enabled == 0 || hashed_filenames == 0)
+ return 0;
+
+ item = hash_remove (filename, hashed_filenames, 0);
+ if (item)
+ {
+ if (item->data)
+ phash_freedata (item->data);
+ free (item->key);
+ free (item);
+ return 0;
+ }
+ return 1;
+}
+
+/* Place FILENAME (key) and FULL_PATH (data->path) into the
+ hash table. CHECK_DOT if non-null is for future calls to
+ phash_search (); it means that this file was found
+ in a directory in $PATH that is not an absolute pathname.
+ FOUND is the initial value for times_found. */
+void
+phash_insert (filename, full_path, check_dot, found)
+ char *filename, *full_path;
+ int check_dot, found;
+{
+ register BUCKET_CONTENTS *item;
+
+ if (hashing_enabled == 0)
+ return;
+
+ if (hashed_filenames == 0)
+ phash_create ();
+
+ item = hash_insert (filename, hashed_filenames, 0);
+ if (item->data)
+ free (pathdata(item)->path);
+ else
+ {
+ item->key = savestring (filename);
+ item->data = xmalloc (sizeof (PATH_DATA));
+ }
+ pathdata(item)->path = savestring (full_path);
+ pathdata(item)->flags = 0;
+ if (check_dot)
+ pathdata(item)->flags |= HASH_CHKDOT;
+ if (*full_path != '/')
+ pathdata(item)->flags |= HASH_RELPATH;
+ item->times_found = found;
+}
+
+/* Return the full pathname that FILENAME hashes to. If FILENAME
+ is hashed, but (data->flags & HASH_CHKDOT) is non-zero, check
+ ./FILENAME and return that if it is executable. This always
+ returns a newly-allocated string; the caller is responsible
+ for freeing it. */
+char *
+phash_search (filename)
+ const char *filename;
+{
+ register BUCKET_CONTENTS *item;
+ char *path, *dotted_filename, *tail;
+ int same;
+
+ if (hashing_enabled == 0 || hashed_filenames == 0)
+ return ((char *)NULL);
+
+ item = hash_search (filename, hashed_filenames, 0);
+
+ if (item == NULL)
+ return ((char *)NULL);
+
+ /* If this filename is hashed, but `.' comes before it in the path,
+ see if ./filename is executable. If the hashed value is not an
+ absolute pathname, see if ./`hashed-value' exists. */
+ path = pathdata(item)->path;
+ if (pathdata(item)->flags & (HASH_CHKDOT|HASH_RELPATH))
+ {
+ tail = (pathdata(item)->flags & HASH_RELPATH) ? path : (char *)filename; /* XXX - fix const later */
+ /* If the pathname does not start with a `./', add a `./' to it. */
+ if (tail[0] != '.' || tail[1] != '/')
+ {
+ dotted_filename = (char *)xmalloc (3 + strlen (tail));
+ dotted_filename[0] = '.'; dotted_filename[1] = '/';
+ strcpy (dotted_filename + 2, tail);
+ }
+ else
+ dotted_filename = savestring (tail);
+
+ if (executable_file (dotted_filename))
+ return (dotted_filename);
+
+ free (dotted_filename);
+
+#if 0
+ if (pathdata(item)->flags & HASH_RELPATH)
+ return ((char *)NULL);
+#endif
+
+ /* Watch out. If this file was hashed to "./filename", and
+ "./filename" is not executable, then return NULL. */
+
+ /* Since we already know "./filename" is not executable, what
+ we're really interested in is whether or not the `path'
+ portion of the hashed filename is equivalent to the current
+ directory, but only if it starts with a `.'. (This catches
+ ./. and so on.) same_file () tests general Unix file
+ equivalence -- same device and inode. */
+ if (*path == '.')
+ {
+ same = 0;
+ tail = (char *)strrchr (path, '/');
+
+ if (tail)
+ {
+ *tail = '\0';
+ same = same_file (".", path, (struct stat *)NULL, (struct stat *)NULL);
+ *tail = '/';
+ }
+
+ return same ? (char *)NULL : savestring (path);
+ }
+ }
+
+ return (savestring (path));
+}