aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/cibuild.yml2
-rw-r--r--.github/workflows/cifuzz.yml2
-rw-r--r--.github/workflows/codeql.yml6
-rw-r--r--bash-completion/column3
-rw-r--r--bash-completion/lslogins2
-rw-r--r--include/pt-gpt-partnames.h4
-rw-r--r--include/shells.h7
-rw-r--r--lib/Makemodule.am1
-rw-r--r--lib/exec_shell.c8
-rw-r--r--lib/meson.build1
-rw-r--r--lib/shells.c26
-rw-r--r--login-utils/login.1.adoc7
-rw-r--r--login-utils/login.c29
-rw-r--r--login-utils/su-common.c5
-rw-r--r--login-utils/sulogin.c9
-rw-r--r--misc-utils/cal.1.adoc3
-rw-r--r--sys-utils/flock.c6
-rw-r--r--sys-utils/setpriv.c6
-rw-r--r--term-utils/script.c5
-rw-r--r--term-utils/scriptlive.c5
-rw-r--r--text-utils/column.1.adoc2
-rw-r--r--text-utils/column.c4
-rw-r--r--text-utils/more.c6
-rwxr-xr-xtools/checkcompletion.sh51
-rwxr-xr-xtools/get-options.sh96
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 "$@"