Based on an idea from Dan Colascione: https://sourceware.org/ml/cygwin/2010-11/msg00022.html
Usage: add this to your shell startup: EXECIGNORE='*.so:*.so.*' to cause TAB completion to ignore shared library files when crawling the file system to look for executables. Cygwin has been using a variation of this patch since December 2010, in part because on that platform, shared libraries (*.dll) MUST have the executable bit set, and MUST live in /usr/bin alongside normal executables, so they can easily get in the way of TAB completion. At least on Linux, *.so and *.so.* files tend to live in a separate location (/usr/lib{,64}/) from normal executables. But even on Linux, attempts to directly execute a .so file (other than the rare exception like /usr/lib/libc.so) usually results in spectacular failure, so this is useful to more than just Cygwin. Signed-off-by: Eric Blake <ebl...@redhat.com> --- doc/bash.1 | 8 ++++++++ doc/bashref.texi | 11 ++++++++++- findcmd.c | 43 +++++++++++++++++++++++++++++++++++++------ findcmd.h | 1 + pathexp.h | 4 ++-- variables.c | 9 +++++++++ variables.h | 1 + 7 files changed, 68 insertions(+), 9 deletions(-) diff --git a/doc/bash.1 b/doc/bash.1 index f8deeb8..fbbac23 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -1990,6 +1990,14 @@ Similar to .BR BASH_ENV ; used when the shell is invoked in POSIX mode. .TP +.B EXECIGNORE +A colon-separated list of extended glob patterns (see \fBPattern +Matching\fP). Files with full paths matching one of these patterns are +not considered executable for the purposes of completion and PATH +searching, but the \fB[\fP, \fB[[\fP, and \fBtest\fP builtins are not +affected. Use this variable to ignore shared library files that must +have the executable bit set, but which are not actually executable. +.TP .B FCEDIT The default editor for the .B fc diff --git a/doc/bashref.texi b/doc/bashref.texi index 397e1d6..dedc41b 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -14,7 +14,7 @@ This is Edition @value{EDITION}, last updated @value{UPDATED}, of @cite{The GNU Bash Reference Manual}, for @code{Bash}, Version @value{VERSION}. -Copyright @copyright{} 1988--2014 Free Software Foundation, Inc. +Copyright @copyright{} 1988--2015 Free Software Foundation, Inc. @quotation Permission is granted to copy, distribute and/or modify this document @@ -5653,2 +5653,2 @@ Similar to @code{BASH_ENV}; used when the shell is invoked in The numeric effective user id of the current user. This variable is readonly. +@item EXECIGNORE +A colon-separated list of extended glob patterns (@pxref{Pattern +Matching}). Files with full paths matching one of these patterns are +not considered executable for the purposes of completion and PATH +searching, but the @code{[}, @code{[[}, and @code{test} builtins are +not affected. Use this variable to ignore shared library files that +must have the executable bit set, but which are not actually +executable. + @item FCEDIT The editor used as a default by the @option{-e} option to the @code{fc} builtin command. diff --git a/findcmd.c b/findcmd.c index e667691..bfd8685 100644 --- a/findcmd.c +++ b/findcmd.c @@ -48,6 +48,8 @@ extern int errno; #endif +#include <glob/strmatch.h> + extern int posixly_correct; extern int last_command_exit_value; @@ -77,6 +79,35 @@ int check_hashed_filenames = CHECKHASH_DEFAULT; containing the file of interest. */ int dot_found_in_search = 0; +static struct ignorevar execignore = +{ + "EXECIGNORE", + NULL, + 0, + NULL, + NULL +}; + +void +setup_exec_ignore (char *varname) +{ + setup_ignore_patterns (&execignore); +} + +/* Return non-zero if we should pretend the file is not executable, + * regardless of what the file system tells us. */ +static int +is_on_exec_blacklist (const char *name) +{ + struct ign *p; + int flags = FNM_EXTMATCH | FNM_CASEFOLD; + + for (p = execignore.ignores; p && p->val; p++) + if (strmatch (p->val, (char *)name, flags) != FNM_NOMATCH) + return 1; + return 0; +} + /* Return some flags based on information about this file. The EXISTS bit is non-zero if the file is found. The EXECABLE bit is non-zero the file is executble. @@ -104,7 +135,7 @@ file_status (name) file access mechanisms into account. eaccess uses the effective user and group IDs, not the real ones. We could use sh_eaccess, but we don't want any special treatment for /dev/fd. */ - if (eaccess (name, X_OK) == 0) + if (!is_on_exec_blacklist (name) && eaccess (name, X_OK) == 0) r |= FS_EXECABLE; if (eaccess (name, R_OK) == 0) r |= FS_READABLE; @@ -114,7 +145,7 @@ file_status (name) /* We have to use access(2) to determine access because AFS does not support Unix file system semantics. This may produce wrong answers for non-AFS files when ruid != euid. I hate AFS. */ - if (access (name, X_OK) == 0) + if (!is_on_exec_blacklist (name) && access (name, X_OK) == 0) r |= FS_EXECABLE; if (access (name, R_OK) == 0) r |= FS_READABLE; @@ -131,7 +162,7 @@ file_status (name) if (current_user.euid == (uid_t)0) { r |= FS_READABLE; - if (finfo.st_mode & S_IXUGO) + if (!is_on_exec_blacklist (name) && (finfo.st_mode & S_IXUGO)) r |= FS_EXECABLE; return r; } @@ -139,7 +170,7 @@ file_status (name) /* If we are the owner of the file, the owner bits apply. */ if (current_user.euid == finfo.st_uid) { - if (finfo.st_mode & S_IXUSR) + if (!is_on_exec_blacklist (name) && (finfo.st_mode & S_IXUSR)) r |= FS_EXECABLE; if (finfo.st_mode & S_IRUSR) r |= FS_READABLE; @@ -148,7 +179,7 @@ file_status (name) /* If we are in the owning group, the group permissions apply. */ else if (group_member (finfo.st_gid)) { - if (finfo.st_mode & S_IXGRP) + if (!is_on_exec_blacklist (name) && (finfo.st_mode & S_IXGRP)) r |= FS_EXECABLE; if (finfo.st_mode & S_IRGRP) r |= FS_READABLE; @@ -157,7 +188,7 @@ file_status (name) /* Else we check whether `others' have permission to execute the file */ else { - if (finfo.st_mode & S_IXOTH) + if (!is_on_exec_blacklist (name) && (finfo.st_mode & S_IXOTH)) r |= FS_EXECABLE; if (finfo.st_mode & S_IROTH) r |= FS_READABLE; diff --git a/findcmd.h b/findcmd.h index 52ad1d0..116d2a7 100644 --- a/findcmd.h +++ b/findcmd.h @@ -31,5 +31,6 @@ extern char *find_user_command __P((const char *)); extern char *find_path_file __P((const char *)); extern char *search_for_command __P((const char *, int)); extern char *user_command_matches __P((const char *, int, int)); +extern void setup_exec_ignore __P((char *)); #endif /* _FINDCMD_H_ */ diff --git a/pathexp.h b/pathexp.h index 601914c..741d32a 100644 --- a/pathexp.h +++ b/pathexp.h @@ -71,7 +71,7 @@ extern char *quote_globbing_chars __P((const char *)); extern char **shell_glob_filename __P((const char *)); /* Filename completion ignore. Used to implement the "fignore" facility of - tcsh and GLOBIGNORE (like ksh-93 FIGNORE). + tcsh, GLOBIGNORE (like ksh-93 FIGNORE), and EXECIGNORE. It is passed a NULL-terminated array of (char *)'s that must be free()'d if they are deleted. The first element (names[0]) is the @@ -87,7 +87,7 @@ struct ign { typedef int sh_iv_item_func_t __P((struct ign *)); struct ignorevar { - char *varname; /* FIGNORE or GLOBIGNORE */ + char *varname; /* FIGNORE, GLOBIGNORE, or EXECIGNORE */ struct ign *ignores; /* Store the ignore strings here */ int num_ignores; /* How many are there? */ char *last_ignoreval; /* Last value of variable - cached for speed */ diff --git a/variables.c b/variables.c index c992410..1981c6a 100644 --- a/variables.c +++ b/variables.c @@ -4732,6 +4732,8 @@ static struct name_and_function special_vars[] = { { "COMP_WORDBREAKS", sv_comp_wordbreaks }, #endif + { "EXECIGNORE", sv_execignore }, + { "FUNCNEST", sv_funcnest }, { "GLOBIGNORE", sv_globignore }, @@ -4930,6 +4932,13 @@ sv_globignore (name) setup_glob_ignore (name); } +/* What to do when EXECIGNORE changes. */ +void +sv_execignore (char *name) +{ + setup_exec_ignore (name); +} + #if defined (READLINE) void sv_comp_wordbreaks (name) diff --git a/variables.h b/variables.h index 4574ca8..f4f5042 100644 --- a/variables.h +++ b/variables.h @@ -375,6 +375,7 @@ extern void sv_ifs __P((char *)); extern void sv_path __P((char *)); extern void sv_mail __P((char *)); extern void sv_funcnest __P((char *)); +extern void sv_execignore __P((char *)); extern void sv_globignore __P((char *)); extern void sv_ignoreeof __P((char *)); extern void sv_strict_posix __P((char *)); -- 2.1.0