Files
RescueBRU/customcd/files/root/rescuebru/functions.sh
2026-03-03 19:53:38 -05:00

680 lines
22 KiB
Bash

# functions.sh
# Rescue CD Backup & Restore Utility common functions
# version 7.2.5
# Rod Wright 1/19/2024
# Refer to CHANGELOG file for details.
# -----------------------------------------------------------------------------
function monitor() {
# Run a command while monitoring for user input
# Takes two arguments. The first is a string containing
# the complete command to be executed. The second is the word
# "pausable" if you would like to be able to pause or abort
# the operation.
unset pausekey
unset pausable
cmd_string=$1
if [ "$2" = "pausable" ]; then
pausable=1
dialog --sleep 4 --begin 20 5 --no-shadow \
--infobox "You may press any letter key at any time to pause the operation." 3 70
fi
eval "$cmd_string &"
cmd_pid=$!
let watching=1
let alive=1
while [[ $watching && $alive ]]; do
if ! `kill -0 $cmd_pid 2>/dev/null` ; then
unset alive
wait $cmd_pid
cmd_exit_stat=$?
fi
if [ "$pausable" = "1" ]; then read -s -n1 -t1 pausekey <&1; fi
if [ $pausekey ]; then
unset watching
kill -s STOP $cmd_pid
if approved "Operation paused. Do you want to continue?" "y"; then
dialog --sleep 4 --begin 20 5 --no-shadow \
--infobox "You may press any letter key at any time to pause the operation." 3 70
kill -s CONT $cmd_pid
let watching=1
else
kill -s CONT $cmd_pid
kill -s TERM $cmd_pid
wait $cmd_pid 2>/dev/null
return 1
fi
fi
done
return $cmd_exit_stat
}
function progreport() {
# Creates an infobox to report cumulative progress.
# Takes one argument, the text to print in box.
# If the text supplied is "" then a new report is started.
if [ -n "$1" ]; then
echo "$1">>$progfile
dialog --infobox "`tail -20 $progfile`" 24 80
else
echo "">$progfile
fi
}
function flush_input() {
# Flush spurious user input. Call this before a 'read' when you
# need to get user data. Do NOT call this if you're just trying to
# detect keystrokes.
read -t .1 -n 10000 discard
}
function approved() {
# Ask a question requiring a y or n for an answer
# takes two arguments.
# First is the text of the prompt.
# Second is the default result, "y" or "n".
# Returns 0 if answer is yes, 1 if no.
if [[ $2 = "n" ]]; then
flush_input
dialog --aspect 80 --defaultno --yesno "$1" 0 0
elif [[ $2 = "y" ]]; then
flush_input
dialog --aspect 80 --yesno "$1" 0 0
fi
return $?
}
function validate_text() {
# Ensures a string contains only allowed characters.
# Takes two arguments, the first is "name" or "path",
# the second is the string to be validated
# returns 0 if valid, 1 if not
unset chars_only
tst_strg=$2
case $1 in
"name" )
chars_regex="^[\.0-9_a-zA-Z\-]*$"
;;
"path" )
chars_regex="^[\.\/0-9_a-zA-Z\-]*$"
;;
* )
return 1
;;
esac
echo $tst_strg|grep -q -e $chars_regex
return $?
}
function operation_log() {
# Turns the operation log on or off.
# Takes one argument, the string "on" or "off".
if [ "$1" = "on" ]; then
local opfn=`echo ${operation// /_}`
OPERATION_LOG="$LOG_PATH/${opfn}_`date +%Y-%m-%d_%H%M%S`.log"
elif [ "$1" = "off" ]; then
OPERATION_LOG="/dev/null"
else
pause "You must specify \"on\" or \"off\"."
return 1
fi
}
function log_session() {
# Appends supplied text to the session log file. Argument is text to log
if [ "$LOGGING_ENABLED" -eq 1 ]; then
echo "[`date +%T`]: $1">>$SESSION_LOG
fi
}
function log_operation() {
# Appends supplied text to the operation log file. Argument is text to log
if [ "$LOGGING_ENABLED" -eq 1 ]; then
echo "[`date +%T`]: $1">>$OPERATION_LOG
fi
}
function log() {
# Logs to all three of the log types. Argument is text to log.
# The filename of the logfile is determined at the point in time
# when the variable (SESSION_LOG, etc.) is defined. So define the
# session log at program start and use the operation_log()
# function at the start and end of each operation.
log_session "$1"
log_operation "$1"
}
function contains() {
# Determine if an array contains a key or value.
# Takes two arguments. First argument is the key or value.
# Second argument is a list of elements.
# Returns 0 if array contains key or value, 1 if not.
local element
for element in "${@:2}"; do
if [ "$element" = "$1" ]; then
return 0
fi
done
return 1
}
function inform() {
# Display an informative message for a short time
# Takes two arguments, first is the text to display.
# Second is optionally the number of seconds the message stays up.
if [ $2 ]; then
secs=$2
else
secs=3
fi
dialog --sleep "$secs" --aspect 80 --infobox "$1" 0 0
}
function pause() {
# Display a message and wait for acknowledgment
# takes one optional argument, the message to display
if [ "$1" = "" ]; then
pausetxt="----- PAUSED -----"
else
pausetxt="$1"
fi
dialog --aspect 80 --msgbox "$pausetxt" 0 0
}
function showimages() {
if [ -d $IMAGE_PATH ]; then
radiotmp=`mktemp -q`
radiocoltmp=`mktemp -q`
echo "___NAME___ ___DATE___ ___SIZE___ ___VERSION___">$radiotmp
origifs=$IFS
IFS=$'\n'
# Build a list of existing images
for line in `ls -l --time-style=+%Y-%m-%d\ %H:%M --hide="lost+found" $IMAGE_PATH/|sed -e 1,1d|awk {'print $8" "$6'}`; do
IFS=$origifs
local name=`echo $line|cut -d " " -f1`
local size=$(echo `du -sh $IMAGE_PATH/$name`|cut -d " " -f1)
local version=`cat $IMAGE_PATH/$name/version.txt 2>/dev/null`
if [ "$version" = "" ]; then version="-"; fi
if [ -e $IMAGE_PATH/$name/latest_backup_date.txt ]; then
local date=`cat $IMAGE_PATH/$name/latest_backup_date.txt`
else
local date=`echo $line|cut -d " " -f2`
fi
echo "$name $date $size $version">>$radiotmp
IFS=$'\n'
done
IFS=$origifs
column -t $radiotmp>$radiocoltmp
flush_input
dialog --title "Images available on backup media" \
--exit-label "Return" \
--textbox $radiocoltmp 24 79
rm -f $radiotmp $radiocoltmp
else
if [ "$PROG_NAME" = "rescuebru.sh" ]; then
flush_input
pause "Media does not appear to be set up as backup media.\nChoose Set up backup media from the main menu."
elif [ "$PROG_NAME" = "rbruserver.sh" ]; then
flush_input
pause "Path to backup images not found."
fi
return 1
fi
rm -f $radiotmp $radiocoltmp
return 0
}
function lognoteview() {
local backupname
local logname
# Allows user to view log files saved on backup media
if [ "$CLIENT_NET_STATUS" -eq 1 ]; then
echo "is allocated and is viewing log files.">$MEDIA_MNT_DIR/$wkg_addr
fi
while true; do
# Generate a list of backups for the radiobox dialog
radiotmp=`mktemp -q`
buildimagelist
# Prompt for backup name
if ! pickimagename "view log/notes files for" "single" ; then
rm -f $radiotmp
if [ "$CLIENT_NET_STATUS" -eq 1 ]; then
echo "is allocated and idle.">$MEDIA_MNT_DIR/$wkg_addr
fi
return 0
else
backupname=$imagename
fi
rm -f $radiotmp
# Generate a list of logs and notes for the radiobox dialog
radiotmp=`mktemp -q`
tagtmp=`mktemp -q`
for tag in `ls $IMAGE_PATH/$backupname/*.log|rev|cut -d / -f1|rev`; do
echo "$tag \"off\"">>$radiotmp
done
for tag in `ls $IMAGE_PATH/$backupname/*.notes|rev|cut -d / -f1|rev`; do
echo "$tag \"off\"">>$radiotmp
done
if [ ! -s "$radiotmp" ]; then
pause "No logs or notes found for this image."
rm -f $radiotmp $tagtmp
else
# Prompt for log or note file name
if [ `wc -l $radiotmp|cut -d " " -f1` -eq 1 ]; then
lognotename=`cat $radiotmp|cut -d " " -f1`
if approved "Only one log/notes file found. Select Yes to view that or No to return to the previous menu." "y"; then
# Show the log or notes file
flush_input
dialog --title $lognotename --textbox $IMAGE_PATH/$backupname/$lognotename 25 80
else
rm -f $radiotmp $tagtmp
if [ "$CLIENT_NET_STATUS" -eq 1 ]; then
echo "is allocated and idle.">$MEDIA_MNT_DIR/$wkg_addr
fi
return 0
fi
else
flush_input
while dialog --no-items --cancel-label "Return" --radiolist "Select log/notes file to view" \
20 60 20 `cat $radiotmp` 2>$tagtmp ; do
lognotename=`cat $tagtmp`
# Show the log or notes file
flush_input
dialog --title $lognotename --textbox $IMAGE_PATH/$backupname/$lognotename 25 80
done
fi
rm -f $radiotmp $tagtmp
fi
done
}
function getbackupname() {
# Get imagename for backup
radiotmp=`mktemp -q`
# Insert an option to create a new name
echo "\"NEW_IMAGE_NAME\" \"---------- ------ -----\" \"on\"">$radiotmp
buildimagelist
imgcount=`wc -l $radiotmp|cut -d " " -f1`
while [ ! $iname_confirmed ]; do
if [ $imgcount -eq 1 ]; then
# new image name is the only option
if ! getnewname ; then
log "Unable to get new image name."
rm -f $radiotmp
return 1
else
iname_confirmed=1
fi
else
# show the radiolist
if ! pickimagename "backup" "single"; then
log "Unable to get image name."
rm -f $radiotmp
return 1
else
iname_confirmed=1
fi
fi
if [ "$imagename" = "NEW_IMAGE_NAME" ]; then
# NEW_IMAGE_NAME was chosen from list
if ! getnewname ; then
log "Unable to get new image name."
rm -f $radiotmp
return 1
else
iname_confirmed=1
fi
fi
# test imagename for uniqueness and ask what to do
if [ -d $IMAGE_PATH/$imagename ]; then
overwrite_confirm
case $? in
1 )
unset iname_confirmed
;;
2 )
iname_confirmed=1
imgovr=1
rm -f $ovrchoicetmp
rm -f $radiotmp
;;
* )
unset iname_confirmed
rm -f $radiotmp
return 1
;;
esac
fi
done
rm -f $radiotmp
return 0
}
function getrestorename() {
# Get imagename for restore
radiotmp=`mktemp -q`
buildimagelist
imgcount=`wc -l $radiotmp|cut -d " " -f1`
if [ $imgcount -eq 1 ]; then
# only one image found
imagename=`cat $radiotmp|cut -d " " -f1|tr -d '"'`
pause "$imagename was the only image found. Selecting that to restore."
rm -f $radiotmp
return 0
else
if ! pickimagename "restore" "single"; then
rm -f $radiotmp
return 1
fi
fi
}
function getmodnames() {
# Get source and/or destination image names
# Parameter is "copy", "rename", or "delete"
# get existing image name
radiotmp=`mktemp -q`
buildimagelist
imgcount=`wc -l $radiotmp|cut -d " " -f1`
if [ $imgcount -eq 0 ]; then
# no images found
pause "No images found."
log "No images found."
rm -f $radiotmp
return 1
elif [ $imgcount -eq 1 ]; then
# only one image found
srcimagename=`cat $radiotmp|cut -d " " -f1|tr -d '"'`
pause "$srcimagename was the only image found. Selecting that to $1."
rm -f $radiotmp
else
# more than one image found
if [ "$1" = "delete" ]; then
pickcmd="pickimagename $1 multiple"
else
pickcmd="pickimagename $1 single"
fi
if ! $pickcmd ; then
rm -f $radiotmp
return 1
else
srcimagename=$imagename
fi
fi
if [ "$1" = "copy" -o "$1" = "rename" ]; then
# get destination image name
unset imagename
if ! getnewname ; then
pause "Unable to get new image name."
log "Unable to get new image name."
return 1
elif [ -d $IMAGE_PATH/$imagename ]; then
# test imagename for uniqueness and ask what to do
overwrite_confirm
case $1 in
0 )
unset iname_confirmed
inform "Overwrite confirmation cancelled."
return 1
;;
1 )
unset iname_confirmed
;;
2 )
destimagename=$imagename
iname_confirmed=1
imgovr=1
return 0
;;
esac
else
destimagename=$imagename
iname_confirmed=1
imgovr=1
return 0
fi
fi
}
function overwrite_confirm() {
# Get confirmation to overwrite an image
# Returns 1 to select a new name, 2 to overwrite existing, or something
# else to just cancel.
ovrchoicetmp=`mktemp -q`
dialog --no-tags --menu "You have selected an existing image name." 9 60 9 \
"1" "Return to image selection and enter a new name" \
"2" "Overwrite existing image (if operation successful)" 2>$ovrchoicetmp
local retval=`cat $ovrchoicetmp`
rm -f $ovrchoicetmp
return $retval
}
function pickimagename() {
# Choose a new image name(s) from a radiolist or checklist dialog
# $radiotmp list file must already exist using buildimagelist()
# takes two arguments. First is operation to be performed.
# Second is "single" or "multiple".
radiocoltmp=`mktemp -q`
selectiontmp=`mktemp -q`
column -t $radiotmp>$radiocoltmp
local dialog_script=`mktemp -q`
if [ "$2" = "single" ]; then
local listtype="--radiolist"
local noun="name"
elif [ "$2" = "multiple" ]; then
local listtype="--checklist"
local noun="names"
else
pause "ERROR in pickimagename(). Must specify single or multiple."
rm -f $radiotmp $radiocoltmp $selectiontmp $dialog_script
return 1
fi
echo "dialog --cancel-label \"Return\" $listtype \"Select image $noun to $1\" 25 80 25 `cat $radiocoltmp|tr '\n' ' '` 2>$selectiontmp">$dialog_script
if ! bash $dialog_script ; then
rm -f $radiotmp $radiocoltmp $selectiontmp $dialog_script
return 1
fi
imagename=`cat $selectiontmp`
rm -f $radiocoltmp $selectiontmp $dialog_script
return 0
}
function getnewname() {
# Prompt for a new image name
local iname_invalid=1
while [ $iname_invalid ]; do
# Prompt for new image name
nametmp=`mktemp -q`
if dialog --inputbox "Please enter new image name" \
8 50 "$imagename" 2>$nametmp ; then
imagename=`cat $nametmp`
if ! validate_text name $imagename; then
pause "Image name contains invalid characters. Valid characters are upper and lowercase letters, numbers, underscore, and dash. Try again." 0 0
elif [ "$imagename" = "" ]; then
pause "Image name cannot be blank. \nTry again."
elif [ "$imagename" = "NEW_IMAGE_NAME" ]; then
pause "\"NEW_IMAGE_NAME\" is not a valid image name. \nChoose another."
elif [ "$imagename" = "lost+found" ]; then
pause "\"lost+found\" is not a valid image name. \nChoose another."
else
unset iname_invalid
rm -f $nametmp
fi
else
inform "Image name entry aborted."
rm -f $nametmp
unset imagename
return 1
fi
done
log "New image name is $imagename"
return 0
}
function buildimagelist() {
# Create a file containing a list of existing images with info
if [ ! -e $radiotmp ]; then
radiotmp=`mktemp -q`
fi
origifs=$IFS
IFS=$'\n'
for line in `ls --time-style=+%Y-%m-%d\ %H:%M --hide="lost+found" -l $IMAGE_PATH/|sed -e 1,1d|awk {'print $8" "$6'}`; do
IFS=$origifs
local name=`echo $line|cut -d " " -f1`
local size=$(echo `du -sh $IMAGE_PATH/$name`|cut -d " " -f1)
local version=`cat $IMAGE_PATH/$name/version.txt 2>/dev/null`
if [ "$version" = "" ]; then version="-"; fi
if [ -e $IMAGE_PATH/$name/latest_backup_date.txt ]; then
local bdate=`cat $IMAGE_PATH/$name/latest_backup_date.txt`
else
local bdate=`echo $line|cut -d " " -f2`
fi
local tag="$name"
local item="$bdate $size $version"
local status="off"
echo "\"$tag\" \"$item\" \"$status\"">>$radiotmp
IFS=$'\n'
done
IFS=$origifs
}
function getimagename() {
# Dispatcher function to get image name(s) for an operation
# parameter is "backup" or "restore" or "copy" or "rename" or "delete"
unset imgovr
while [ ! $iname_confirmed ]; do
case $1 in
"backup" )
getbackupname
return $?
;;
"restore" )
getrestorename
return $?
;;
"copy" | "rename" | "delete" )
getmodnames "$1"
return $?
;;
* )
pause "${FUNCNAME[@]} :Invalid parameter passed to getimagename()."
return 1
;;
esac
done
}
function shutdown_proc() {
# Manage shutdown or reboot
# Takes one argument, "shutdown" or "reboot"
# Returns 1 on abort or invalid option.
case "$1" in
"reboot" )
warntxt="REBOOT"
cmdtxt="reboot"
abrttxt="Reboot"
;;
"shutdown" )
warntxt="SHUTDOWN"
cmdtxt="poweroff"
abrttxt="Shutdown"
;;
* )
inform "Invalid shutdown option. Aborting." "4"
return 1
;;
esac
flush_input
if dialog --no-cancel --pause "$warntxt IN PROGRESS. Press <Esc> to abort or <Enter> to confirm." \
10 70 30 ; then
if cleanup ; then
inform "Starting $warntxt now." "1"
reset
$cmdtxt
else
pause "$abrttxt could not be completed."
return 1
fi
else
inform "$abrttxt aborted"
return 1
fi
}
function image_mod {
while true; do
# Show modification menu
imtmp=`mktemp -q`
if dialog --no-cancel \
--title "IMAGE MANAGEMENT" \
--menu "Please enter choice:" 11 50 6 \
"1" "Copy an image" \
"2" "Rename an image" \
"3" "Delete one or more images" \
"M" "Return to main menu" 2>$imtmp ; then
imchoice=`cat $imtmp`
rm -f $imtmp
case $imchoice in
"M" )
return 0
;;
3 )
mod_type="delete"
mod_cmd="rm -rf "
;;
2 )
mod_type="rename"
mod_cmd="mv "
;;
1 )
mod_type="copy"
mod_cmd="cp -R "
;;
esac
# Get source and/or destination image names
unset srcimagename destimagename imagename
if getmodnames "$mod_type" ; then
# srcimagename, destimagename for copy or rename
# srcimagename for delete
if [ "$mod_type" = "copy" -o "$mod_type" = "rename" ]; then
inform "$mod_type in progress. Please wait..."
if $mod_cmd $IMAGE_PATH/$srcimagename $IMAGE_PATH/$destimagename ; then
inform "$mod_type succeeded."
else
pause "$mod_type failed."
fi
elif [ "$mod_type" = "delete" ]; then
unset delfail
if approved "WARNING! You are about to permanently delete the following image(s):\n$srcimagename\nAre you sure?" "n"; then
inform "$mod_type in progress. Please wait..."
for delimg in $srcimagename; do
if ! $mod_cmd $IMAGE_PATH/$delimg ; then
delfail="$delfail $delimg"
fi
done
if [ $delfail ]; then
pause "The following images could not be deleted:\n$delfail"
else
inform "$mod_type succeeded."
fi
else
inform "Image deletion aborted."
fi
else
pause "An invalid modification type was specified."
fi
else
pause "Couldn't get image name to $mod_type."
fi
else
inform "Image modification canceled."
return 1
fi
done
}