Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi-apply-tool: first version #302

Merged
merged 20 commits into from
Jan 3, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions src/tools/patchmanager-tool
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/bin/sh
set -euCf
export POSIXLY_CORRECT=1
export LC_ALL=POSIX # For details see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_02

PM_CONF=/etc/patchmanager2.conf
PM_EXE=/usr/sbin/patchmanager

called="$(basename $0)"

function surplus() {
if [ -n "$*" ]; then
printf 'Too many parameters: %s\n' "$*" >&2
printf 'Call "%s -h" for a help page.\n' "$called" >&2
exit 1
fi
}

function usage() {
printf "Patchmanager tool is a front-end for the patchmanager executable.\n"
printf "USAGE: %s [-a|-A|-d|-D|-e|-h] [<patchlist>|-f <filepath>]\n" "$called"
printf "\t-a | --activate\t\t\tActivate / apply Patches from list or file (via -f).\n"
printf "\t-A | --activate-all\t\tActivate / apply all Patches formerly marked as active.\n"
printf "\t-d | --deactivate\t\tDeactivate / unapply Patches from list or file (via -f).\n"
printf "\t-D | --deactivate-all\t\tDeactivate / unapply all Patches.\n"
printf "\t-f | --file <filepath>\t\tUse <filepath> for the list of Patches.\n"
printf "\t-e | --export\t\t\tExport list of Patches marked as active either to STDout or (via -f) to a file.\n"
printf "\t-h | --help\t\t\tPrint this help.\n"
printf "* The <patchlist> must be comprised of (internal) names of Patches, separated by white-spaces, commas or both.\n"
printf "* For writing to <filepath>, it must not already exist and must be in a writable directory.\n"
printf "* For reading from <filepath>, it must be a plain text file containing the (internal) names of Patches, separated by white-spaces, commas or both.\n"
printf "* The -a, -A, -d, -D options must be executed as root to be effective, otherwise they output a script with the commands to execute.\n"
printf "* In order to use this tool as a drop-in replacement for calling the patchmanager executable, its options -u (synonym to -d), --unapply-all (synonym to -D) and --reset-system are also supported.\n"
printf "Exit codes:\n"
printf "\t0: All went fine.\n"
printf "\t1: Incorrect parameter(s) provided.\n"
printf "\t2: Error when interacting with the filesystem.\n"
printf "\t3: Malformed Patch name provided.\n"
}

patchlist=""
operation=""
case "$1" in
-a|--activate)
operation="-a"
shift
if [ "$1" = "-f" ] || [ "$1" = "--file" ]; then
shift
filepath="$1"
shift
surplus "$*"
if [ -r "$filepath" ] && [ -s "$filepath" ]; then
patchlist="$(tr ',[:blank:]' ' ' < "$filepath" | tr -s ' ')"
else
printf 'File "%s" does not exist, is empty or not readable!\n' "$filepath" >&2
exit 2
fi
else
patchlist="$(printf '%s' "$*" | tr ',[:blank:]' ' ' | tr -s ' ')"
fi
;;
-A|--activate-all)
operation="-a"
shift
surplus "$*"
else
if [ -r $PM_CONF ] && [ -s $PM_CONF ] && [ -f $PM_CONF ]; then
patchlist="$(grep '^applied=' $PM_CONF | cut -s -d "=" -f 2- | tr ',[:blank:]' ' ' | tr -s ' ')"
else
printf 'File "%s" does not exist, is empty, not readable or not a regular file!\n' $PM_CONF >&2
exit 2
fi
fi
;;
-d|-u|--deactivate)
operation="-u"
shift
shift
if [ "$1" = "-f" ] || [ "$1" = "--file" ]; then
shift
filepath="$1"
shift
surplus "$*"
if [ -r "$filepath" ] && [ -s "$filepath" ]; then
patchlist="$(tr ',[:blank:]' ' ' < "$filepath" | tr -s ' ')"
else
printf 'File "%s" does not exist, is empty or not readable!\n' "$filepath" >&2
exit 2
fi
else
patchlist="$(printf '%s' "$*" | tr ',[:blank:]' ' ' | tr -s ' ')"
fi
;;
-D|--deactivate-all|--unapply-all)
operation="--unapply-all"
shift
surplus "$*"
;;
--reset-system)
operation="--reset-system"
shift
surplus "$*"
;;
-e|--export)
shift
if [ "$1" = "-f" ] || [ "$1" = "--file" ]; then
shift
filepath="$1"
shift
surplus "$*"
if [ ! -w $(dirname "$filepath") ]; then
printf 'Cannot write to given directory: %s\n' "$filepath" >&2
exit 2
elif [ -e "$filepath" ]; then
printf 'File %s exists, will not overwrite.\n' "$filepath" >&2
exit 2
else
grep '^applied=' $PM_CONF | cut -s -d "=" -f 2- > "$filepath"
exit 0
fi
surplus "$*"
printf '%s\n' "$(grep '^applied=' $PM_CONF | cut -s -d "=" -f 2-)"
exit 0
;;
-h|--help)
usage
exit 0
;;
*)
printf 'Missing or wrong parameters!\n' >&2
printf 'Call "%s -h" for a help page.\n' "$called" >&2
exit 1
;;
esac

if [ -z "$patchlist" ] || printf %s "$patchlist" | grep -q '[$&();<>|]'; then
printf 'Patchlist is empty or contains illegal characters!\n' >&2
printf 'Call "%s -h" for a help page.\n' "$called" >&2
exit 1
fi

uid="$(id -u)"
if [ "$uid" != "0" ]; then
printf 'Not running as root, thus only listing the commands to execute.\nYou can redirect STDout into a file and execute it as root.\n\n' >&2
printf '#!/bin/sh\n\n'
fi
for p in $patchlist; do
if printf '%s' "$p" | grep -vq '^[[:alpha:]][[:alnum:]_.+-]*[[:alnum:]]$'; then
printf 'Malformed Patch name "%s" provided!' "$p" >&2
exit 3
elif [ "$uid" = "0" ]; then
$PM_EXE $operation $p
sleep 0.2
else
printf '%s %s %s; sleep 0.2\n' $PM_EXE $operation $p
fi
done