diff options
| -rw-r--r-- | .github/workflows/cibuild.yml | 2 | ||||
| -rw-r--r-- | .github/workflows/cifuzz.yml | 2 | ||||
| -rw-r--r-- | .github/workflows/codeql.yml | 6 | ||||
| -rw-r--r-- | bash-completion/column | 3 | ||||
| -rw-r--r-- | bash-completion/lslogins | 2 | ||||
| -rw-r--r-- | include/pt-gpt-partnames.h | 4 | ||||
| -rw-r--r-- | include/shells.h | 7 | ||||
| -rw-r--r-- | lib/Makemodule.am | 1 | ||||
| -rw-r--r-- | lib/exec_shell.c | 8 | ||||
| -rw-r--r-- | lib/meson.build | 1 | ||||
| -rw-r--r-- | lib/shells.c | 26 | ||||
| -rw-r--r-- | login-utils/login.1.adoc | 7 | ||||
| -rw-r--r-- | login-utils/login.c | 29 | ||||
| -rw-r--r-- | login-utils/su-common.c | 5 | ||||
| -rw-r--r-- | login-utils/sulogin.c | 9 | ||||
| -rw-r--r-- | misc-utils/cal.1.adoc | 3 | ||||
| -rw-r--r-- | sys-utils/flock.c | 6 | ||||
| -rw-r--r-- | sys-utils/setpriv.c | 6 | ||||
| -rw-r--r-- | term-utils/script.c | 5 | ||||
| -rw-r--r-- | term-utils/scriptlive.c | 5 | ||||
| -rw-r--r-- | text-utils/column.1.adoc | 2 | ||||
| -rw-r--r-- | text-utils/column.c | 4 | ||||
| -rw-r--r-- | text-utils/more.c | 6 | ||||
| -rwxr-xr-x | tools/checkcompletion.sh | 51 | ||||
| -rwxr-xr-x | tools/get-options.sh | 96 |
25 files changed, 243 insertions, 53 deletions
diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 38dfb266e7..9eeaba0495 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -135,7 +135,7 @@ jobs: image: ${{ matrix.image }} steps: - name: Repository checkout - uses: actions/checkout@v6 + uses: actions/checkout@v1 - name: Ubuntu setup run: .github/workflows/cibuild-setup-ubuntu.sh - name: Configure diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index e4777eb182..d2736cd0b2 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -52,7 +52,7 @@ jobs: dry-run: false sanitizer: ${{ matrix.sanitizer }} - name: Upload Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() && steps.build.outcome == 'success' with: name: ${{ matrix.sanitizer }}-${{ matrix.architecture }}-artifacts diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 93143ad679..3a43aeca40 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} queries: +security-extended,security-and-quality @@ -54,7 +54,7 @@ jobs: COMPILER: gcc - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/bash-completion/column b/bash-completion/column index 08d0772200..eea03f35d4 100644 --- a/bash-completion/column +++ b/bash-completion/column @@ -13,7 +13,7 @@ _column_module() COMPREPLY=( $(compgen -W "auto never always" -- $cur) ) return 0 ;; - '-s'|'--separator'|'-o'|'--output-separator'|'-n'|'--table-name'|'-O') + '-s'|'--separator'|'--input-separator'|'-o'|'--output-separator'|'-n'|'--table-name'|'-O') COMPREPLY=( $(compgen -W "string" -- $cur) ) return 0 ;; @@ -54,6 +54,7 @@ _column_module() --tree-parent --output-width --separator + --input-separator --output-separator --wrap-separator --fillrows diff --git a/bash-completion/lslogins b/bash-completion/lslogins index efad4bc0b4..dd1770855d 100644 --- a/bash-completion/lslogins +++ b/bash-completion/lslogins @@ -73,7 +73,7 @@ _lslogins_module() --btmp-file --shell --lastlog-file - --lastlog-file2 + --lastlog2-file --help --version" -- $cur) ) return 0 diff --git a/include/pt-gpt-partnames.h b/include/pt-gpt-partnames.h index b17a636299..cf12d2baa4 100644 --- a/include/pt-gpt-partnames.h +++ b/include/pt-gpt-partnames.h @@ -202,8 +202,8 @@ DEF_GUID("52637672-7900-11AA-AA11-00306543ECAC", N_("Apple Silicon recovery")), /* Solaris */ DEF_GUID("6A82CB45-1DD2-11B2-99A6-080020736631", N_("Solaris boot")), DEF_GUID("6A85CF4D-1DD2-11B2-99A6-080020736631", N_("Solaris root")), -/* same as Apple ZFS */ -DEF_GUID("6A898CC3-1DD2-11B2-99A6-080020736631", N_("Solaris /usr & Apple ZFS")), +/* ZFS pool member; originally Solaris /usr and briefly Apple ZFS */ +DEF_GUID("6A898CC3-1DD2-11B2-99A6-080020736631", N_("ZFS pool member")), DEF_GUID("6A87C46F-1DD2-11B2-99A6-080020736631", N_("Solaris swap")), DEF_GUID("6A8B642B-1DD2-11B2-99A6-080020736631", N_("Solaris backup")), DEF_GUID("6A8EF2E9-1DD2-11B2-99A6-080020736631", N_("Solaris /var")), diff --git a/include/shells.h b/include/shells.h index c770a13ba9..7f2d2469c2 100644 --- a/include/shells.h +++ b/include/shells.h @@ -4,7 +4,14 @@ #ifndef UTIL_LINUX_SHELLS_H #define UTIL_LINUX_SHELLS_H +#include <pwd.h> + +#define UL_SHELL_NOENV (1 << 0) +#define UL_SHELL_NOPWD (1 << 1) + extern void print_shells(FILE *out, const char *format); extern int is_known_shell(const char *shell_name); +const char *ul_default_shell(int flags, const struct passwd *pw); + #endif /* UTIL_LINUX_SHELLS_H */ diff --git a/lib/Makemodule.am b/lib/Makemodule.am index a9da577348..1d598faa28 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -49,6 +49,7 @@ libcommon_la_SOURCES = \ if LINUX libcommon_la_SOURCES += \ lib/linux_version.c \ + lib/shells.c \ lib/loopdev.c endif diff --git a/lib/exec_shell.c b/lib/exec_shell.c index ffe65f006e..8f10ea4df4 100644 --- a/lib/exec_shell.c +++ b/lib/exec_shell.c @@ -26,19 +26,15 @@ #include "xalloc.h" #include "exec_shell.h" - -#define DEFAULT_SHELL "/bin/sh" +#include "shells.h" void __attribute__((__noreturn__)) exec_shell(void) { - const char *shell = getenv("SHELL"); + const char *shell = ul_default_shell(0, NULL); char *shellc; const char *shell_basename; char *arg0; - if (!shell) - shell = DEFAULT_SHELL; - shellc = xstrdup(shell); shell_basename = basename(shellc); xasprintf(&arg0, "-%s", shell_basename); diff --git a/lib/meson.build b/lib/meson.build index 0f94a9b99b..cb35ecbd60 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -25,6 +25,7 @@ lib_common_sources = ''' randutils.c sha1.c sha256.c + shells.c signames.c strutils.c strv.c diff --git a/lib/shells.c b/lib/shells.c index 13f293c5e0..ef2aecd0f5 100644 --- a/lib/shells.c +++ b/lib/shells.c @@ -1,6 +1,11 @@ /* * SPDX-License-Identifier: GPL-2.0-or-later */ +#include <sys/types.h> +#include <pwd.h> +#include <stdlib.h> +#include <paths.h> +#include <unistd.h> #include <sys/syslog.h> #if defined (HAVE_LIBECONF) && defined (USE_VENDORDIR) #include <libeconf.h> @@ -116,3 +121,24 @@ extern int is_known_shell(const char *shell_name) #endif return ret; } + +const char *ul_default_shell(int flags, const struct passwd *pw) +{ + const char *shell = NULL; + + if (!(flags & UL_SHELL_NOENV)) { + shell = getenv("SHELL"); + if (shell && *shell) + return shell; + } + if (!(flags & UL_SHELL_NOPWD)) { + if (!pw) + pw = getpwuid(getuid()); + if (pw) + shell = pw->pw_shell; + if (shell && *shell) + return shell; + } + + return _PATH_BSHELL; +} diff --git a/login-utils/login.1.adoc b/login-utils/login.1.adoc index f28323ff9c..de89b03c6f 100644 --- a/login-utils/login.1.adoc +++ b/login-utils/login.1.adoc @@ -18,7 +18,7 @@ login - begin session on the system == SYNOPSIS -*login* [*-p*] [*-h* _host_] [*-H*] [*-f* _username_|_username_] +*login* [*-p*] [*-s* _shell_] [*-h* _host_] [*-H*] [*-f* _username_|_username_] == DESCRIPTION @@ -36,7 +36,7 @@ Other environment variables are preserved if the *-p* option is given or if *LOG The environment variables defined by PAM are always preserved. -Then the user's shell is started. If no shell is specified for the user in _/etc/passwd_, then _/bin/sh_ is used. If the specified shell contains a space, it is treated as a shell script. If there is no home directory specified in _/etc/passwd_, then _/_ is used, followed by _.hushlogin_ check as described below. +Then the user's shell is started. If no shell is specified for the user with *-s* or in _/etc/passwd_, then _/bin/sh_ is used. If the specified shell contains a space, it is treated as a shell script. If there is no home directory specified in _/etc/passwd_, then _/_ is used, followed by _.hushlogin_ check as described below. If the file _.hushlogin_ exists, then a "quiet" login is performed. This disables the checking of mail and the printing of the last login time and message of the day. Otherwise, if _/var/log/lastlog_ exists, the last login time is printed, and the current login is recorded. @@ -56,6 +56,9 @@ Note that the *-h* option has an impact on the *PAM service* *name*. The standar *-H*:: Used by other servers (for example, *telnetd*(8)) to tell *login* that printing the hostname should be suppressed in the login: prompt. See also *LOGIN_PLAIN_PROMPT* below. +*-s*, *--shell* _shell_:: +Specify a _shell_, other than the one defined in _/etc/passwd_, to log in to. + include::man-common/help-version.adoc[] == CONFIG FILE ITEMS diff --git a/login-utils/login.c b/login-utils/login.c index 402e178201..3cd1d528be 100644 --- a/login-utils/login.c +++ b/login-utils/login.c @@ -111,6 +111,8 @@ struct login_context { const char *tty_number; /* end of the tty_path */ mode_t tty_mode; /* chmod() mode */ + char *shell_arg; /* command line argument defining the login shell */ + const char *username; /* points to PAM, pwd or cmd_username */ char *cmd_username; /* username specified on command line */ @@ -1282,17 +1284,18 @@ static void init_remote_info(struct login_context *cxt, char *remotehost) static void __attribute__((__noreturn__)) usage(void) { fputs(USAGE_HEADER, stdout); - printf(_(" %s [-p] [-h <host>] [-H] [[-f] <username>]\n"), program_invocation_short_name); + printf(_(" %s [-p] [-s <shell>] [-h <host>] [-H] [[-f] <username>]\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, stdout); fputs(_("Begin a session on the system.\n"), stdout); fputs(USAGE_OPTIONS, stdout); - puts(_(" -p do not destroy the environment")); - puts(_(" -f skip a login authentication")); - puts(_(" -h <host> hostname to be used for utmp logging")); - puts(_(" -H suppress hostname in the login prompt")); - printf(" --help %s\n", USAGE_OPTSTR_HELP); - printf(" -V, --version %s\n", USAGE_OPTSTR_VERSION); + puts(_(" -p do not destroy the environment")); + puts(_(" -f skip a login authentication")); + puts(_(" -h <host> hostname to be used for utmp logging")); + puts(_(" -H suppress hostname in the login prompt")); + puts(_(" -s, --shell <path> define the shell to log in to")); + printf(" --help %s\n", USAGE_OPTSTR_HELP); + printf(" -V, --version %s\n", USAGE_OPTSTR_VERSION); printf(USAGE_MAN_TAIL("login(1)")); exit(EXIT_SUCCESS); } @@ -1328,6 +1331,7 @@ static void initialize(int argc, char **argv, struct login_context *cxt) /* the only two longopts to satisfy UL standards */ enum { HELP_OPTION = CHAR_MAX + 1 }; const struct option longopts[] = { + {"shell", required_argument, NULL, 's'}, {"help", no_argument, NULL, HELP_OPTION}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} @@ -1356,7 +1360,7 @@ static void initialize(int argc, char **argv, struct login_context *cxt) load_credentials(cxt); - while ((c = getopt_long(argc, argv, "fHh:pV", longopts, NULL)) != -1) + while ((c = getopt_long(argc, argv, "fHh:ps:V", longopts, NULL)) != -1) switch (c) { case 'f': cxt->noauth = 1; @@ -1379,6 +1383,10 @@ static void initialize(int argc, char **argv, struct login_context *cxt) cxt->keep_env = 1; break; + case 's': + cxt->shell_arg = optarg; + break; + case 'V': print_version(EXIT_SUCCESS); case HELP_OPTION: @@ -1580,8 +1588,11 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0') + if (cxt.shell_arg && *cxt.shell_arg != '\0') { + pwd->pw_shell = cxt.shell_arg; + } else if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0') { pwd->pw_shell = _PATH_BSHELL; + } init_environ(&cxt); /* init $HOME, $TERM ... */ diff --git a/login-utils/su-common.c b/login-utils/su-common.c index 4d54eab31b..c6232ce7ac 100644 --- a/login-utils/su-common.c +++ b/login-utils/su-common.c @@ -105,9 +105,6 @@ UL_DEBUG_DEFINE_MASKNAMES(su) = UL_DEBUG_EMPTY_MASKNAMES; #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) -/* The shell to run if none is given in the user's passwd entry. */ -#define DEFAULT_SHELL "/bin/sh" - /* The user to become if none is specified. */ #define DEFAULT_USER "root" @@ -1167,7 +1164,7 @@ int su_main(int argc, char **argv, int mode) su->old_user = xgetlogin(); if (!su->pwd->pw_shell || !*su->pwd->pw_shell) - su->pwd->pw_shell = DEFAULT_SHELL; + su->pwd->pw_shell = _PATH_BSHELL; if (use_supp && !use_gid) su->pwd->pw_gid = groups[0]; diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c index eb4609db65..c546cc7c16 100644 --- a/login-utils/sulogin.c +++ b/login-utils/sulogin.c @@ -34,6 +34,7 @@ #include <fcntl.h> #include <signal.h> #include <pwd.h> +#include <paths.h> #include <shadow.h> #include <termios.h> #include <errno.h> @@ -892,7 +893,7 @@ static void sushell(struct passwd *pwd, struct console *con) if (pwd->pw_shell[0]) su_shell = pwd->pw_shell; else - su_shell = "/bin/sh"; + su_shell = _PATH_BSHELL; } if ((p = strrchr(su_shell, '/')) == NULL) p = su_shell; @@ -941,9 +942,9 @@ static void sushell(struct passwd *pwd, struct console *con) execl(su_shell, shell, (char *)NULL); warn(_("failed to execute %s"), su_shell); - xsetenv("SHELL", "/bin/sh", 1); - execl("/bin/sh", profile ? "-sh" : "sh", (char *)NULL); - warn(_("failed to execute %s"), "/bin/sh"); + xsetenv("SHELL", _PATH_BSHELL, 1); + execl(_PATH_BSHELL, profile ? "-sh" : "sh", (char *)NULL); + warn(_("failed to execute %s"), _PATH_BSHELL); } #ifdef HAVE_LIBSELINUX diff --git a/misc-utils/cal.1.adoc b/misc-utils/cal.1.adoc index b4096ed15f..a464d56b4d 100644 --- a/misc-utils/cal.1.adoc +++ b/misc-utils/cal.1.adoc @@ -120,6 +120,9 @@ If a _number_ is specified, the requested week in the desired or current year will be printed and its number highlighted. The _number_ may be ignored if _month_ is also specified. + +If the _number_ is specified but the current day is unspecified on the command +line, then the current day is not highlighted. ++ See the *NOTES* section for more details. *--color*[**=**_when_]:: diff --git a/sys-utils/flock.c b/sys-utils/flock.c index 4cc0610e50..642e026c85 100644 --- a/sys-utils/flock.c +++ b/sys-utils/flock.c @@ -47,6 +47,7 @@ #include "closestream.h" #include "monotonic.h" #include "timer.h" +#include "shells.h" #ifndef F_OFD_GETLK #define F_OFD_GETLK 36 @@ -207,6 +208,7 @@ int main(int argc, char *argv[]) int conflict_exit_code = 1; char **cmd_argv = NULL, *sh_c_argv[4]; const char *filename = NULL; + enum { OPT_VERBOSE = CHAR_MAX + 1, OPT_FCNTL, @@ -327,9 +329,7 @@ int main(int argc, char *argv[]) _("%s requires exactly one command argument"), argv[optind + 1]); cmd_argv = sh_c_argv; - cmd_argv[0] = getenv("SHELL"); - if (!cmd_argv[0] || !*cmd_argv[0]) - cmd_argv[0] = _PATH_BSHELL; + cmd_argv[0] = (char *)ul_default_shell(0, NULL); cmd_argv[1] = "-c"; cmd_argv[2] = argv[optind + 2]; cmd_argv[3] = NULL; diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c index c218be8e5e..505d1ee5b4 100644 --- a/sys-utils/setpriv.c +++ b/sys-utils/setpriv.c @@ -30,6 +30,7 @@ #include <sys/prctl.h> #include <sys/types.h> #include <unistd.h> +#include <paths.h> #include "all-io.h" #include "c.h" @@ -56,9 +57,6 @@ #define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */ -/* The shell to set SHELL env.variable if none is given in the user's passwd entry. */ -#define DEFAULT_SHELL "/bin/sh" - static gid_t get_group(const char *s, const char *err); enum cap_type { @@ -741,7 +739,7 @@ static void do_reset_environ(struct passwd *pw) if (pw->pw_shell && *pw->pw_shell) xsetenv("SHELL", pw->pw_shell, 1); else - xsetenv("SHELL", DEFAULT_SHELL, 1); + xsetenv("SHELL", _PATH_BSHELL, 1); xsetenv("HOME", pw->pw_dir, 1); xsetenv("USER", pw->pw_name, 1); diff --git a/term-utils/script.c b/term-utils/script.c index ff7f4409f6..4e302347f2 100644 --- a/term-utils/script.c +++ b/term-utils/script.c @@ -70,6 +70,7 @@ #include "signames.h" #include "pty-session.h" #include "debug.h" +#include "shells.h" static UL_DEBUG_DEFINE_MASK(script); UL_DEBUG_DEFINE_MASKNAMES(script) = UL_DEBUG_EMPTY_MASKNAMES; @@ -966,9 +967,7 @@ int main(int argc, char **argv) log_associate(&ctl, &ctl.in, timingfile, format); } - shell = getenv("SHELL"); - if (!shell) - shell = _PATH_BSHELL; + shell = ul_default_shell(0, NULL); ctl.pty = ul_new_pty(ctl.isterm); if (!ctl.pty) diff --git a/term-utils/scriptlive.c b/term-utils/scriptlive.c index e4a3434ed5..6ac685506f 100644 --- a/term-utils/scriptlive.c +++ b/term-utils/scriptlive.c @@ -38,6 +38,7 @@ #include "pty-session.h" #include "script-playutils.h" #include "monotonic.h" +#include "shells.h" #define SCRIPT_MIN_DELAY 0.0001 /* from original scriptreplay.pl */ @@ -281,9 +282,7 @@ main(int argc, char *argv[]) replay_set_delay_max(ss.setup, &maxdelay); replay_set_delay_min(ss.setup, &mindelay); - shell = getenv("SHELL"); - if (shell == NULL) - shell = _PATH_BSHELL; + shell = ul_default_shell(0, NULL); fprintf(stdout, _(">>> scriptlive: Starting your typescript execution by %s.\n"), command ? command : shell); diff --git a/text-utils/column.1.adoc b/text-utils/column.1.adoc index 69f2f794ff..e1e5a1c6f4 100644 --- a/text-utils/column.1.adoc +++ b/text-utils/column.1.adoc @@ -99,7 +99,7 @@ Omit printing the header. This option allows having user-supplied column names o *-o, --output-separator* _string_:: Column delimiter for table output (default is two spaces). -*-s, --separator* _separators_:: +*-s, --input-separator, --separator* _separators_:: Possible input-item delimiters (default is whitespace). *-S, --use-spaces* _number_:: diff --git a/text-utils/column.c b/text-utils/column.c index 7a37cb886c..5a9e9a03b6 100644 --- a/text-utils/column.c +++ b/text-utils/column.c @@ -1021,7 +1021,8 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out); fputs(_(" -o, --output-separator <string> columns separator for table output\n" " (default is two spaces)\n"), out); - fputs(_(" -s, --separator <string> possible table delimiters\n"), out); + fputs(_(" -s, --input-separator, --separator <string>\n" + " possible table delimiters\n"), out); fputs(_(" -x, --fillrows fill rows before columns\n"), out); fputs(_(" -S, --use-spaces <number> minimal whitespaces between columns (no tabs)\n"), out); @@ -1059,6 +1060,7 @@ int main(int argc, char **argv) { "color", optional_argument, NULL, OPT_COLOR }, { "fillrows", no_argument, NULL, 'x' }, { "help", no_argument, NULL, 'h' }, + { "input-separator", required_argument, NULL, 's' }, /* alias for --separator */ { "json", no_argument, NULL, 'J' }, { "keep-empty-lines", no_argument, NULL, 'L' }, { "output-separator", required_argument, NULL, 'o' }, diff --git a/text-utils/more.c b/text-utils/more.c index 4980aef4ca..bc04064cf1 100644 --- a/text-utils/more.c +++ b/text-utils/more.c @@ -89,6 +89,7 @@ #include "widechar.h" #include "closestream.h" #include "env.h" +#include "shells.h" #ifdef TEST_PROGRAM # define NON_INTERACTIVE_MORE 1 @@ -174,7 +175,7 @@ struct more_control { int next_jump; /* number of lines to skip ahead */ char **file_names; /* The list of file names */ int num_files; /* Number of files left to process */ - char *shell; /* name of the shell to use */ + const char *shell; /* name of the shell to use */ int sigfd; /* signalfd() file descriptor */ sigset_t sigset; /* signal operations */ char *line_buf; /* line buffer */ @@ -2110,8 +2111,7 @@ static void initterm(struct more_control *ctl) if ((ctl->backspace_ch = tigetstr(TERM_BACKSPACE)) == NULL) ctl->backspace_ch = BACKSPACE; - if ((ctl->shell = getenv("SHELL")) == NULL) - ctl->shell = _PATH_BSHELL; + ctl->shell = ul_default_shell(0, NULL); } int main(int argc, char **argv) diff --git a/tools/checkcompletion.sh b/tools/checkcompletion.sh index 58e1f7a08c..c92354a2c4 100755 --- a/tools/checkcompletion.sh +++ b/tools/checkcompletion.sh @@ -6,8 +6,10 @@ # 2. All bash-completion files are registered in bash-completion/Makemodule.am # 3. All bash-completion files are registered in meson.build # 4. All bash-completion files correspond to actual programs +# 5. All bash-completion files handle all available long options in each program # # Copyright (C) 2025 Karel Zak <kzak@redhat.com> +# Copyright (C) 2025 Christian Goeschel Ndjomouo <cgoesc2@wgu.edu> # set -e @@ -43,7 +45,11 @@ exclude_programs="nologin|agetty|login|sulogin|switch_root|vipw|line|kill" # These are handled via install-data-hook-bashcomp-* rules # - runuser: symlinked to su completion # - lastb: symlinked to last completion -special_handling="runuser|lastb" +special_handling="runuser|lastb" + +# Certain completions have an unusual algorithm that is distinct from the pattern used +# in the majority of completion files, we skip these for now. +unusual_completions="pipesz" top_srcdir=${1:-.} [ -d "${top_srcdir}" ] || die "directory '${top_srcdir}' not found" @@ -97,6 +103,44 @@ extract_meson_registered() { fi } +# Check for the bash-completion file integrity, i.e. all long options are completed +# Argument(s): program_name +check_completion_file_integrity() { + local prog="$1" + + prog_long_opts="$( TOP_SRCDIR="${top_srcdir}" "${top_srcdir}"/tools/get-options.sh "$prog" \ + | sed -e 's/^$//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + if [[ "$?" != "0" || -z "$prog_long_opts" ]]; then + echo "Failed to get long options for $prog" + return 1 + fi + + # tools/get-options.sh prints 'ENOTSUP' when it receives the name of an + # unsupported program. See comments for the 'unsupported_programs' variable + # in tools/get-options.sh for more details. + # + # We do not treat this case as an error, thereby we simply return 0 to the + # caller and skip the comparison. + if [ "$prog_long_opts" == "ENOTSUP" ]; then + return 0 + fi + + comp_opts="$( cat "${completion_dir}/${prog}" \ + | grep -o -P '[[:space:]]*--(?![^[:alnum:]])[A-Za-z-.0-9_]*' \ + | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \ + | sort \ + | uniq )" + + res="$( comm -23 <(echo "${prog_long_opts}") <(echo "${comp_opts}") )" + if [ -n "$res" ]; then + printf "%s\n%s\n" "${prog}:" "$res" + return 1 + fi + + return 0 +} + # Get programs that should have completions programs=$(extract_programs | grep -v -w -E "(${exclude_programs}|${special_handling})") @@ -148,6 +192,11 @@ if [ -n "$meson_unregistered" ]; then errors=$((errors + 1)) fi +for f in $files; do + [[ "$f" =~ $unusual_completions ]] && continue + check_completion_file_integrity "$f" || errors=$((errors + 1)) +done + if [ $errors -eq 0 ]; then echo "All bash-completion files are consistent." exit 0 diff --git a/tools/get-options.sh b/tools/get-options.sh new file mode 100755 index 0000000000..49d798ce7b --- /dev/null +++ b/tools/get-options.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +# This file is part of util-linux. +# +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This file 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. +# +# Copyright (C) 2025 Christian Goeschel Ndjomouo <cgoesc2@wgu.edu> +TOP_SRCDIR=${TOP_SRCDIR:-../} + +# Directories that contain relevant source files for util-linux programs. +src_file_paths="$(grep -rE --include="*.c" --exclude="*test_*" \ + --exclude-dir="lib*" \ + --exclude-dir="po*" \ + --exclude-dir="tests" \ + --exclude-dir="tools" \ + --exclude-dir="bash-completion" \ + --exclude-dir=".[a-z]*" \ + --exclude-dir="man-common" \ + --exclude-dir="Documentation" \ + --exclude-dir="build*" \ + -l "getopt_long(_only)?\s*\(" \ + )" + +# We skip these programs because they do not make use of 'struct option longopts[]' +# which is passed to getopt(3) for command line argument parsing. +unsupported_programs='blockdev|fsck|mkfs\.cramfs|pg|renice|whereis' + +# In general a program's source file name will be '<program_name>.c', however +# some tools have differing file names. To handle these special cases we build +# a hash table with the program name as the key and the actual source file name +# as the value, the latter will ultimately be passed to find_prog_src(). +typeset -A canonical_src_prefix +canonical_src_prefix=( \ + [su]="su-common" +) + +function find_prog_src() { + local prog + prog="$1" + + for p in ${src_file_paths}; do + if [[ "${p##*/}" =~ ^"${prog}".c$ ]]; then + echo "${TOP_SRCDIR}/${p}" + break + fi + done +} + +function extract_long_opts() { + local src_path + src_path="$1" + + awk -F ',' 'BEGIN { x = 0 }; \ + /struct[[:space:]]*option[[:space:]]*.*[[:space:]]*\[\][[:space:]]*=[[:space:]]*(\{)?/ { x = 1 } \ + x && ! /.*\/\*.*(deprecated|COMPLETION:no).*\*\/.*/ { print $1 } \ + /\};/ { x = 0 }' "${src_path}" \ + | grep -Eo '".*"' \ + | tr -d '"' \ + | sort \ + | awk '{ printf "--%s\n", $0 }' \ + | grep -v '^--_.*$' +} + +function main() { + local progname + progname="$1" + + if [[ "$progname" =~ $unsupported_programs ]]; then + echo "ENOTSUP" + return 0 + fi + + # Handle special programs that have unusual source file names + if [ -n "${canonical_src_prefix[$progname]+exists}" ]; then + progname="${canonical_src_prefix[$progname]}" + fi + + src_path="$(find_prog_src "$progname")" + if [ -z "$src_path" ]; then + return 1 + fi + + extract_long_opts "$src_path" + + return 0 +} + +main "$@" |
