+/* Replace a tilde-prefix in VAL with a `~', assuming the user typed it. VAL
+ is an expanded filename. DIRECTORY_PART is the tilde-prefix portion
+ of the un-tilde-expanded version of VAL (what the user typed). */
+static char *
+restore_tilde (val, directory_part)
+ char *val, *directory_part;
+{
+ int l, vl, dl2, xl;
+ char *dh2, *expdir, *ret, *v;
+
+ vl = strlen (val);
+
+ /* We need to duplicate the expansions readline performs on the directory
+ portion before passing it to our completion function. */
+ dh2 = directory_part ? bash_dequote_filename (directory_part, 0) : 0;
+ bash_directory_expansion (&dh2);
+ dl2 = strlen (dh2);
+
+ expdir = bash_tilde_expand (directory_part, 0);
+ xl = strlen (expdir);
+ if (*directory_part == '~' && STREQ (directory_part, expdir))
+ {
+ /* tilde expansion failed, so what should we return? we use what the
+ user typed. */
+ v = mbschr (val, '/');
+ vl = STRLEN (v);
+ ret = (char *)xmalloc (xl + vl + 2);
+ strcpy (ret, directory_part);
+ if (v && *v)
+ strcpy (ret + xl, v);
+
+ free (dh2);
+ free (expdir);
+
+ return ret;
+ }
+ free (expdir);
+
+ /*
+ dh2 = unexpanded but dequoted tilde-prefix
+ dl2 = length of tilde-prefix
+ expdir = tilde-expanded tilde-prefix
+ xl = length of expanded tilde-prefix
+ l = length of remainder after tilde-prefix
+ */
+ l = (vl - xl) + 1;
+ if (l <= 0)
+ {
+ free (dh2);
+ return (savestring (val)); /* XXX - just punt */
+ }
+
+ ret = (char *)xmalloc (dl2 + 2 + l);
+ strcpy (ret, dh2);
+ strcpy (ret + dl2, val + xl);
+
+ free (dh2);
+ return (ret);
+}
+
+static char *
+maybe_restore_tilde (val, directory_part)
+ char *val, *directory_part;
+{
+ rl_icppfunc_t *save;
+ char *ret;
+
+ save = (dircomplete_expand == 0) ? save_directory_hook () : (rl_icppfunc_t *)0;
+ ret = restore_tilde (val, directory_part);
+ if (save)
+ restore_directory_hook (save);
+ return ret;
+}
+
+/* Simulate the expansions that will be performed by
+ rl_filename_completion_function. This must be called with the address of
+ a pointer to malloc'd memory. */
+static void
+bash_directory_expansion (dirname)
+ char **dirname;
+{
+ char *d, *nd;
+
+ d = savestring (*dirname);
+
+ if ((rl_directory_rewrite_hook) && (*rl_directory_rewrite_hook) (&d))
+ {
+ free (*dirname);
+ *dirname = d;
+ }
+ else if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d))
+ {
+ free (*dirname);
+ *dirname = d;
+ }
+ else if (rl_completion_found_quote)
+ {
+ nd = bash_dequote_filename (d, rl_completion_quote_character);
+ free (*dirname);
+ free (d);
+ *dirname = nd;
+ }
+}
+
+/* If necessary, rewrite directory entry */
+static char *
+bash_filename_rewrite_hook (fname, fnlen)
+ char *fname;
+ int fnlen;
+{
+ char *conv;
+
+ conv = fnx_fromfs (fname, fnlen);
+ if (conv != fname)
+ conv = savestring (conv);
+ return conv;
+}
+
+/* Functions to save and restore the appropriate directory hook */
+/* This is not static so the shopt code can call it */
+void
+set_directory_hook ()
+{
+ if (dircomplete_expand)
+ {
+ rl_directory_completion_hook = bash_directory_completion_hook;
+ rl_directory_rewrite_hook = (rl_icppfunc_t *)0;
+ }
+ else
+ {
+ rl_directory_rewrite_hook = bash_directory_completion_hook;
+ rl_directory_completion_hook = (rl_icppfunc_t *)0;
+ }
+}
+
+static rl_icppfunc_t *
+save_directory_hook ()
+{
+ rl_icppfunc_t *ret;
+
+ if (dircomplete_expand)
+ {
+ ret = rl_directory_completion_hook;
+ rl_directory_completion_hook = (rl_icppfunc_t *)NULL;
+ }
+ else
+ {
+ ret = rl_directory_rewrite_hook;
+ rl_directory_rewrite_hook = (rl_icppfunc_t *)NULL;
+ }
+
+ return ret;
+}
+
+static void
+restore_directory_hook (hookf)
+ rl_icppfunc_t *hookf;
+{
+ if (dircomplete_expand)
+ rl_directory_completion_hook = hookf;
+ else
+ rl_directory_rewrite_hook = hookf;
+}
+
+/* Check whether not DIRNAME, with any trailing slash removed, exists. If
+ SHOULD_DEQUOTE is non-zero, we dequote the directory name first. */
+static int
+directory_exists (dirname, should_dequote)
+ const char *dirname;
+ int should_dequote;
+{
+ char *new_dirname;
+ int dirlen, r;
+ struct stat sb;
+
+ /* We save the string and chop the trailing slash because stat/lstat behave
+ inconsistently if one is present. */
+ new_dirname = should_dequote ? bash_dequote_filename ((char *)dirname, rl_completion_quote_character) : savestring (dirname);
+ dirlen = STRLEN (new_dirname);
+ if (new_dirname[dirlen - 1] == '/')
+ new_dirname[dirlen - 1] = '\0';
+#if defined (HAVE_LSTAT)
+ r = lstat (new_dirname, &sb) == 0;
+#else
+ r = stat (new_dirname, &sb) == 0;
+#endif
+ free (new_dirname);
+ return (r);
+}
+
+/* Expand a filename before the readline completion code passes it to stat(2).
+ The filename will already have had tilde expansion performed. */
+static int
+bash_filename_stat_hook (dirname)
+ char **dirname;
+{
+ char *local_dirname, *new_dirname, *t;
+ int should_expand_dirname, return_value;
+ int global_nounset;
+ WORD_LIST *wl;
+
+ local_dirname = *dirname;
+ should_expand_dirname = return_value = 0;
+ if (t = mbschr (local_dirname, '$'))
+ should_expand_dirname = '$';
+ else if (t = mbschr (local_dirname, '`')) /* XXX */
+ should_expand_dirname = '`';
+
+ if (should_expand_dirname && directory_exists (local_dirname, 0))
+ should_expand_dirname = 0;
+
+ if (should_expand_dirname)
+ {
+ new_dirname = savestring (local_dirname);
+ /* no error messages, and expand_prompt_string doesn't longjmp so we don't
+ have to worry about restoring this setting. */
+ global_nounset = unbound_vars_is_error;
+ unbound_vars_is_error = 0;
+ wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB|W_NOPROCSUB|W_COMPLETE); /* does the right thing */
+ unbound_vars_is_error = global_nounset;
+ if (wl)
+ {
+ free (new_dirname);
+ new_dirname = string_list (wl);
+ /* Tell the completer we actually expanded something and change
+ *dirname only if we expanded to something non-null -- stat
+ behaves unpredictably when passed null or empty strings */
+ if (new_dirname && *new_dirname)
+ {
+ free (local_dirname); /* XXX */
+ local_dirname = *dirname = new_dirname;
+ return_value = STREQ (local_dirname, *dirname) == 0;
+ }
+ else
+ free (new_dirname);
+ dispose_words (wl);
+ }
+ else
+ free (new_dirname);
+ }
+
+ /* This is very similar to the code in bash_directory_completion_hook below,
+ but without spelling correction and not worrying about whether or not
+ we change relative pathnames. */
+ if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1]))
+ {
+ char *temp1, *temp2;
+
+ t = get_working_directory ("symlink-hook");
+ temp1 = make_absolute (local_dirname, t);
+ free (t);
+ temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+
+ /* If we can't canonicalize, bail. */
+ if (temp2 == 0)
+ {
+ free (temp1);
+ return return_value;
+ }
+
+ free (local_dirname);
+ *dirname = temp2;
+ free (temp1);
+ }
+
+ return (return_value);
+}
+