- convert_var_to_{array,assoc}: only save the old value if the variable
isn't an array; dispose old value appropriately
Report and patch from Grisha Levit <grishalevit@gmail.com>
+
+ 6/15
+ ----
+builtins/type.def
+ - describe_command: even if the command name contains a slash, if the
+ flags include CDESC_ABSPATH, convert to an absolute pathname if it
+ doesn't begin with a slash
+
+builtins/command.def
+ - command_builtin: in posix mode, `command -v' adds CDESC_ABSPATH
+ to the flags passed to describe_command() so it prints as an
+ absolute pathname
+ From a discussion on the dash mailing list about POSIX requirements
+
+redir.c
+ - redirection_error: make sure to sanitize command names using
+ printable_filename before printing error messages
+ Report from Vincent Lefevre <vincent@vinc17.net>
+
+execute_cmd.c,shell.c,variables.c
+builtins/evalfile.c,builtins/evalstring.c
+builtins/builtin.def,builtins/command.def,builtins/enable.def
+builtins/exec.def,builtins/hash.def,builtins/help.def,builtins/history.def
+builtins/source.def,builtins/type.def
+ - sanitize command and file names used in error messages by calling
+ printable_filename
builtin_builtin (WORD_LIST *list)
{
sh_builtin_func_t *function;
- register char *command;
+ char *command, *newname;
int r;
if (r = no_options (list))
if (function == 0)
{
- sh_notbuiltin (command);
+ newname = printable_filename (command, 0);
+ sh_notbuiltin (newname);
+ if (newname != command)
+ free (newname);
return (EXECUTION_FAILURE);
}
else
verbose = CDESC_SHORTDESC|CDESC_ABSPATH; /* look in common.h for constants */
break;
case 'v':
- verbose = CDESC_REUSABLE; /* ditto */
+ verbose = CDESC_REUSABLE| (posixly_correct ? CDESC_ABSPATH : 0); /* ditto */
break;
CASE_HELPOPT;
default:
if (verbose)
{
int found, any_found;
+ char *newfn;
for (any_found = 0; list; list = list->next)
{
found = describe_command (list->word->word, verbose|use_standard_path);
if (found == 0 && verbose != CDESC_REUSABLE)
- sh_notfound (list->word->word);
+ {
+ newfn = printable_filename (list->word->word, 0);
+ sh_notfound (newfn);
+ if (newfn != list->word->word)
+ free (newfn);
+ }
any_found += found;
}
This file is enable.def, from which is created enable.c.
It implements the builtin "enable" in Bash.
-Copyright (C) 1987-2024 Free Software Foundation, Inc.
+Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
{
while (list)
{
- char *command;
+ char *command, *newname;
command = list->word->word;
opt = enable_shell_command (command, flags & NFLAG);
if (opt == EX_NOTFOUND)
{
- sh_notbuiltin (command);
+ newname = printable_filename (command, 0);
+ sh_notbuiltin (newname);
+ if (newname != command)
+ free (newname);
result = EXECUTION_FAILURE;
}
else if (opt != EXECUTION_SUCCESS)
name = list->word->word;
if (absolute_program (name))
{
+ name = printable_filename (list->word->word, 0);
builtin_error (_("%s: builtin names may not contain slashes"), name);
+ if (name != list->word->word)
+ free (name);
continue;
}
{
struct builtin *b;
void *handle;
- char *funcname;
+ char *funcname, *newname;
sh_unload_func_t *unloadfunc;
int ref, i;
size_t size;
b = builtin_address_internal (name, 1);
if (b == 0)
{
- sh_notbuiltin (name);
+ newname = printable_filename (name, 0);
+ sh_notbuiltin (newname);
+ if (newname != name)
+ free (newname);
return (EXECUTION_FAILURE);
}
if (b->flags & STATIC_BUILTIN)
{
- builtin_error (_("%s: not dynamically loaded"), name);
+ newname = printable_filename (name, 0);
+ builtin_error (_("%s: not dynamically loaded"), newname);
+ if (newname != name)
+ free (newname);
return (EXECUTION_FAILURE);
}
using it drops to zero. */
if (ref == 1 && local_dlclose (handle) != 0)
{
- builtin_error (_("%s: cannot delete: %s"), name, dlerror ());
+ newname = printable_filename (name, 0);
+ builtin_error (_("%s: cannot delete: %s"), newname, dlerror ());
+ if (newname != name)
+ free (newname);
return (EXECUTION_FAILURE);
}
/* evalfile.c - read and evaluate commands from a file or file descriptor */
-/* Copyright (C) 1996-2017,2022-2024 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
file_error_and_exit:
if (((flags & FEVAL_ENOENTOK) == 0) || errno != ENOENT)
- file_error (filename);
+ {
+ t = printable_filename ((char *)filename, 0);
+ file_error (t);
+ if (t != filename)
+ free (t);
+ }
if (flags & FEVAL_LONGJMP)
{
if (S_ISDIR (finfo.st_mode))
{
- (*errfunc) (_("%s: is a directory"), filename);
+ t = printable_filename ((char *)filename, 0);
+ (*errfunc) (_("%s: is a directory"), t);
+ if (t != filename)
+ free (t);
close (fd);
return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
}
else if ((flags & FEVAL_REGFILE) && S_ISREG (finfo.st_mode) == 0)
{
- (*errfunc) (_("%s: not a regular file"), filename);
+ t = printable_filename ((char *)filename, 0);
+ (*errfunc) (_("%s: not a regular file"), t);
+ if (t != filename)
+ free (t);
close (fd);
return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
}
/* evalstring.c - evaluate a string as one or more shell commands. */
-/* Copyright (C) 1996-2025 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
int
open_redir_file (REDIRECT *r, char **fnp)
{
- char *fn;
+ char *fn, *newfn;
int fd;
if (r->instruction != r_input_direction)
fd = open(fn, O_RDONLY);
if (fd < 0)
{
- internal_error ("%s: %s", fn, strerror (errno));
+ newfn = printable_filename (fn, 0);
+ internal_error ("%s: %s", newfn, strerror (errno));
+ if (newfn != fn)
+ free (newfn);
free (fn);
if (fnp)
*fnp = 0;
This file is exec.def, from which is created exec.c.
It implements the builtin "exec" in Bash.
-Copyright (C) 1987-2021,2022,2024,2025 Free Software Foundation, Inc.
+Copyright (C) 1987-2021,2022,2024-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
if (command == 0)
{
+ newname = printable_filename (args[0], 0);
if (file_isdir (args[0]))
{
#if defined (EISDIR)
- builtin_error ("%s: %s: %s", args[0], _("cannot execute"), strerror (EISDIR));
+ builtin_error ("%s: %s: %s", newname, _("cannot execute"), strerror (EISDIR));
#else
- builtin_error ("%s: %s: %s", args[0], _("cannot execute"), strerror (errno));
+ builtin_error ("%s: %s: %s", newname, _("cannot execute"), strerror (errno));
#endif
exit_value = EX_NOEXEC;
}
else
{
- sh_notfound (args[0]);
+ sh_notfound (newname);
exit_value = EX_NOTFOUND; /* As per Posix.2, 3.14.6 */
}
+ if (newname != args[0])
+ free (newname);
goto failed_exec;
}
This file is hash.def, from which is created hash.c.
It implements the builtin "hash" in Bash.
-Copyright (C) 1987-2024 Free Software Foundation, Inc.
+Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
hash_builtin (WORD_LIST *list)
{
int expunge_hash_table, list_targets, list_portably, delete, opt;
- char *w, *pathname;
+ char *w, *pathname, *newfn;
if (hashing_enabled == 0)
{
{
if (absolute_program (pathname))
{
- sh_restricted (pathname);
- return (EXECUTION_FAILURE);
+ newfn = printable_filename (pathname, 0);
+ sh_restricted (newfn);
+ if (newfn != pathname)
+ free (newfn);
+ return (EXECUTION_FAILURE);
}
/* If we are changing the hash table in a restricted shell, make sure the
target pathname can be found using a $PATH search. */
w = find_user_command (pathname);
if (w == 0 || *w == 0 || executable_file (w) == 0)
{
- sh_notfound (pathname);
+ newfn = printable_filename (pathname, 0);
+ sh_notfound (newfn);
+ if (newfn != pathname)
+ free (newfn);
free (w);
return (EXECUTION_FAILURE);
}
{
if (file_isdir (pathname))
{
+ newfn = printable_filename (pathname, 0);
#ifdef EISDIR
- builtin_error ("%s: %s", pathname, strerror (EISDIR));
+ builtin_error ("%s: %s", newfn, strerror (EISDIR));
#else
- builtin_error (_("%s: is a directory"), pathname);
+ builtin_error (_("%s: is a directory"), newfn);
#endif
+ if (newfn != pathname)
+ free (newfn);
opt = EXECUTION_FAILURE;
}
else
{
if (phash_remove (w))
{
- sh_notfound (w);
+ newfn = printable_filename (w, 0);
+ sh_notfound (newfn);
+ if (newfn != w)
+ free (newfn);
opt = EXECUTION_FAILURE;
}
}
add_hashed_command (char *w, int quiet)
{
int rv;
- char *full_path;
+ char *full_path, *newfn;
rv = 0;
if (find_function (w) == 0 && find_shell_builtin (w) == 0)
else
{
if (quiet == 0)
- sh_notfound (w);
+ {
+ newfn = printable_filename (w, 0);
+ sh_notfound (newfn);
+ if (newfn != w)
+ free (newfn);
+ }
rv++;
}
FREE (full_path);
list_hashed_filename_targets (WORD_LIST *list, int fmt)
{
int all_found, multiple;
- char *target;
+ char *target, *fp, *fn;
WORD_LIST *l;
all_found = 1;
for (l = list; l; l = l->next)
{
+ fn = printable_filename (l->word->word, 1);
target = phash_search (l->word->word);
if (target == 0)
{
all_found = 0;
- sh_notfound (l->word->word);
+ sh_notfound (fn);
+ if (fn != l->word->word)
+ free (fn);
continue;
}
+ fp = printable_filename (target, 1);
+ if (fp != target)
+ {
+ free (target);
+ target = fp;
+ }
if (fmt)
- printf ("builtin hash -p %s %s\n", target, l->word->word);
+ printf ("builtin hash -p %s %s\n", target, fn);
else
{
if (multiple)
- printf ("%s\t", l->word->word);
+ printf ("%s\t", fn);
printf ("%s\n", target);
}
+ if (fn != l->word->word)
+ free (fn);
free (target);
}
This file is help.def, from which is created help.c.
It implements the builtin "help" in Bash.
-Copyright (C) 1987-2025 Free Software Foundation, Inc.
+Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
open_helpfile (const char *name)
{
int fd;
+ char *newfn;
fd = open (name, O_RDONLY);
if (fd == -1)
{
- builtin_error ("%s: %s: %s", name, _("cannot open"), strerror (errno));
+ newfn = printable_filename (name, 0);
+ builtin_error ("%s: %s: %s", newfn, _("cannot open"), strerror (errno));
+ if (newfn != name)
+ free (newfn);
return -1;
}
return fd;
history_builtin (WORD_LIST *list)
{
int flags, opt, result, old_history_lines, obase, ind;
- char *filename, *delete_arg, *range;
+ char *filename, *newfn, *delete_arg, *range;
intmax_t delete_offset;
flags = 0;
#if defined (RESTRICTED_SHELL)
if (restricted && absolute_program (filename))
{
- sh_restricted (filename);
+ newfn = printable_filename (filename, 0);
+ sh_restricted (newfn);
+ if (newfn != filename)
+ free (newfn);
return (EXECUTION_FAILURE);
}
#endif
static void
history_error (const char *filename, int e, int r)
{
+ char *newfn;
+
switch (e)
{
case ENOENT:
#if defined (EACCES)
case EACCES:
#endif
+ newfn = printable_filename ((char *)filename, 0);
if (r)
- builtin_error ("%s: %s: %s", filename, _("read error"), strerror (e));
+ builtin_error ("%s: %s: %s", newfn, _("read error"), strerror (e));
else
- builtin_error ("%s: %s: %s", filename, _("write error"), strerror (e));
+ builtin_error ("%s: %s: %s", newfn, _("write error"), strerror (e));
+ if (newfn != filename)
+ free (newfn);
break;
default:
#if defined (RESTRICTED_SHELL)
if (restricted && (pathstring || absolute_program (list->word->word)))
{
- sh_restricted (list->word->word);
+ filename = printable_filename (list->word->word, 0);
+ sh_restricted (filename);
+ if (filename != list->word->word)
+ free (filename);
return (EXECUTION_FAILURE);
}
#endif
This file is type.def, from which is created type.c.
It implements the builtin "type" in Bash.
-Copyright (C) 1987-2023 Free Software Foundation, Inc.
+Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
while (list)
{
int found;
+ char *newfn;
found = describe_command (list->word->word, dflags);
if (!found && (dflags & (CDESC_PATH_ONLY|CDESC_TYPE)) == 0)
- sh_notfound (list->word->word);
+ {
+ newfn = printable_filename (list->word->word, 0);
+ sh_notfound (newfn);
+ if (newfn != list->word->word)
+ free (newfn);
+ }
any_failed += found == 0;
list = list->next;
f = file_status (command);
if (f & FS_EXECABLE)
{
+ /* convert to absolute pathname? */
+ if ((dflags & CDESC_ABSPATH) && ABSPATH (command) == 0)
+ full_path = sh_makepath ((char *)NULL, command, MP_DOCWD|MP_RMDOT);
+ else
+ full_path = command;
+
if (dflags & CDESC_TYPE)
puts ("file");
else if (dflags & CDESC_SHORTDESC)
- printf (_("%s is %s\n"), command, command);
+ printf (_("%s is %s\n"), command, full_path);
else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
- printf ("%s\n", command);
+ printf ("%s\n", full_path);
+
+ if (full_path != command)
+ free (full_path);
/* There's no use looking in the hash table or in $PATH,
because they're not consulted when an absolute program
Begin a sequence of non-printing characters, which could be used to
embed a terminal control sequence into the prompt.
This escape is only useful when the prompt will be supplied to
-\fBreadline\fP, so it shouldn't be used in \fBPS0\fP or \fBPS4\fP
+\fBreadline\fP,
+and is ignored and removed otherwise,
+so it shouldn't be used in \fBPS0\fP or \fBPS4\fP
or when line editing is not enabled.
.TP
.B \e]
This could be used to
embed a terminal control sequence into the prompt.
This escape is only useful when the prompt will be supplied to
-Readline, so it shouldn't be used in @env{PS0} or @env{PS4},
+Readline,
+and is ignored and removed otherwise,
+so it shouldn't be used in @env{PS0} or @env{PS4},
or when line editing is not enabled.
@item \]
exceeds @code{PATH_MAX} when canonicalized, @code{cd} will
attempt to use the supplied directory name.
+@item
+The @code{command} builtin prints command name arguments containing a
+slash as full pathnames when the @option{-v} option is supplied.
+
@item
When the @code{xpg_echo} option is enabled, Bash does not attempt to
interpret any arguments to @code{echo} as options.
command = (char *)NULL;
if (restricted && mbschr (pathname, '/'))
{
+ char *newfn;
+
+ newfn = printable_filename (pathname, 0);
internal_error (_("%s: restricted: cannot specify `/' in command names"),
- pathname);
+ newfn);
+ if (newfn != pathname)
+ free (newfn);
result = last_command_exit_value = EXECUTION_FAILURE;
/* If we're not going to fork below, we must already be in a child
shell_execve (char *command, char **args, char **env)
{
int i, fd, sample_len;
- char sample[HASH_BANG_BUFSIZ];
+ char sample[HASH_BANG_BUFSIZ], *newfn;
size_t larray;
SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */
{
/* make sure this is set correctly for file_error/report_error */
last_command_exit_value = (i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC; /* XXX Posix.2 says that exit status is 126 */
+ newfn = printable_filename (command, 0);
+
if (file_isdir (command))
#if defined (EISDIR)
- internal_error ("%s: %s", command, strerror (EISDIR));
+ internal_error ("%s: %s", newfn, strerror (EISDIR));
#else
- internal_error (_("%s: is a directory"), command);
+ internal_error (_("%s: is a directory"), newfn);
#endif
else if (executable_file (command) == 0)
{
errno = i;
- file_error (command);
+ file_error (newfn);
}
/* errors not involving the path argument to execve. */
else if (i == E2BIG || i == ENOMEM)
{
errno = i;
- file_error (command);
+ file_error (newfn);
}
else
{
interp[ilen] = 'M';
interp[ilen + 1] = '\0';
}
- sys_error ("%s: %s: %s", command, interp, _("bad interpreter"));
+ sys_error ("%s: %s: %s", newfn, interp, _("bad interpreter"));
FREE (interp);
- return (EX_NOEXEC);
+ last_command_exit_value = EX_NOEXEC;
}
else
#endif
if (i == ENOENT)
{
errno = i;
- internal_error (_("%s: cannot execute: required file not found"), command);
+ internal_error (_("%s: cannot execute: required file not found"), newfn);
}
else
{
errno = i;
- file_error (command);
+ file_error (newfn);
}
}
+
+ if (newfn != command)
+ free (newfn);
+
return (last_command_exit_value);
}
#endif
if (check_binary_file (sample, sample_len))
{
- internal_error ("%s: %s: %s", command, _("cannot execute binary file"), strerror (i));
+ newfn = printable_filename (command, 0);
+ internal_error ("%s: %s: %s", newfn, _("cannot execute binary file"), strerror (i));
+ if (newfn != command)
+ free (newfn);
errno = i;
return (EX_BINARY_FILE);
}
void
redirection_error (REDIRECT *temp, int error, char *fn)
{
- char *filename, *allocname;
+ char *filename, *allocname, *newfn;
int oflags;
- allocname = 0;
+ allocname = newfn = 0;
+
if ((temp->rflags & REDIR_VARASSIGN) && error < 0)
filename = allocname = savestring (temp->redirector.filename->word);
else if ((temp->rflags & REDIR_VARASSIGN) == 0 && temp->redirector.dest < 0)
else
filename = allocname = itos (temp->redirectee.dest);
+ newfn = printable_filename (filename, 0);
+
switch (error)
{
case AMBIGUOUS_REDIRECT:
- internal_error ("%s: %s", filename, _("ambiguous redirect"));
+ internal_error ("%s: %s", newfn, _("ambiguous redirect"));
break;
case NOCLOBBER_REDIRECT:
- internal_error ("%s: %s", filename, _("cannot overwrite existing file"));
+ internal_error ("%s: %s", newfn, _("cannot overwrite existing file"));
break;
#if defined (RESTRICTED_SHELL)
case RESTRICTED_REDIRECT:
- internal_error ("%s: %s", filename, _("restricted: cannot redirect output"));
+ internal_error ("%s: %s", newfn, _("restricted: cannot redirect output"));
break;
#endif /* RESTRICTED_SHELL */
break;
case BADVAR_REDIRECT:
- internal_error ("%s: %s", filename, _("cannot assign fd to variable"));
+ internal_error ("%s: %s", newfn, _("cannot assign fd to variable"));
break;
default:
- internal_error ("%s: %s", filename, strerror (error));
+ internal_error ("%s: %s", newfn, strerror (error));
break;
}
+ if (newfn != filename)
+ FREE (newfn);
+
FREE (allocname);
}
if (fd < 0)
{
e = errno;
- file_error (filename);
+ file_error (printable_filename (filename, 0));
#if defined (JOB_CONTROL)
end_job_control (); /* just in case we were run as bash -i script */
#endif
#else
errno = EINVAL;
#endif
- file_error (filename);
+ file_error (printable_filename (filename, 0));
#if defined (JOB_CONTROL)
end_job_control (); /* just in case we were run as bash -i script */
#endif
if (sample_len < 0)
{
e = errno;
+ t = printable_filename (filename, 0);
if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode))
{
#if defined (EISDIR)
errno = EISDIR;
- file_error (filename);
+ file_error (t);
#else
- internal_error (_("%s: Is a directory"), filename);
+ internal_error (_("%s: Is a directory"), t);
#endif
}
else
{
errno = e;
- file_error (filename);
+ file_error (t);
}
#if defined (JOB_CONTROL)
end_job_control (); /* just in case we were run as bash -i script */
}
else if (sample_len > 0 && (check_binary_file (sample, sample_len)))
{
- internal_error ("%s: %s", filename, _("cannot execute binary file"));
+ t = printable_filename (filename, 0);
+ internal_error ("%s: %s", t, _("cannot execute binary file"));
#if defined (JOB_CONTROL)
end_job_control (); /* just in case we were run as bash -i script */
#endif
assign_hashcmd (SHELL_VAR *self, char *value, arrayind_t ind, char *key)
{
#if defined (RESTRICTED_SHELL)
- char *full_path;
+ char *full_path, *newfn;
if (restricted)
{
if (absolute_program (value))
{
- sh_restricted (value);
+ newfn = printable_filename (value, 0);
+ sh_restricted (newfn);
+ if (newfn != value)
+ free (newfn);
return (SHELL_VAR *)NULL;
}
/* If we are changing the hash table in a restricted shell, make sure the
full_path = find_user_command (value);
if (full_path == 0 || *full_path == 0 || executable_file (full_path) == 0)
{
- sh_notfound (value);
+ newfn = printable_filename (value, 0);
+ sh_notfound (newfn);
+ if (newfn != value)
+ free (newfn);
free (full_path);
return ((SHELL_VAR *)NULL);
}