aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarel Zak <kzak@redhat.com>2025-12-03 11:14:13 +0100
committerKarel Zak <kzak@redhat.com>2025-12-03 11:14:13 +0100
commit9bfd9d9262e9b42eb56f601945acbbe72a3c0220 (patch)
tree2789c320e6b51e5c33558f492086c0fd0b3f017e
parent764c97800eadd1a49225c566771fec6905115acb (diff)
parentc5c8326a8a19b7560b23fea386804c9f1325295b (diff)
downloadutil-linux-9bfd9d9262e9b42eb56f601945acbbe72a3c0220.tar.gz
Merge branch 'feat/check_bash_comp_integrity' of https://github.com/cgoesche/util-linux-fork
* 'feat/check_bash_comp_integrity' of https://github.com/cgoesche/util-linux-fork: bash-completion: (lslogins) fix typo in long option tools: (checkcompletion.sh) test the integrity of long options completion tools: new helper for extraction of program long options from source files
-rw-r--r--bash-completion/lslogins2
-rwxr-xr-xtools/checkcompletion.sh51
-rwxr-xr-xtools/get-options.sh96
3 files changed, 147 insertions, 2 deletions
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/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 "$@"