# 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 to abort or 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 }