#!/bin/bash
########################################################################
# Copyright (c) 2020 MariaDB Corporation Ab
#
# Use of this software is governed by the Business Source License included
# in the LICENSE.TXT file and at www.mariadb.com/bsl11.
#
# Change Date: 2024-07-10
#
# On the date above, in accordance with the Business Source License, use
# of this software will be governed by version 2 or later of the General
# Public License.
#
########################################################################
# Documentation:  bash mcs_backup_manager.sh help
# Version: 3.18
# 
# Backup Example
#   LocalStorage: sudo ./mcs_backup_manager.sh backup
#   S3:           sudo ./mcs_backup_manager.sh backup -bb s3://my-cs-backups
#
########################################################################
#
# Restore Example
#   LocalStorage: sudo ./mcs_backup_manager.sh restore -l <date>
#   S3:           sudo ./mcs_backup_manager.sh restore -bb s3://my-cs-backups -l <date> 
# 
########################################################################
mcs_bk_manager_version="3.18"
start=$(date +%s)
action=$1

print_action_help_text() {
    echo "
MariaDB ColumnStore Backup Manager

Actions:

    backup                  Full & Incremental ColumnStore backup with additional flags to augment the backup taken
    restore                 Restore a backup taken with this script
    dbrm_backup             Quick hot backup of internal ColumnStore metadata only - only use under support recommendation
    dbrm_restore            Restore internal ColumnStore metadata from dbrm_backup - only use under support recommendation

Documentation:
    bash $0 <action> help

Example:
    bash $0 backup help
    "
}

check_operating_system() {

    OPERATING_SYSTEM=$(awk -F= '/^ID=/{gsub(/"/, "", $2); print $2}' /etc/os-release)

    # Supported OS
    case $OPERATING_SYSTEM in
        centos | rhel | rocky )
            return 1;
            ;;
        ubuntu | debian )
            return 1;
            ;;
        *)  # unknown option
            printf "\ncheck_operating_system: unknown os & version: $OPERATING_SYSTEM\n"
            exit 2;
    esac   
}

load_default_backup_variables() {
    check_operating_system
    
    # What directory to store the backups on this machine or the target machine.
    # Consider write permissions of the scp user and the user running this script.
    # Mariadb-backup will use this location as a tmp dir for S3 and remote backups temporarily
    # Example: /mnt/backups/
    backup_location="/tmp/backups/"

    # Are the backups going to be stored on the same machine this script is running on or another server - if Remote you need to setup scp=
    # Options: "Local" or "Remote"
    backup_destination="Local"

    # Used only if backup_destination="Remote"
    # The user/credentials that will be used to scp the backup files
    # Example: "centos@10.14.51.62"
    scp=""

    # Only used if storage=S3
    # Name of the bucket to store the ColumnStore backups
    # Example: "s3://my-cs-backups"
    backup_bucket=""

    # OS specific Defaults
    case $OPERATING_SYSTEM in
        centos | rocky | rhel )
            MARIADB_SERVER_CONFIGS_PATH="/etc/my.cnf.d"
            ;;
        ubuntu | debian )
            MARIADB_SERVER_CONFIGS_PATH="/etc/mysql/mariadb.conf.d/"
            ;;
        *)  # unknown option
            handle_failed_dependencies "\nload_default_backup_variables: unknown os & version: $OPERATING_SYSTEM\n";
    esac  

    # Fixed Paths
    CS_CONFIGS_PATH="/etc/columnstore"
    DBRM_PATH="/var/lib/columnstore/data1/systemFiles/dbrm"
    STORAGEMANAGER_PATH="/var/lib/columnstore/storagemanager"
    STORAGEMANGER_CNF="$CS_CONFIGS_PATH/storagemanager.cnf"

    # Configurable Paths
    cs_metadata=$(grep ^metadata_path $STORAGEMANGER_CNF | cut -d "=" -f 2 | tr -d " ")
    cs_journal=$(grep ^journal_path $STORAGEMANGER_CNF  | cut -d "=" -f 2 | tr -d " ")
    cs_cache=$(grep -A25 "\[Cache\]" $STORAGEMANGER_CNF | grep ^path | cut -d "=" -f 2 | tr -d " ")

    # What storage topology is being used by Columnstore - found in /etc/columnstore/storagemanager.cnf
    # Options: "LocalStorage" or "S3" 
    storage=$(grep -m 1 "^service = " $STORAGEMANGER_CNF | awk '{print $3}')

    # Name of the existing bucket used in the cluster - found in /etc/columnstore/storagemanager.cnf
    # Example: "mcs-20211101172656965400000001"
    bucket=$( grep -m 1 "^bucket =" $STORAGEMANGER_CNF | awk '{print $3}')

    # modes ['direct','indirect'] - direct backups run on the columnstore nodes themselves. indirect run on another machine that has read-only mounts associated with columnstore/mariadb
    mode="direct"

    # Name of the Configuration file to load variables from - relative or full path accepted
    config_file=""

    # Track your write speed with "dstat --top-cpu --top-io"
    # Monitor individual rsyncs with ` watch -n0.25 "ps aux | grep 'rsync -a' | grep -vE 'color|grep' | wc -l; ps aux | grep 'rsync -a' | grep -vE 'color|grep' " `
    # Determines if columnstore data directories will have multiple rsync running at the same time for different subfolders to parallelize writes
    parallel_rsync=false
    # Directory structure:    /var/lib/columnstore/data$dbroot/$dir1.dir/$dir2.dir/$dir3.dir/$dir4.dir/$partition.dir/FILE.$seqnum.cdf ;
    # DEPTH [1,2,3,4] determines at what level to begin (dir1/dir2/dir3/dir4) the number of PARALLEL_FOLDERS to issue
    DEPTH=3
    # PARALLEL_FOLDERS determines the number of parent directories to have rsync x PARALLEL_THREADS running at the same time ( for DEPTH=3 i.e /var/lib/columnstore/data$dbroot/$dir1.dir/$dir2.dir/$dir3.dir)
    PARALLEL_FOLDERS=4
    # PARALLEL_THREADS determines the number of threads to rsync subdirectories of the set DEPTH level ( for DEPTH=3 i.e /var/lib/columnstore/data$dbroot/$dir1.dir/$dir2.dir/$dir3.dir/X)
    # PARALLEL_THREADS is also used in for pigz compression parallelism
    PARALLEL_THREADS=4

    # Google Cloud
    # PARALLEL_UPLOAD=50
    RETRY_LIMIT=3

    # Other Variables
    #today=$(date +%m-%d-%Y-%H%M%S)
    today=$(date +%m-%d-%Y)
    HA=false
    if [ ! -f /var/lib/columnstore/local/module ]; then  pm="pm1"; else pm=$(cat /var/lib/columnstore/local/module);  fi;
    PM_NUMBER=$(echo "$pm" | tr -dc '0-9')
    if [[ -z $PM_NUMBER ]]; then PM_NUMBER=1; fi;
    
    #source_ips=$(grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" /etc/columnstore/Columnstore.xml)
    #source_host_names=$(grep "<Node>" /etc/columnstore/Columnstore.xml)
    cmapi_key=""
    if [[ -f "$CS_CONFIGS_PATH/cmapi_server.conf" ]]; then
        cmapi_key="$(grep "x-api-key" "$CS_CONFIGS_PATH/cmapi_server.conf" | awk '{print $3}' | tr -d "'")";
    fi
    final_message="Backup Complete"
    skip_save_brm=false
    skip_locks=false
    skip_polls=false
    skip_mdb=false
    skip_bucket_data=false
    quiet=false
    extra_s3_args=""
    extra_cmd_args=""
    rsync_flags=" -av";
    poll_interval=5
    poll_max_wait=60;
    list_backups=false
    retention_file_lock_name=".delete-lock"
    apply_retention_only=false

    # Compression Variables
    compress_format=""
    split_file_mdb_prefix="mariadb-backup.tar"
    split_file_cs_prefix="cs-localfiles.tar"
    timeout=999999
    log_file="backup.log"
    col_xml_backup="Columnstore.xml.source_backup"
    cmapi_backup="cmapi_server.conf.source_backup"

    # Used by on premise S3 vendors
    # Example: "http://127.0.0.1:8000"
    s3_url=""
    no_verify_ssl=false

    # Deletes backups older than this variable retention_days
    retention_days=0

    # Tracks if flush read lock has been run
    read_lock=false
    incremental=false
    columnstore_online=false

    confirm_xmllint_installed
   
    # Number of DBroots
    # Integer usually 1 or 3
    DBROOT_COUNT=1
    DBROOT_COUNT=$(xmllint --xpath "string(//DBRootCount)" $CS_CONFIGS_PATH/Columnstore.xml)
    ASSIGNED_DBROOT=$(xmllint --xpath "string(//ModuleDBRootID$PM_NUMBER-1-3)" $CS_CONFIGS_PATH/Columnstore.xml)
}

parse_backup_variables() {   
    # Dynamic Arguments
    while [[ $# -gt 0 ]]; do
        key="$1"
        case $key in
            backup)
                shift # past argument
                ;;
            -bl|--backup-location)
                backup_location="$2"
                shift # past argument
                shift # past value
                ;;
            -bd|--backup-destination)
                backup_destination="$2"
                shift # past argument
                shift # past value
                ;;
            -scp|--secure-copy-protocol)
                scp="$2"
                shift # past argument
                shift # past value
                ;;
            -bb|--backup-bucket)
                backup_bucket="$2"
                shift # past argument
                shift # past value
                ;;
            -url| --endpoint-url)
                s3_url="$2"
                shift # past argument
                shift # past value
                ;;
            -s|--storage)
                storage="$2"
                shift # past argument
                shift # past value
                ;;
            -i|--incremental)
                incremental=true
                today="$2"
                shift # past argument
                shift # past value
                ;;
            -P|--parallel)
                parallel_rsync=true
                PARALLEL_THREADS="$2"
                shift # past argument
                shift # past value
                ;;
            -ha | --highavilability)
                HA=true
                shift # past argument
                ;;
            -f| --config-file)
                config_file="$2"
                if [ "$config_file" == "" ] || [ ! -f "$config_file" ]; then
                    handle_early_exit_on_backup "\n[!!!] Invalid Configuration File: $config_file\n" true
                fi  
                shift # past argument
                shift # past value
                ;;
            -sbrm| --skip-save-brm)
                skip_save_brm=true
                shift # past argument
                ;;
            -spoll| --skip-polls)
                skip_polls=true
                shift # past argument
                ;;
            -slock| --skip-locks)
                skip_locks=true
                shift # past argument
                ;;
            -smdb |  --skip-mariadb-backup)
                skip_mdb=true
                shift # past argument
                ;;
            -sb   | --skip-bucket-data)
                skip_bucket_data=true
                shift # past argument
                ;;
            -nb   | --name-backup)
                today="$2"
                shift # past argument
                shift # past value
                ;;
            -m    | --mode)
                mode="$2"
                shift # past argument
                shift # past value
                ;;
            -c    | --compress)
                compress_format="$2"
                shift # past argument
                shift # past value
                ;;
            -q    | --quiet)
                quiet=true
                shift # past argument
                ;;
            -nv-ssl| --no-verify-ssl)
                no_verify_ssl=true 
                shift # past argument
                ;;
            -pi| --poll-interval)
                poll_interval="$2"
                shift # past argument
                shift # past value
                ;;
            -pmw| --poll-max-wait)
                poll_max_wait="$2"
                shift # past argument
                shift # past value
                ;;
            -r|--retention-days)
                retention_days="$2"
                shift # past argument
                shift # past value
                ;;
            -aro| --apply-retention-only)
                apply_retention_only=true
                shift # past argument
                ;;
            -li | --list)
                list_backups=true
                shift # past argument
                ;;
            -h|--help|-help|help)
                print_backup_help_text;
                exit 1;
                ;;
            *)  # unknown option
                handle_early_exit_on_backup "\nunknown flag: $1\n" true
        esac
    done

    # Load config file
    if [ -n "$config_file" ] && [ -f "$config_file" ]; then
        if ! source "$config_file"; then
            handle_early_exit_on_backup "\n[!!!] Failed to source configuration file: $config_file\n" true
        fi
    fi

    # Adjustment for indirect mode
    if [ $mode == "indirect" ]; then skip_locks=true; skip_save_brm=true; fi;

    # check retention_days is not empty string and is an integer
    confirm_integer_else_fail "$retention_days" "Retention"
}

print_backup_help_text() {
     echo "
    Columnstore Backup

        -bl    | --backup-location        Directory where the backup will be saved
        -bd    | --backup-destination     If the directory is 'Local' or 'Remote' to this script
        -scp   | --secure-copy-protocol   scp connection to remote server if -bd 'Remote'
        -bb    | --backup-bucket          Bucket name for where to save S3 backups
        -url   | --endpoint-url           On premise url to s3 storage api example: http://127.0.0.1:8000
        -nv-ssl| --no-verify-ssl          Skips verifying ssl certs, useful for on premise s3 storage
        -s     | --storage                The storage used by columnstore data 'LocalStorage' or 'S3'
        -i     | --incremental            Adds columnstore deltas to an existing full backup [ <Folder>, auto_most_recent ]
        -P     | --parallel               Enables parallel rsync for faster backups, setting the number of simultaneous rsync processes. With -c/--compress, sets the number of compression threads.
        -f     | --config-file            Path to backup configuration file to load variables from - see load_default_backup_variables() for defaults
        -sbrm  | --skip-save-brm          Skip saving brm prior to running a backup - ideal for dirty backups
        -slock | --skip-locks             Skip issuing write locks - ideal for dirty backups
        -spoll | --skip-polls             Skip sql checks confirming no write/cpimports running
        -smdb  | --skip-mariadb-backup    Skip running a mariadb-backup for innodb data - ideal for incremental dirty backups
        -sb    | --skip-bucket-data       Skip taking a copy of the columnstore data in the bucket
        -pi    | --poll-interval          Number of seconds between poll checks for active writes & cpimports
        -pmw   | --poll-max-wait          Max number of minutes for polling checks for writes to wait before exiting as a failed backup attempt
        -q     | --quiet                  Silence verbose copy command outputs
        -c     | --compress               Compress backup in X format - Options: [ pigz ]
        -nb    | --name-backup            Define the name of the backup - default: date +%m-%d-%Y
        -r     | --retention-days         Retain backups created within the last X days, the rest are deleted, default 0 = keep all backups
        -aro   | --apply-retention-only   Only apply retention policy to existing backups, does not run a backup
        -li    | --list                   List all backups available in the backup location
        -ha    | --highavilability        Hint wether shared storage is attached @ below on all nodes to see all data
                                            HA LocalStorage ( /var/lib/columnstore/dataX/ )
                                            HA S3           ( /var/lib/columnstore/storagemanager/ ) 

        Local Storage Examples:
            ./$0 backup -bl /tmp/backups/ 
            ./$0 backup -bl /tmp/backups/ -P 8
            ./$0 backup -bl /tmp/backups/ --incremental auto_most_recent
            ./$0 backup -bl /tmp/backups/ --list
            ./$0 backup -bl /tmp/backups/ -bd Remote -scp root@172.31.6.163 -s LocalStorage

        S3 Examples:  
            ./$0 backup -bb s3://my-cs-backups -s S3
            ./$0 backup -bb s3://my-cs-backups -c pigz --quiet -sb
            ./$0 backup -bb gs://my-cs-backups -s S3 --incremental 12-18-2023
            ./$0 backup -bb s3://my-on-premise-bucket -s S3 -url http://127.0.0.1:8000
            
        Cron Example:
        sudo crontab -e
        */60 */24 * * *  /tmp/$0 backup -P 4 -nb `date +\%m-\%d-\%Y-\%H:\%M` >> /tmp/cs_backup.log  2>&1

    ";

    # Hidden flags
    # -m | --mode           Options ['direct','indirect'] - direct backups run on the columnstore nodes themselves. indirect run on another machine that has read-only mounts associated with columnstore/mariadb

}

print_backup_variables() {
    # Log Variables
    s1=20
    s2=20
    printf "\nBackup Variables\n"
    echo "--------------------------------------------------------------------------"
    if [ -n "$config_file" ] && [ -f "$config_file" ]; then
        printf "%-${s1}s %-${s2}s\n" "Configuration File:" "$config_file";
    fi
    echo "--------------------------------------------------------------------------"
    printf "%-10s %-10s %-10s %-10s %-10s\n" "Skips:" "BRM($skip_save_brm)" "Locks($skip_locks)" "MariaDB($skip_mdb)" "Bucket($skip_bucket_data)";
    echo "--------------------------------------------------------------------------"
    printf "%-${s1}s  %-${s2}s\n" "Storage:" "$storage";
    printf "%-${s1}s  %-${s2}s\n" "Folder:" "$today";
    printf "%-${s1}s  %-${s2}s\n" "DB Root Count:" "$DBROOT_COUNT";
    printf "%-${s1}s  %-${s2}s\n" "PM:" "$pm";
    printf "%-${s1}s  %-${s2}s\n" "Highly Available:" "$HA";
    printf "%-${s1}s  %-${s2}s\n" "Incremental:" "$incremental";
    printf "%-${s1}s  %-${s2}s\n" "Timestamp:" "$(date +%m-%d-%Y-%H%M%S)";
    printf "%-${s1}s  %-${s2}s\n" "Retention:" "$retention_days";

    if [[ -n "$compress_format" ]]; then
        printf "%-${s1}s  %-${s2}s\n" "Compression:" "true";
        printf "%-${s1}s  %-${s2}s\n" "Compression Format:" "$compress_format";
        printf "%-${s1}s  %-${s2}s\n" "Compression Threads:" "$PARALLEL_THREADS";
    else 
        printf "%-${s1}s  %-${s2}s\n" "Parallel Enabled:" "$parallel_rsync";
        if $parallel_rsync ; then printf "%-${s1}s  %-${s2}s\n" "Parallel Threads:" "$PARALLEL_THREADS";   fi;
    fi

    if [ $storage == "LocalStorage" ]; then
        printf "%-${s1}s  %-${s2}s\n" "Backup Destination:" "$backup_destination";
        if [ $backup_destination == "Remote" ]; then   printf "%-${s1}s  %-${s2}s\n" "scp:" "$scp";  fi; 
        printf "%-${s1}s  %-${s2}s\n" "Backup Location:" "$backup_location";
    fi

    if [ $storage == "S3" ]; then 
        printf "%-${s1}s  %-${s2}s\n" "Active Bucket:" "$bucket";
        printf "%-${s1}s  %-${s2}s\n" "Backup Bucket:" "$backup_bucket";
    fi
    echo "--------------------------------------------------------------------------"
}

check_package_managers() {
    
    package_manager='';
    if command -v apt &> /dev/null ; then
        if ! command -v dpkg-query &> /dev/null ; then
            printf "[!!] Cant access dpkg-query\n"
            exit 1;
        fi
        package_manager="apt";
    fi

    if command -v yum &> /dev/null ; then
        package_manager="yum";
    fi    

    if [ $package_manager == '' ]; then 
        handle_failed_dependencies "[!!] No package manager found: yum or apt must be installed"
        exit 1;
    fi;
}

confirm_xmllint_installed() {
    
    if ! command -v xmllint > /dev/null; then
        printf "[!] xmllint not installed ... attempting auto install\n\n"
        check_package_managers
        case $package_manager in
            yum ) 
                install_command="yum install libxml2 -y";
                ;;
            apt )
                install_command="apt install libxml2-utils -y --quiet";
                ;;
            *)  # unknown option
                handle_failed_dependencies "[!!] package manager not implemented: $package_manager\n"
                exit 2;
        esac

        if ! eval $install_command; then
            handle_failed_dependencies "[!] Failed to install xmllint\nThis is required."
            exit 1;
        fi
    fi
}

confirm_rsync_installed() {

    if ! command -v rsync  > /dev/null; then
        printf "[!] rsync not installed ... attempting auto install\n\n"
        check_package_managers
        case $package_manager in
            yum ) 
                install_command="yum install rsync -y";
                ;;
            apt )
                install_command="apt install rsync -y --quiet";
                ;;
            *)  # unknown option
                log_debug_message "[!!] package manager not implemented: $package_manager\n"
                exit 2;
        esac

        if ! eval $install_command; then
            handle_failed_dependencies "[!] Failed to install rsync\nThis is required."
            exit 1;
        fi
    fi
}

confirm_mariadb_backup_installed() {

    if $skip_mdb ; then return; fi;

    if ! command -v  mariadb-backup  > /dev/null; then
        printf "[!]  mariadb-backup not installed ... attempting auto install\n\n"
        check_package_managers
        case $package_manager in
            yum ) 
                install_command="yum install MariaDB-backup -y";
                ;;
            apt )
                install_command="apt install mariadb-backup -y --quiet";
                ;;
            *)  # unknown option
                log_debug_message "[!!] package manager not implemented: $package_manager\n"
                exit 2;
        esac

        if ! eval $install_command; then
            handle_failed_dependencies "[!] Failed to install MariaDB-backup\nThis is required."
            exit 1;
        fi
    fi
}

confirm_pigz_installed() {

    if ! command -v pigz  > /dev/null; then
        printf "[!] pigz not installed ... attempting auto install\n\n"
        check_package_managers
        case $package_manager in
            yum ) 
                install_command="yum install pigz -y";
                ;;
            apt )
                install_command="apt install pigz -y --quiet";
                ;;
            *)  # unknown option
                log_debug_message "[!!] package manager not implemented: $package_manager\n"
                exit 2;
        esac

        if ! eval $install_command; then
            handle_failed_dependencies "[!] Failed to install pigz\nThis is required for '-c pigz'"
            exit 1;
        fi
    fi
}

check_for_dependencies() {
    # Check pidof works
    if [ $mode != "indirect" ] &&  ! command -v pidof > /dev/null; then
        handle_failed_dependencies "\n\n[!] Please make sure pidof is installed and executable\n\n"
    fi
    
    # used for save_brm and defining columnstore_user
    if ! command -v stat > /dev/null; then
        handle_failed_dependencies "\n\n[!] Please make sure stat is installed and executable\n\n"
    fi

    confirm_rsync_installed
    confirm_mariadb_backup_installed

    if [ $1 == "backup" ] && [ $mode != "indirect" ] && ! command -v dbrmctl  > /dev/null; then 
        handle_failed_dependencies "\n\n[!] dbrmctl unreachable to issue lock \n\n"
    fi

    if [ $storage == "S3" ]; then
        
        # Default cloud
        cloud="aws"
        
        # Critical argument for S3 - determine which cloud
        if [ -z "$backup_bucket" ]; then handle_failed_dependencies "\n\n[!] Undefined --backup-bucket: $backup_bucket \nfor examples see: ./$0 backup --help\n\n"; fi
        if [[ $backup_bucket == gs://* ]]; then
            cloud="gcp"; protocol="gs";
        elif [[ $backup_bucket == s3://* ]]; then
            cloud="aws"; protocol="s3";
        else 
            handle_failed_dependencies "\n\n[!] Invalid --backup-bucket - does not lead with gs:// or s3:// - $backup_bucket\n\n";
        fi

        if [ $cloud == "gcp" ]; then
            # If GCP - Check gsutil installed
            gsutil=""
            protocol="gs"
            if command -v mcs_gsutil > /dev/null; then
                gsutil=$(which mcs_gsutil 2>/dev/null)
            elif ! command -v gsutil > /dev/null; then
                which gsutil
                echo "Hints:  
                curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-443.0.0-linux-x86_64.tar.gz
                tar -xvf google-cloud-cli-443.0.0-linux-x86_64.tar.gz
                ./google-cloud-sdk/install.sh -q
                sudo ln -s \$PWD/google-cloud-sdk/bin/gsutil /usr/bin/gsutil

                Then
                A)    gsutil config -a
                or B) gcloud auth activate-service-account --key-file=user-file.json
                "

                handle_failed_dependencies "\n\n[!] Please make sure gsutil cli is installed configured and executable\n\n"
            else 
                gsutil=$(which gsutil 2>/dev/null)
            fi
           
            # gsutil syntax for silent
            if $quiet; then extra_s3_args+="-q"; fi
        else 

            # on prem S3 will use aws cli
            # If AWS - Check aws-cli installed
            awscli=""
            protocol="s3"
            if command -v mcs_aws > /dev/null; then
                awscli=$(which mcs_aws 2>/dev/null)
            elif ! command -v aws > /dev/null; then
                which aws
                echo "Hints:  
                curl \"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\" -o \"awscliv2.zip\"
                unzip awscliv2.zip
                sudo ./aws/install
                aws configure"

                handle_failed_dependencies "\n\n[!] Please make sure aws cli is installed configured and executable\nSee existing .cnf aws credentials with:  grep aws_ $STORAGEMANGER_CNF \n\n"
            else
                awscli=$(which aws 2>/dev/null)
            fi
            
            # aws syntax for silent
            if $quiet; then extra_s3_args+="--quiet"; fi
        fi;
    fi
}

validation_prechecks_for_backup() {
    echo "Prechecks"

    # Adjust rsync for incremental copy
    additional_rsync_flags=""
    if $quiet; then extra_cmd_args+="  2> /dev/null"; additional_rsync_flags+=" -a"; else additional_rsync_flags+=" -av "; fi
    if  $incremental ; then additional_rsync_flags+=" --inplace --no-whole-file --delete"; fi;
    if [ -z "$mode" ]; then printf "\n[!!!] Required field --mode: $mode - is empty\n"; exit 1; fi
    if [ "$mode" != "direct" ] && [ "$mode" != "indirect" ] ; then printf "\n[!!!] Invalid field --mode: $mode\n"; exit 1; fi

    # Poll Variable Checks
    confirm_integer_else_fail "$poll_interval" "poll_interval"
    confirm_integer_else_fail "$poll_max_wait" "poll_max_wait"
    max_poll_attempts=$((poll_max_wait * 60 / poll_interval))
    if [ "$max_poll_attempts" -lt 1 ]; then max_poll_attempts=1; fi;

    # Detect if columnstore online
    if [ $mode == "direct" ]; then
        if [ -z $(pidof PrimProc) ] || [ -z $(pidof WriteEngineServer) ]; then 
            printf " - Columnstore is OFFLINE \n"; 
            export columnstore_online=false; 
        else 
            printf " - Columnstore is ONLINE - safer if offline (but not required to be offline) \n"; 
            export columnstore_online=true; 
        fi
    fi;

    # Validate compression option
    if [[ -n "$compress_format" ]]; then
        case $compress_format in
            pigz) 
                confirm_pigz_installed
                ;;
            *)  # unknown option
                handle_early_exit_on_backup "\n[!!!] Invalid field --compress: $compress_format\n\n" true
        esac
    fi;

    # If Remote save - Check ssh works to remote
    if [ $backup_destination == "Remote" ]; then
        if ssh -o ConnectTimeout=10 $scp echo ok 2>&1 ;then
            printf 'SSH Works\n'
        else
            handle_early_exit_on_backup "Failed Command: ssh $scp\n" true
        fi
    fi

    # Storage Based Checks
    if [ $storage == "LocalStorage" ]; then 

        # Incremental Job checks
        if $incremental; then
            
            # $backup_location must exist to find an existing full back to add to
            if [ ! -d $backup_location ]; then
                handle_early_exit_on_backup "[X] Backup directory ($backup_location) DOES NOT exist ( -bl <directory> ) \n\n" true; 
            fi
             
            if [ "$today" == "auto_most_recent" ]; then 
                auto_select_most_recent_backup_for_incremental
            fi
            
            # Validate $today is a non-empty value
            if [ -z "$today" ]; then 
                handle_early_exit_on_backup "\nUndefined folder to increment on ($backup_location$today)\nTry --incremental <folder> or --incremental auto_most_recent \n" true
            fi

            # Cant continue if this folder (which represents a full backup) does not exists
            if [ $backup_destination == "Local" ]; then 
                if [ -d $backup_location$today ]; then 
                    printf " - Full backup directory exists\n"; 
                else  
                    handle_early_exit_on_backup "[X] Full backup directory ($backup_location$today) DOES NOT exist \n\n" true; 
                fi;
            elif [ $backup_destination == "Remote" ]; then
                if [[ $(ssh $scp test -d $backup_location$today && echo exists) ]]; then  
                    printf " - Full backup directory exists\n"; 
                else 
                    handle_early_exit_on_backup "[X] Full backup directory ($backup_location$today) DOES NOT exist on remote $scp \n\n" true;  
                fi
            fi
        fi
    elif [ $storage == "S3" ]; then
        
        # Adjust s3api flags for on premise/custom endpoints
        add_s3_api_flags=""
        if [ -n "$s3_url" ];  then add_s3_api_flags+=" --endpoint-url $s3_url"; fi;
        if $no_verify_ssl; then add_s3_api_flags+=" --no-verify-ssl"; fi;
        
        # Validate additional relevant arguments for S3
        if [ -z "$backup_bucket" ]; then echo "Invalid --backup_bucket: $backup_bucket - is empty"; exit 1; fi

        # Check cli access to bucket
        if [ $cloud == "gcp" ]; then
        
            if $gsutil ls $backup_bucket > /dev/null ; then
                printf " - Success listing backup bucket\n"
            else 
                printf "\n[X] Failed to list bucket contents... \nCheck $gsutil credentials: $gsutil config -a"
                handle_early_exit_on_backup "\n$gsutil ls $backup_bucket \n\n" true
            fi

        else 

            # Check aws cli access to bucket
            if $( $awscli $add_s3_api_flags s3 ls $backup_bucket > /dev/null ) ; then
                printf " - Success listing backup bucket\n"
            else 
                printf "\n[X] Failed to list bucket contents... \nCheck aws cli credentials: aws configure"
                handle_early_exit_on_backup "\naws $add_s3_api_flags s3 ls $backup_bucket \n" true
            fi
        fi;

        # Incremental Job checks
        if $incremental; then
            if [ "$today" == "auto_most_recent" ]; then 
                auto_select_most_recent_backup_for_incremental
            fi
            
            # Validate $today is a non-empty value
            if [ -z "$today" ]; then 
                handle_early_exit_on_backup "\nUndefined folder to increment on ($backup_bucket/$today)\nTry --incremental <folder> or --incremental auto_most_recent \n" true
            fi

            # Cant continue if this folder (which represents a full backup) does not exists
            if [ $cloud == "gcp" ]; then
                if [[ $( $gsutil ls $backup_bucket/$today | head ) ]]; then  
                    printf " - Full backup directory exists\n"; 
                else 
                    handle_early_exit_on_backup "[X] Full backup directory ($backup_bucket/$today) DOES NOT exist in GCS \nCheck - $gsutil ls $backup_bucket/$today | head \n\n" true;   
                fi  
            else
                if [[ $( $awscli $add_s3_api_flags s3 ls $backup_bucket/$today/ | head ) ]]; then  
                    printf " - Full backup directory exists\n"; 
                else 
                    handle_early_exit_on_backup "[X] Full backup directory ($backup_bucket/$today) DOES NOT exist in S3 \nCheck - aws $add_s3_api_flags s3 ls $backup_bucket/$today | head \n\n" true; 
                fi  
            fi;
        fi
    else 
        handle_early_exit_on_backup "Invalid Variable storage: $storage" true
    fi
}

# Used when "--incremental auto_most_recent" passed in during incremental backups
# This function identifies which backup directory is the most recent and sets today=x so that the incremental backup applies to said last full backup
# For LocalStorage: based on ls -td <backup_dir> | head -n 1
# For S3: using the awscli/gsutil, compare the dates of the backup folders restoreS3.job file to find the most recent S3 backup to increment ont top off
auto_select_most_recent_backup_for_incremental() {

    printf " - Searching for most recent backup ...."
    if [ $storage == "LocalStorage" ]; then 
         
        # Example: find /mnt/backups/ -mindepth 1 -maxdepth 1 -type d ! -exec test -f "{}/.auto-delete-via-retention" \; -printf "%T@ %p\n" | sort -nr | awk '{print $2}' | head -n 1
        if [ -f "${backup_location}${retention_file_lock_name}-$pm" ] ; then
            handle_early_exit_on_backup "\n[!!!] ${backup_location}${retention_file_lock_name}-$pm exists. are multiple backups running or was there an error? Manually resolve before retrying\n" true
        fi

        # Primary node is in the middle of applying retention policy, distorting the most recent backup
        if [ -f "${backup_location}${retention_file_lock_name}-pm1" ] ; then
            most_recent_backup="$(head -n1 "${backup_location}${retention_file_lock_name}-pm1" | awk '{print $2}')"
        else
            most_recent_backup=$(find "$backup_location" -mindepth 1 -maxdepth 1 -type d -printf "%T@ %p\n" | sort -nr | awk '{print $2}' | head -n 1)
        fi

        # Debug messaging to understand the most recent backup modified times
        if ! $quiet; then
            echo ""
            find "$backup_location" -mindepth 1 -maxdepth 1 -type d -printf "%T@ %p\n" | sort -nr 
        fi

        # If no backup found, exit
        if [[ -z "$most_recent_backup" ]]; then
            handle_early_exit_on_backup "\n[!!!] No backup found to increment in '$backup_location', please run a full backup or define a folder that exists --incremental <folder>\n" true
        else 
            today=$(basename $most_recent_backup 2>/dev/null)
        fi

    elif [ $storage == "S3" ]; then
        current_date=$(date +%s)
        backups=$(s3ls $backup_bucket)
        most_recent_backup=""
        most_recent_backup_time_diff=$((2**63 - 1));

        while IFS= read -r line; do
            
            folder=$(echo "$line" | awk '{print substr($2, 1, length($2)-1)}')
            date_time=$(s3ls "${backup_bucket}/${folder}/restore --recursive" |  awk '{print $1,$2}')

            if [[ -n "$date_time" ]]; then

                # Parse the date
                backup_date=$(date -d "$date_time" +%s)
                
                # Calculate the difference in days
                time_diff=$(( (current_date - backup_date) ))    
                # echo "date_time: $date_time"
                # echo "backup_date: $backup_date"
                # echo "time_diff: $time_diff"
                # echo "days_diff: $((time_diff / (60*60*24) ))"

                if [ $time_diff -lt $most_recent_backup_time_diff ]; then
                    most_recent_backup=$folder
                    most_recent_backup_time_diff=$time_diff
                fi
            fi
            printf "."
        done <<< "$backups"
       
        # printf "\n\nMost Recent: $most_recent_backup \n"
        # printf "Time Diff: $most_recent_backup_time_diff \n"

        if [[ -z "$most_recent_backup" ]]; then
            handle_early_exit_on_backup "\n[!!!] No backup found to increment, please run a full backup or define a folder that exists --incremental <folder>\n" true
            exit 1;
        else 
            today=$most_recent_backup
        fi
    fi
    printf " selected: $today \n"
}

apply_backup_retention_policy() {
    
    if [ "$retention_days" -eq 0 ]; then
        printf " - Skipping Backup Retention Policy\n"
        return 0;
    fi

    # PM1 is in charge of deleting the backup 
    # Rest of the nodes should wait for the delete to be over if it finds the lock file
    applying_retention_start=$(date +%s)
    printf "\nApplying Backup Retention Policy\n"
    if [ "$pm" == "pm1" ] || [ -n "${PM_FORCE_DELETE-}" ] ; then 
    
        retention_file_lock_name="${retention_file_lock_name}-$pm"
        
        if [ $storage == "LocalStorage" ]; then
            
            # Create a lock file to prevent other PMs from deleting backups
            touch "${backup_location}${retention_file_lock_name}"
            echo "most_recent_backup: $most_recent_backup" >> "${backup_location}${retention_file_lock_name}"
            printf " - Created: ${backup_location}${retention_file_lock_name}\n"

            # example1: find /mnt/backups/ -mindepth 1 -maxdepth 1 -type d -name "*" -mmin +10 -exec echo "{}" \;
            # example2: find /mnt/backups/ -mindepth 1 -maxdepth 1 -type d -name "*" -mmin +10 -exec sh -c 'printf " - Deleting: {} \n" && echo "{}" >> "/mnt/backups/.delete-lock-pm1" && rm -rf "{}"' \;
            # find "$backup_location" -mindepth 1 -maxdepth 1 -type d -name "*" -mmin +$retention_days -exec sh -c 'printf " - Deleting: {} \n" && echo "{}" >> "'"${backup_location}${retention_file_lock_name}"'" && rm -rf "{}"' \;
            find "$backup_location" -mindepth 1 -maxdepth 1 -type d -name "*" -mtime +$retention_days -exec sh -c 'printf " - Deleting: {} \n" && echo "{}" >> "'"${backup_location}${retention_file_lock_name}"'" && rm -rf "{}"' \;
            
            # Cleanup the lock file
            rm "${backup_location}${retention_file_lock_name}"
            printf " - Deleted: ${backup_location}${retention_file_lock_name}\n"

        elif [ $storage == "S3" ]; then 

            # Create a lock file to prevent other PMs from deleting backups
            echo "most_recent_backup: $most_recent_backup" >> "${retention_file_lock_name}"
            s3cp "${retention_file_lock_name}" "${backup_bucket}/${retention_file_lock_name}"

            current_date=$(date +%s)
            backups=$(s3ls $backup_bucket)
        
            while IFS= read -r line; do

                delete_backup=false
                folder=$(echo "$line" | awk '{print substr($2, 1, length($2)-1)}')
                date_time=$(s3ls "${backup_bucket}/${folder}/restore --recursive" |  awk '{print $1,$2}')
        
                if [[ -n "$date_time" ]]; then

                    # Parse the date
                    backup_date=$(date -d "$date_time" +%s)
                    
                    # Calculate the difference in days
                    days_diff=$(( (current_date - backup_date) / (60*60*24) ))    
                    # echo "line: $line"
                    # echo "date_time: $date_time"
                    # echo "backup_date: $backup_date"
                    # echo "days_diff: $days_diff"

                    if [ $days_diff -gt "$retention_days" ]; then
                        delete_backup=true
                    fi
                else
                    delete_backup=true
                fi
                
                if $delete_backup; then
                    echo "${backup_bucket}/${folder}" >> "${retention_file_lock_name}"
                    s3cp "${retention_file_lock_name}" "${backup_bucket}/${retention_file_lock_name}"
                    s3rm "${backup_bucket}/${folder}"
                    #printf "\n   - Deleting: ${backup_bucket}/${folder}"
                fi
                printf "."
            done <<< "$backups"

            # Cleanup the lock file
            s3rm "${backup_bucket}/${retention_file_lock_name}"
        fi
        
        applying_retention_end=$(date +%s)
        printf "Done $(human_readable_time $((applying_retention_end-applying_retention_start))) \n" 
    else

        local retry_limit=2160
        local retry_counter=0
        local retry_sleep_interval=5
        if [ $storage == "LocalStorage" ]; then
            
            if [ -f "${backup_location}${retention_file_lock_name}-pm1" ]; then
                printf " - Waiting for PM1 to finish deleting backups (max: $retry_limit intervals of $retry_sleep_interval sec ) ... \n"
            fi;
            while [ -f "${backup_location}${retention_file_lock_name}-pm1" ]; do
                
                if [ -f "${backup_location}${retention_file_lock_name}-pm1" ]; then
                    printf " - Waiting on PM1 to finish deleting %s \n" "$(tail -n 1 "${backup_location}${retention_file_lock_name}-pm1" 2>/dev/null )"
                fi;
                
                if [ $retry_counter -ge $retry_limit ]; then
                    handle_early_exit_on_backup "\n [!] PM1 lock file still exists after $retry_limit sec, deleting backups via retention policy might have failed or taking too long, please investigate, exiting\n" true
                fi
                sleep $retry_sleep_interval
                ((retry_counter++))
            done
        elif [ $storage == "S3" ]; then 
            if [[ $(s3ls "${backup_bucket}/${retention_file_lock_name}")  ]]; then
                printf " - Waiting for PM1 to finish deleting backups (max: $retry_limit intervals of $retry_sleep_interval sec ) ... \n"
            fi;
            while [[ $(s3ls "${backup_bucket}/${retention_file_lock_name}")  ]]; do
                if [[ $(s3ls "${backup_bucket}/${retention_file_lock_name}")  ]]; then
                    printf " - Waiting on PM1 to finish deleting %s \n" "$( s3cp "${backup_bucket}/${retention_file_lock_name}" "-" | tail -n 1 2>/dev/null )"
                fi;
                if [ $retry_counter -ge $retry_limit ]; then
                    handle_early_exit_on_backup "\n [!] PM1 lock file still exists after $retry_limit sec, deleting backups via retention policy might have failed or taking too long, please investigate, exiting\n" true
                fi
                sleep $retry_sleep_interval
                ((retry_counter++))
            done
        fi
        applying_retention_end=$(date +%s)
        printf "Done $(human_readable_time $((applying_retention_end-applying_retention_start))) \n" 
    fi

}

cs_read_only_wait_loop() {
    retry_limit=1800
    retry_counter=0
    read_only_mode_message="DBRM is currently Read Only!"
    current_status=$(dbrmctl status);
    printf " - Confirming CS Readonly...                          "
    while [ "$current_status" != "$read_only_mode_message" ]; do
        sleep 1
        printf "."
        current_status=$(dbrmctl status);
        if [ $? -ne 0 ]; then
            handle_early_exit_on_backup "\n[!] Failed to get dbrmctl status\n\n" 
        fi
        if [ $retry_counter -ge $retry_limit ]; then
            handle_early_exit_on_backup "\n[!] Set columnstore readonly wait retry limit exceeded: $retry_counter \n\n"
        fi

        ((retry_counter++))
    done
    printf "Done\n\n"
}

# Having columnstore offline is best for non-volatile backups  
# If online - issue a flush tables with read lock and set DBRM to readonly
issue_write_locks() 
{
    if [ $mode == "indirect" ] || $skip_locks; then printf "\n"; return; fi;
    if ! $skip_mdb && ! pidof mariadbd > /dev/null; then 
        handle_early_exit_on_backup "\n[X] MariaDB is offline ... Needs to be online to issue read only lock and to run mariadb-backup \n\n" true; 
    fi;

    printf "\nLocks \n"; 
    # Pre 23.10.2 CS startreadonly does not exist - so poll cpimports added to protect them
    # Consider removing when 23.10.x is EOL
    if ! $skip_polls; then 
        poll_check_no_active_sql_writes
        poll_check_no_active_cpimports
    fi;

    ORIGINAL_READONLY_STATUS=$(mariadb -qsNe "select @@read_only;")
    # Set MariaDB Server ReadOnly Mode
    if pidof mariadbd > /dev/null; then
        printf " - Issuing read-only lock to MariaDB Server ...      ";
        if mariadb -e "FLUSH TABLES WITH READ LOCK;"; then
            mariadb -e "set global read_only=ON;"
            read_lock=true
            printf " Done\n"; 
        else    
            handle_early_exit_on_backup "\n[X] Failed issuing read-only lock\n" 
        fi 
    fi
    
    if [ $pm == "pm1" ]; then 
        
        # Set Columnstore ReadOnly Mode
        startreadonly_exists=$(dbrmctl -h 2>&1 | grep "startreadonly")
        printf " - Issuing read-only lock to Columnstore Engine ...   ";
        if ! $columnstore_online; then 
            printf "Skip since offline\n";
        
        elif [[ -n "$startreadonly_exists" ]]; then
            printf "startreadonly "
            if dbrmctl startreadonly ; then
                if ! $skip_polls; then 
                    cs_read_only_wait_loop
                fi
            else
                handle_early_exit_on_backup "\n[X] Failed issuing columnstore BRM lock via dbrmctl startreadonly\n" 
            fi;

        elif [ $DBROOT_COUNT == "1" ]; then 
            if ! dbrmctl readonly ; then
                handle_early_exit_on_backup "\n[X] Failed issuing columnstore BRM lock via dbrmctl readonly \n" 
            fi
        else 
            if command -v mcs &> /dev/null && command -v jq &> /dev/null ; then
                if ! mcs cluster set mode --mode readonly  | jq -r tostring ; then
                    handle_early_exit_on_backup "\n[X] Failed issuing columnstore BRM lock via cmapi\n" 
                fi
            else
                # Older CS versions do not have mcs cli
                cmapiResponse=$(curl -s -X PUT https://127.0.0.1:8640/cmapi/0.4.0/cluster/mode-set --header 'Content-Type:application/json' --header "x-api-key:$cmapi_key" --data '{"timeout":20, "mode": "readonly"}' -k);
                if [[ $cmapiResponse == '{"error":'* ]] ; then 
                    handle_early_exit_on_backup "\n[X] Failed issuing columnstore BRM lock via cmapi\n" 
                else
                    printf "$cmapiResponse \n";
                fi
            fi
        fi
    else
        cs_read_only_wait_loop
    fi
}

poll_check_no_active_sql_writes() {
    poll_active_sql_writes_start=$(date +%s)
    printf " - Polling for active Writes ...                      "
    query="SELECT COUNT(*) FROM information_schema.processlist where user != 'root' AND command = 'Query' AND ( info LIKE '% INSERT %' OR info LIKE '% UPDATE %' OR info LIKE '% DELETE %' OR info LIKE '% LOAD DATA %');"
    attempts=0
    no_writes=false
    while [ "$attempts" -lt "$max_poll_attempts" ]; do
        active_writes=$( mariadb -s -N -e "$query" )
        if [ "$?" -ne 0 ]; then
            handle_early_exit_on_backup "\n$active_writes\n[X] Failed to poll for active writes\n" true
        fi

        if [ "$active_writes" -le 0 ]; then
            poll_active_sql_writes_end=$(date +%s)
            printf "Done $(human_readable_time $((poll_active_sql_writes_start-poll_active_sql_writes_end))) \n" 
            no_writes=true
            break
        else
            
            if ! $quiet; then 
                printf "\nActive write operations detected: $active_writes\n"; 
                mariadb -e "select ID,USER,TIME,LEFT(INFO, 50) from information_schema.processlist where user != 'root' AND command = 'Query' AND ( info LIKE '% INSERT %' OR info LIKE '% UPDATE %' OR info LIKE '% DELETE %' OR info LIKE '% LOAD DATA %');"
            else 
                printf "."
            fi;
            sleep "$poll_interval"
            ((attempts++))
        fi
    done

    if ! $no_writes; then
        mariadb -e "select ID,USER,TIME,INFO from information_schema.processlist where user != 'root' AND command = 'Query' AND ( info LIKE '% INSERT %' OR info LIKE '% UPDATE %' OR info LIKE '% DELETE %' OR info LIKE '% LOAD DATA %');"   
        handle_early_exit_on_backup "\n[X] Exceeded poll_max_wait: $poll_max_wait minutes\nActive write operations detected: $active_writes \n" true
    fi;
    
}

poll_check_no_active_cpimports() {
    poll_active_cpimports_start=$(date +%s)
    printf " - Polling for active cpimports ...                   "
    attempts=0
    no_cpimports=false
    if ! $columnstore_online ; then printf "Skip since offline\n"; return ; fi;
    while [ "$attempts" -lt "$max_poll_attempts" ]; do
        active_cpimports=$(viewtablelock 2>/dev/null )
        if [[ "$active_cpimports" == *"No tables are locked"* ]]; then
            poll_active_cpimports_end=$(date +%s)
            printf "Done $(human_readable_time $((poll_active_cpimports_end-poll_active_cpimports_start))) \n" 
            no_cpimports=true
            break
        else 
            printf "."
            if ! $quiet; then printf "\n$active_cpimports\n - Waiting for cpimports to finish ..."; fi;
            sleep "$poll_interval"
            ((attempts++))
        fi
    done

    if ! $no_cpimports; then
        handle_early_exit_on_backup "\n[X] Exceeded poll_max_wait: $poll_max_wait minutes\nActive cpimports\n$active_cpimports \n" true
    fi;
}

run_save_brm() {

    if $skip_save_brm || [ $pm != "pm1" ] || ! $columnstore_online || [ $mode == "indirect" ]; then return; fi;

    printf "\nBlock Resolution Manager\n"
    local tmp_dir="/tmp/DBRMbackup-$today"
    if [ ! -d "$tmp_dir" ]; then mkdir -p $tmp_dir; fi;

    # Copy extent map locally just in case save_brm fails
    if [ $storage == "LocalStorage" ]; then
        printf " - Backing up DBRMs @ $tmp_dir ... " 
        cp -R $DBRM_PATH/* $tmp_dir
        printf " Done \n" 
    elif [ $storage == "S3" ]; then 

        printf " - Backing up minimal DBRMs @ $tmp_dir ... "  
        # Base Set
        smcat /data1/systemFiles/dbrm/BRM_saves_current 2>/dev/null > $tmp_dir/BRM_saves_current 
        smcat /data1/systemFiles/dbrm/BRM_saves_journal 2>/dev/null > $tmp_dir/BRM_saves_journal 
        smcat /data1/systemFiles/dbrm/BRM_saves_em   2>/dev/null > $tmp_dir/BRM_saves_em 
        smcat /data1/systemFiles/dbrm/BRM_saves_vbbm 2>/dev/null > $tmp_dir/BRM_saves_vbbm 
        smcat /data1/systemFiles/dbrm/BRM_saves_vss  2>/dev/null > $tmp_dir/BRM_saves_vss 

        # A Set
        smcat /data1/systemFiles/dbrm/BRM_savesA_em   2>/dev/null > $tmp_dir/BRM_savesA_em 
        smcat /data1/systemFiles/dbrm/BRM_savesA_vbbm 2>/dev/null > $tmp_dir/BRM_savesA_vbbm 
        smcat /data1/systemFiles/dbrm/BRM_savesA_vss  2>/dev/null > $tmp_dir/BRM_savesA_vss 
        
        # B Set
        smcat /data1/systemFiles/dbrm/BRM_savesB_em   2>/dev/null > $tmp_dir/BRM_savesB_em 
        smcat /data1/systemFiles/dbrm/BRM_savesB_vbbm 2>/dev/null > $tmp_dir/BRM_savesB_vbbm 
        smcat /data1/systemFiles/dbrm/BRM_savesB_vss  2>/dev/null > $tmp_dir/BRM_savesB_vss 
        printf " Done \n" 
    fi
    
    printf " - Saving BRMs... \n"
    brm_owner=$(stat -c "%U" $DBRM_PATH/)
    if sudo -u $brm_owner /usr/bin/save_brm ; then 
        rm -rf $tmp_dir
    else  
        printf "\n Failed: save_brm error - see /var/log/messages - backup @ $tmpDir \n\n"; 
        handle_early_exit_on_backup 
    fi
    
}

# Example: s3sync <from> <to> <success_message> <failed_message>
# Example: s3sync s3://$bucket $backup_bucket/$today/columnstoreData "Done - Columnstore data sync complete"
s3sync() {

    local from=$1
    local to=$2
    local success_message=$3
    local failed_message=$4
    local retries=${5:-0}
    local cmd=""
    
    if [ $cloud == "gcp" ]; then
        cmd="$gsutil $extra_s3_args -m rsync -r -d $from $to"

        # gsutil throws WARNINGS if not directed to /dev/null
        if $quiet; then cmd+="  2>/dev/null"; fi
        eval "$cmd"
    else 
        # Default AWS
        cmd="$awscli $extra_s3_args $add_s3_api_flags s3 sync $from $to"
        $cmd
    fi

    local exit_code=$?

    if [ $exit_code -eq 0 ]; then
        if [ -n "$success_message" ]; then printf "$success_message"; fi;
    else
        if [ $retries -lt $RETRY_LIMIT ]; then 
            echo "$cmd"
            echo "Retrying: $retries"
            sleep 1;
            s3sync "$1" "$2" "$3" "$4" "$(($retries+1))"
        else
            if [ -n "$failed_message" ]; then printf "$failed_message"; fi;
            echo "Was Trying:   $cmd"
            echo "exiting ..."
            handle_early_exit_on_backup
        fi
    fi
}

# Example: s3cp <from> <to>
s3cp() {
    local from=$1
    local to=$2
    local cmd=""
    
    if [ $cloud == "gcp" ]; then
        cmd="$gsutil $extra_s3_args cp $from $to"
    else 
        # Default AWS
        cmd="$awscli $extra_s3_args $add_s3_api_flags s3 cp $from $to"
    fi;

    $cmd

}

# Example: s3rm <path to delete> 
s3rm() {
    local path=$1
    local cmd=""
    
    if [ $cloud == "gcp" ]; then
        cmd="$gsutil $extra_s3_args -m rm -r $path"
    else 
        # Default AWS
        cmd="$awscli $extra_s3_args $add_s3_api_flags s3 rm $path  --recursive"
    fi;

    $cmd
}

# Example: s3ls <path to list> 
s3ls() {
    local path=$1
    local cmd=""
    
    if [ $cloud == "gcp" ]; then
        cmd="$gsutil ls $path"
    else 
        # Default AWS
        cmd="$awscli $add_s3_api_flags s3 ls $path"
    fi;

    $cmd
}

clear_read_lock() {

    if [ $mode == "indirect" ] || $skip_locks; then return; fi;

    printf "\nClearing Locks\n"; 
    # Clear MDB Lock 
    if pidof mariadbd > /dev/null && [ $read_lock ]; then
        printf " - Clearing read-only lock on MariaDB Server ...     ";
        if mariadb -e "UNLOCK TABLES;" && mariadb -qsNe "set global read_only=$ORIGINAL_READONLY_STATUS;"; then
            read_lock=false;
            printf " Done\n"
        else 
            handle_early_exit_on_backup "\n[X] Failed clearing readLock\n" true
        fi
    fi

    if [ $pm == "pm1" ]; then
        # Clear CS Lock
        printf " - Clearing read-only lock on Columnstore Engine ...  "; 
        if ! $columnstore_online; then 
            printf "Skip since offline\n"
        elif [ $DBROOT_COUNT == "1" ]; then 
            if dbrmctl readwrite ; then
                printf " ";
            else 
                handle_early_exit_on_backup "\n[X] Failed clearing columnstore BRM lock via dbrmctl\n" true;
            fi
        elif command -v mcs &> /dev/null && command -v jq &> /dev/null ; then
            if ! mcs cluster set mode --mode readwrite | jq -r tostring ;then
                handle_early_exit_on_backup "\n[X] Failed issuing columnstore BRM lock via cmapi\n"; 
            fi
        elif curl -s -X PUT https://127.0.0.1:8640/cmapi/0.4.0/cluster/mode-set --header 'Content-Type:application/json' --header "x-api-key:$cmapi_key" --data '{"timeout":20, "mode": "readwrite"}' -k  ; then 
            printf " \n"
        else
            handle_early_exit_on_backup "\n[X] Failed clearing columnstore BRM lock\n" true;
        fi
    fi
    
} 

# handle_ called when certain checks/functionality fails
handle_failed_dependencies() {
    printf "\nDependency Checks Failed: $1"
    alert "$1"
    exit 1;
}

# first argument is the error message
# 2nd argument true = skip clear_read_lock, false= do not skip
handle_early_exit_on_backup() {   
    skip_clear_locks=${2:-false}
    if ! $skip_clear_locks; then clear_read_lock; fi;
    printf "\nBackup Failed: $1\n"
    alert "$1"
    exit 1;
}

handle_early_exit_on_restore() {
    printf "\nRestore Failed: $1\n"
    alert "$1"
    exit 1;
}

handle_ctrl_c_backup() {
    echo "Ctrl+C captured. handle_early_exit_on_backup..."
    handle_early_exit_on_backup
}

handle_ctrl_c_dbrm_backup() {
    echo "Ctrl+C captured. handle_early_exit_on_dbrm_backup..."
    handle_early_exit_on_dbrm_backup
}

# $1 is the error message
# $2 is false by default, meaning you need to clear the read lock, true means locks dont exist or not needed to be cleared
handle_early_exit_on_dbrm_backup() {
    $skip_clear_locks=${2:-false}
    if ! $skip_clear_locks; then clear_read_lock; fi;
    printf "\nDBRM Backup Failed: $1\n"
    alert "$1"
    exit 1;
}

alert() {
    # echo "Not implemented yet"
    # slack, email, webhook curl endpoint etc.
    return;
}

# initiate_rsync uses this to wait and monitor all running jobs
# deepParallelRsync uses this to limit the parallelism to a max of $PARALLEL_FOLDERS times $PARALLEL_THREADS
wait_on_rsync()
{
    local visualize=$1
    local pollSleepTime=$2
    local concurrentThreshHold=$3
    local dbrootToSync=$4
    local rsyncInProgress="$(pgrep 'rsync -a' | wc -l )";
    local total=0

    local w=0;
    while [ $rsyncInProgress -gt "$concurrentThreshHold" ]; do 
        if ! $quiet && $visualize && [ $(($w % 10)) -eq 0 ]; then 
            if [[ $total == 0 ]]; then 
                total=$(du -sh /var/lib/columnstore/data$dbrootToSync/) 
            else  
                echo -E "$rsyncInProgress rsync processes running ... seconds: $w"
                echo "Status: $(du -sh $backup_location$today/data$dbrootToSync/)"
                echo "Goal:   $total"
                echo "Monitor rsync: ps aux | grep 'rsync -a' | grep -vE 'color|grep' "
            fi;
        fi
        
        sleep $pollSleepTime
        rsyncInProgress="$(pgrep 'rsync -a' | wc -l )";
        ((w++))
    done
}

# retry_failed_rsync uses this to retry rsync for any failed rsyncs
# @param $1 - retry depth/attempt
retry_failed_rsync() {
    
    local next_depth=$(( $1 + 1 ))
    local retry_rsync_log="mcs-failed-rsync-attempt-$1.log"
    local new_retry_rsync_log="mcs-failed-rsync-attempt-$next_depth.log"

    # confirm retry depth
    if [ -z "$1" ]; then
        echo "No retry depth specified, exiting"
        return 1
    fi

    # Check if max retry depth reached
    if [ "$1" -gt "$max_retries" ]; then
        echo "Max retry dept of $max_retries reached ... exiting"
        return 1
    fi

    # Check if retry log exists
    if [ -f "$retry_rsync_log" ]; then
        printf "\n%-s\n" "Retrying failed rsyncs... Attempt #$1"
        while IFS= read -r line; do
            source=$(echo "$line" | sed -n 's/.*Failed Source: \(.*\)  Copy to Location:.*/\1/p')
            target=$(echo "$line" | sed -n 's/.*Copy to Location: \(.*\)$/\1/p')
            # Remove folder from target else rsync will nest folders improperly
            target_base=$(dirname "$target")

            # additional_rsync_flags
            printf " - rsync $parallel_rsync_flags %-s %-s  ..." "$source" "$target_base"
            if rsync $parallel_rsync_flags "$source" "$target_base"; then
                printf " Done\n"
            else
                echo "Failed Source: $source  Copy to Location: $target" >> "$new_retry_rsync_log"
                printf " Failed\n"
            fi

        done < "$retry_rsync_log"
    else
        echo "Retry log ($retry_rsync_log) does not exist   ... returning error"
        return 1
    fi
    
    # If any rsync failed, retry again
    if [ -f "$new_retry_rsync_log" ]; then
        retry_failed_rsync $next_depth
    fi

    # Cleanup 
    if [ -f $retry_rsync_log ]; then rm $retry_rsync_log; fi;
}

initiate_rsyncs() {
    local dbrootToSync=$1
    retry_rsync_log="mcs-failed-rsync-attempt-1.log"
    max_retries=10
    if [ -f $retry_rsync_log ]; then rm -rf $retry_rsync_log; fi;
    
    # Separate flags for parallel rsync from additional_rsync_flags - never have verbose rsync for parallel
    parallel_rsync_flags=" -a "
    if $incremental ; then parallel_rsync_flags+=" --inplace --no-whole-file --delete"; fi;
    
    deepParallelRsync /var/lib/columnstore/data$dbrootToSync 1 $DEPTH data$dbrootToSync &
    sleep 2;
    #jobs
    wait_on_rsync true 2 0 $dbrootToSync
    wait

    # Check if any rsync failed and retry rsync for that folder
    if [ -f $retry_rsync_log ]; then
        retry_failed_rsync 1
        if [ $? -eq 0 ]; then
            if [ -f $retry_rsync_log ]; then rm $retry_rsync_log; fi;
        fi
        
        # if a file with mcs-failed-rsync-attempt-*.log exists, report it
        if ls mcs-failed-rsync-attempt-*.log 1> /dev/null 2>&1; then
            printf "\n%-s\n" "Could not rsync some directories, please manually triage ... $(date)"
            # Print the file name and the contents of the log files in bash
            for file in mcs-failed-rsync-attempt-*.log; do
                mv -f $file $file.$today
                printf "%-s\n" "File: $file.$today"
                while IFS= read -r line; do
                    source=$(echo "$line" | sed -n 's/.*Failed Source: \(.*\)  Copy to Location:.*/\1/p')
                    target=$(echo "$line" | sed -n 's/.*Copy to Location: \(.*\)$/\1/p')
                    printf "   %-s:  Source Size: %-s     Failed Target Size: %-s \n" "$(date)" "$(du -sb $source)" "$(du -sb $target)"
 
                done < "$file.$today"
            done
            handle_early_exit_on_backup "\n\n[!] Rsync failed for some directories, please check the logs above\nConsider an incremental backup to continue where it left off: --incremental auto_most_recent \n"
        fi
    fi
}

# A recursive function that increments depthCurrent+1 each directory it goes deeper and issuing rsync on each directory remaining at the target depth  
# Example values:   
#   path: /var/lib/columnstore/data1
#   depthCurrent: 1
#   depthTarget: 3
#   depthCurrent: data1
deepParallelRsync() {
    path=$1
    depthCurrent=$2
    depthTarget=$3
    relativePath=$4
    # echo "DEBUG: 
    # path=$1
    # depthCurrent=$2
    # depthTarget=$3
    # relativePath=$4"

    for fullFilePath in $path/*; do

        local fileName=$(basename $fullFilePath)
        # echo "DEBUG - filename: $fileName"

        # If a directory, keep going deeper until depthTarget reached
        if [ -d $fullFilePath ]; then
            #echo "DEBUG - Directory $file"
            mkdir -p $backup_location$today/$relativePath/$fileName

            if [ $depthCurrent -ge $depthTarget ]; then
                #echo "DEBUG - copy to relative: $backup_location$today/$relativePath/"
                if ls $fullFilePath | xargs -P $PARALLEL_THREADS -I {} rsync $parallel_rsync_flags $fullFilePath/{} $backup_location$today/$relativePath/$fileName ; then
                    echo "   + Completed: $backup_location$today/$relativePath/$fileName"
                else 
                    echo "Failed Source: $fullFilePath  Copy to Location: $backup_location$today/$relativePath/$fileName" >> $retry_rsync_log
                    echo "Failed Source: $fullFilePath  Copy to Location: $backup_location$today/$relativePath/$fileName  ... added to $retry_rsync_log"
                fi
                
            else
                # echo "DEBUG - Fork Deeper - $fullFilePath "    
                wait_on_rsync false "0.5" $PARALLEL_FOLDERS
                # Since target depth not reached, recursively call for each directory 
                deepParallelRsync $fullFilePath "$((depthCurrent+1))" $depthTarget "$relativePath/$fileName" &
            fi

        # If a file, rsync right away
        elif [ -f $fullFilePath ]; then
            rsync $additional_rsync_flags $fullFilePath $backup_location$today/$relativePath/

        # If filename is * then the directory is empty 
        elif [ "$fileName" == "*" ]; then 
            # echo "DEBUG - Skipping $relativePath - empty";
            continue
        else
            printf "\n\n Warning - Unhandled - $fullFilePath \n\n";
        fi
    done
    wait
}

human_readable_time() {
    local total_seconds=$1
    local days=$((total_seconds / 86400))
    local hours=$(( (total_seconds % 86400) / 3600 ))
    local minutes=$(( (total_seconds % 3600) / 60 ))
    local seconds=$(( total_seconds % 60 ))

    local output=""

    # Special case for 0 seconds
    if (( total_seconds == 0 )); then
        printf "<1 sec"
        return
    fi

    if (( days > 0 )); then
        output+=" ${days} days"
    fi
    if (( hours > 0 )); then
        output+=" ${hours} hours"
    fi
    if (( minutes > 0 )); then
        output+=" ${minutes} min"
    fi
    if (( seconds > 0 || total_seconds == 0 )); then
        output+=" ${seconds} sec"
    fi

    printf "${output}" | xargs
}

run_backup() {  
    trap handle_ctrl_c_backup SIGINT
    backup_start=$(date +%s) 
    if [ $storage == "LocalStorage" ]; then 
        if [ $backup_destination == "Local" ]; then

            printf "\nLocal Storage Backup\n"

            # Check/Create Directories
            printf " - Creating Backup Directories...    "
            if [[ -n "$compress_format" ]]; then
                mkdir -p $backup_location$today
            else
                mkdir -p $backup_location$today/mysql
                mkdir -p $backup_location$today/configs 
                mkdir -p $backup_location$today/configs/mysql
            
                # Check/Create CS Data Directories
                i=1
                while [ $i -le $DBROOT_COUNT ]; do
                    if [[ $ASSIGNED_DBROOT == "$i" || $HA == true ]]; then mkdir -p $backup_location$today/data$i ; fi
                    ((i++))
                done 
            fi
            printf " Done\n"   
            
            # Backup Columnstore data
            columnstore_backup_start=$(date +%s)
            i=1
            while [ $i -le $DBROOT_COUNT ]; do
                if [[ $ASSIGNED_DBROOT == "$i" || $HA == true ]]; then 
                    if [[ -n "$compress_format" ]]; then
                        # For compression keep track of dirs & files to include and compress/stream in the end - $compress_paths
                       compress_paths="/var/lib/columnstore/data$i/* "  

                    elif $parallel_rsync ; then 
                        printf " - Parallel Rsync CS Data$i...       \n"
                        initiate_rsyncs $i
                        columnstore_backup_end=$(date +%s)
                        printf " - Parallel Rsync CS Data$i            Done $(human_readable_time $((columnstore_backup_end-columnstore_backup_start))) \n"       
                    else
                        printf " - Syncing Columnstore Data$i...      "
                        eval "rsync $additional_rsync_flags /var/lib/columnstore/data$i/* $backup_location$today/data$i/ $extra_cmd_args";
                        columnstore_backup_end=$(date +%s)
                        printf " Done $(human_readable_time $((columnstore_backup_end-columnstore_backup_start))) \n" 

                    fi;
                fi
                ((i++))
            done
            

            # Backup MariaDB data
            if [ $ASSIGNED_DBROOT == "1" ]; then
                
                # logic to increment mysql and keep count if we want to backup incremental mysql data
                # i=1
                # latestMysqlIncrement=0
                # while [ $i -le 365 ]; do
                #     if [ -d $backup_location/mysql$i ]; then  ; else latestMysqlIncrement=i; break; fi;
                # done

                # MariaDB backup wont rerun if folder exists - so clear it before running mariadb-backup
                if [ -d $backup_location$today/mysql ]; then 
                    rm -rf $backup_location$today/mysql
                fi

                # Run mariadb-backup
                mariadb_backup_start=$(date +%s)
                if ! $skip_mdb; then
                    if [[ -n "$compress_format" ]]; then
                        printf " - Compressing MariaDB Data...       "
                        mbd_prefix="$backup_location$today/$split_file_mdb_prefix.$compress_format"
                        case $compress_format in
                            pigz)
                                # Handle Cloud 
                                mariadb_backup_start=$(date +%s)
                                if !  mariabackup --user=root --backup --slave-info --stream xbstream --parallel $PARALLEL_THREADS --ftwrl-wait-timeout=$timeout --ftwrl-wait-threshold=999999 --extra-lsndir=/tmp/checkpoint_out 2>>$log_file | pigz -p $PARALLEL_THREADS -c > $mbd_prefix 2>> $log_file; then
                                    handle_early_exit_on_backup "\nFailed  mariabackup --user=root --backup --stream xbstream --parallel $PARALLEL_THREADS --ftwrl-wait-timeout=$timeout --ftwrl-wait-threshold=999999 --extra-lsndir=/tmp/checkpoint_out 2>>$log_file | pigz -p $PARALLEL_THREADS -c > $mbd_prefix 2>> $log_file \n"
                                fi
                                mariadb_backup_end=$(date +%s)
                                printf " Done @ $mbd_prefix\n"
                                ;;
                            *)  # unknown option
                                handle_early_exit_on_backup "\nUnknown compression flag: $compress_format\n" 
                        esac
                    else
                        printf " - Copying MariaDB Data...           "
                        if eval "mariadb-backup --backup --slave-info --target-dir=$backup_location$today/mysql --user=root $extra_cmd_args" ; then  
                            mariadb_backup_end=$(date +%s)
                            printf " Done $(human_readable_time $((mariadb_backup_end-mariadb_backup_start))) \n" 
                        else 
                            handle_early_exit_on_backup "\n Failed: mariadb-backup --backup --target-dir=$backup_location$today/mysql --user=root\n"; 
                        fi
                    fi
                else 
                    echo "[!] Skipping mariadb-backup"
                fi

                # Backup CS configurations
                if [[ ! -n "$compress_format" ]]; then
                    printf " - Copying Columnstore Configs...    "
                    eval "rsync $additional_rsync_flags $CS_CONFIGS_PATH/Columnstore.xml $backup_location$today/configs/ $extra_cmd_args"
                    eval "rsync $additional_rsync_flags $STORAGEMANGER_CNF $backup_location$today/configs/ $extra_cmd_args"
                    if [ -f $CS_CONFIGS_PATH/cmapi_server.conf ]; then  eval "rsync $additional_rsync_flags $CS_CONFIGS_PATH/cmapi_server.conf $backup_location$today/configs/ $extra_cmd_args"; fi
                    printf " Done\n"
                else
                    # When restoring - do not want to overwrite the new systems entire Columnstore.xml
                    rm -rf $CS_CONFIGS_PATH/$col_xml_backup
                    rm -rf $CS_CONFIGS_PATH/$cmapi_backup
                    cp $CS_CONFIGS_PATH/Columnstore.xml $CS_CONFIGS_PATH/$col_xml_backup
                    if [ -f $CS_CONFIGS_PATH/cmapi_server.conf ]; then cp $CS_CONFIGS_PATH/cmapi_server.conf $CS_CONFIGS_PATH/$cmapi_backup; fi;
                    compress_paths+=" $CS_CONFIGS_PATH/$col_xml_backup $STORAGEMANGER_CNF $CS_CONFIGS_PATH/$cmapi_backup"
                fi;
            fi

            # Backup MariaDB configurations
            if ! $skip_mdb; then
                if [[ -z "$compress_format" ]]; then
                    printf " - Copying MariaDB Configs...        "
                    mkdir -p $backup_location$today/configs/mysql/$pm/
                    eval "rsync $additional_rsync_flags $MARIADB_SERVER_CONFIGS_PATH/* $backup_location$today/configs/mysql/$pm/ $extra_cmd_args"
                    printf " Done\n"
                else
                    compress_paths+=" $MARIADB_SERVER_CONFIGS_PATH/*"
                fi

                # Backup mariadb server replication details
                slave_status=$(mariadb -e "show slave status\G" 2>/dev/null)
                if [[ $? -eq 0 ]]; then
                    if [[ -n "$slave_status" ]]; then
                        if [[ -z "$compress_format" ]]; then
                            mariadb -e "show slave status\G" | grep -E "Master_Host|Master_User|Master_Port|Gtid_IO_Pos" > $backup_location$today/configs/mysql/$pm/replication_details.txt
                        else
                            mariadb -e "show slave status\G" | grep -E "Master_Host|Master_User|Master_Port|Gtid_IO_Pos" > replication_details.txt
                            compress_paths+=" replication_details.txt"
                        fi
                    fi
                else
                    handle_early_exit_on_backup "[!] - Failed to execute the MariaDB command:   mariadb -e 'show slave status\G' \n"
                fi
            fi

            # Handle compression for Columnstore Data & Configs
            if [[ -n "$compress_format" ]]; then
                cs_prefix="$backup_location$today/$split_file_cs_prefix.$compress_format"
                compressed_split_size="250M";
                case $compress_format in
                    pigz) 
                        printf " - Compressing CS Data & Configs...  "
                        columnstore_backup_start=$(date +%s)
                        if ! eval "tar cf - $compress_paths 2>>$log_file | pigz -p $PARALLEL_THREADS -c > $cs_prefix 2>> $log_file"; then
                            handle_early_exit_on_backup "[!] - Compression Failed \ntar cf - $compress_paths 2>>$log_file | pigz -p $PARALLEL_THREADS -c > $cs_prefix 2>> $log_file \n"
                        fi
                        columnstore_backup_end=$(date +%s)

                        # Create summary info for list backup
                        get_latest_em_from_dbrm_directory "$DBRM_PATH"
                        em_file_name=$(basename "$em_file_full_path")
                        version_prefix=${em_file_name::-3}
                        journal_file=$(ls -la "${subdir_dbrms}/BRM_saves_journal" 2>/dev/null | awk 'NR==1 {print $5}' )
                        vbbm_file=$(ls -la "${subdir_dbrms}/${version_prefix}_vbbm" 2>/dev/null | awk 'NR==1 {print $5}' )
                        vss_file=$(ls -la "${subdir_dbrms}/${version_prefix}_vss" 2>/dev/null | awk 'NR==1 {print $5}' )
                        echo "em_file_created: $em_file_created" >> $backup_location$today/backup_summary_info.txt
                        echo "em_file_name: $em_file_name" >> $backup_location$today/backup_summary_info.txt
                        echo "em_file_size: $em_file_size" >> $backup_location$today/backup_summary_info.txt
                        echo "journal_file: $journal_file" >> $backup_location$today/backup_summary_info.txt
                        echo "vbbm_file: $vbbm_file" >> $backup_location$today/backup_summary_info.txt
                        echo "vss_file: $vss_file" >> $backup_location$today/backup_summary_info.txt                        
                        echo "compression: $compress_format" >> $backup_location$today/backup_summary_info.txt                        

                        if [ -f replication_details.txt ]; then 
                            rm -rf replication_details.txt; 
                        fi

                        printf " Done @ $cs_prefix\n"
                        ;;
                    *)  # unknown option
                        handle_early_exit_on_backup "\nUnknown compression flag: $compress_format\n" 
                esac
            fi

            if $incremental ; then 
                # Log each incremental run
                now=$(date "+%m-%d-%Y %H:%M:%S"); echo "$pm updated on $now" >> $backup_location$today/incrementallyUpdated.txt
                final_message="Incremental Backup Complete"
            else 
                # Create restore job file
                extra_flags=""
                if [[ -n "$compress_format" ]]; then extra_flags+=" -c $compress_format"; fi; 
                if $skip_mdb; then extra_flags+=" --skip-mariadb-backup"; fi; 
                echo "$0 restore -l $today -bl $backup_location -bd $backup_destination -s $storage --dbroots $DBROOT_COUNT -m $mode $extra_flags --quiet" > $backup_location$today/restore.job
            fi

            final_message+=" @ $backup_location$today"
            
        elif [ $backup_destination == "Remote" ]; then
        
            # Check/Create Directories on remote server
            printf "[+] Checking Remote Directories... "
            i=1
            makeDataDirectories=""
            while [ $i -le $DBROOT_COUNT ]; do
                makeDataDirectories+="mkdir -p $backup_location$today/data$i ;"
                ((i++))
            done
            echo $makeDirectories
            ssh $scp " mkdir -p $backup_location$today/mysql ;
            $makeDataDirectories
            mkdir -p $backup_location$today/configs ; 
            mkdir -p $backup_location$today/configs/mysql; 
            mkdir -p $backup_location$today/configs/mysql/$pm "
            printf " Done\n"
            
            printf "[~] Rsync Remote Columnstore Data... \n"
            i=1
            while [ $i -le $DBROOT_COUNT ]; do
                #if [ $pm == "pm$i" ]; then rsync -a $additional_rsync_flags /var/lib/columnstore/data$i/* $scp:$backup_location$today/data$i/ ; fi
                if [ $pm == "pm$i" ]; then 
                    find /var/lib/columnstore/data$i/*  -mindepth 1 -maxdepth 2 | xargs -P $PARALLEL_THREADS -I {} rsync $additional_rsync_flags /var/lib/columnstore/data$i/* $scp:$backup_location$today/data$i/
                fi
                ((i++))
            done
            printf "[+] Done - rsync\n"

            # Backup MariaDB configurations
            if ! $skip_mdb; then
                printf "[~] Backing up MariaDB configurations... \n"
                rsync $additional_rsync_flags $MARIADB_SERVER_CONFIGS_PATH/* $scp:$backup_location$today/configs/mysql/$pm/
                printf "[+] Done - configurations\n"
            fi

            # Backup MariaDB data
            if [ $pm == "pm1" ]; then
                if ! $skip_mdb; then
                    printf "[~] Backing up mysql... \n"
                    mkdir -p $backup_location$today
                    if mariadb-backup --backup --target-dir=$backup_location$today --user=root ; then 
                        rsync -a $backup_location$today/* $scp:$backup_location$today/mysql
                        rm -rf $backup_location$today
                    else 
                        printf "\n Failed: mariadb-backup --backup --target-dir=$backup_location$today --user=root\n\n"; 
                        handle_early_exit_on_backup 
                    fi
                else 
                    echo "[!] Skipping mariadb-backup"
                fi;
                
                # Backup CS configurations
                rsync $additional_rsync_flags $CS_CONFIGS_PATH/Columnstore.xml $scp:$backup_location$today/configs/ 
                rsync $additional_rsync_flags $STORAGEMANGER_CNF $scp:$backup_location$today/configs/
                if [ -f $CS_CONFIGS_PATH/cmapi_server.conf ]; then  
                    rsync $additional_rsync_flags $CS_CONFIGS_PATH/cmapi_server.conf $scp:$backup_location$today/configs/; 
                fi
            fi
        
        
            if  $incremental ; then 
                now=$(date "+%m-%d-%Y +%H:%M:%S")
                ssh $scp "echo \"$pm updated on $now\" >> $backup_location$today/incrementallyUpdated.txt"
                final_message="Incremental Backup Complete"
            else
                # Create restore job file
                ssh $scp "echo './$0 restore -l $today -bl $backup_location -bd $backup_destination -s $storage -scp $scp --dbroots $DBROOT_COUNT' > $backup_location$today/restore.job"
            fi
            final_message+=" @ $scp:$backup_location$today"
        fi

    elif [ $storage == "S3" ]; then

        printf "\nS3 Backup\n"
        # consistency check - wait for assigned journal dir to be empty
        i=1
        j_counts=$(find $cs_journal/data$ASSIGNED_DBROOT/* -type f 2>/dev/null | wc -l)
        max_wait=180
        printf " - Checking storagemanager/journal/data$ASSIGNED_DBROOT/*           "
        while [[ $j_counts -gt 0 ]]; do

            # Handle when max_wait exceeded
            if [ $i -gt $max_wait ]; then 
                printf "[!] Maybe you have orphaned journal files, active writes or an unreachable bucket \n"; 
                handle_early_exit_on_backup "\n[!] max_wait exceeded for $cs_journal/data$ASSIGNED_DBROOT/* to sync with bucket "; 
                ls -la $cs_journal/data$ASSIGNED_DBROOT/*; 
            fi;
        
            # Print every 10 seconds
            if (( $i%10 == 0 )); then 
                printf "\n[!] Not empty yet - found $j_counts files @ $cs_journal/data$ASSIGNED_DBROOT/*\n"; 
                printf " - Checking storagemanager/journal/data$ASSIGNED_DBROOT/*           "; 
            fi;

            sleep 1
            i=$(($i+1))
            j_counts=$(find $cs_journal/data$ASSIGNED_DBROOT/* -type f 2>/dev/null | wc -l)
            printf "."
        done
        printf " Done\n"

        # Backup storagemanager - columnstore s3 metadata, journal, cache
        if [[ -z "$compress_format" ]]; then

            printf " - Syncing Columnstore Metadata \n"
            if $HA; then 
                s3sync $STORAGEMANAGER_PATH $backup_bucket/$today/storagemanager " - Done storagemanager/*\n" "\n\n[!!!] sync failed - storagemanager/*\n\n";
            else 
                s3sync $cs_cache/data$ASSIGNED_DBROOT $backup_bucket/$today/storagemanager/cache/data$ASSIGNED_DBROOT "   + cache/data$ASSIGNED_DBROOT\n" "\n\n[!!!] sync failed - cache/data$ASSIGNED_DBROOT\n\n";
                s3sync $cs_metadata/data$ASSIGNED_DBROOT $backup_bucket/$today/storagemanager/metadata/data$ASSIGNED_DBROOT "   + metadata/data$ASSIGNED_DBROOT\n" "\n\n[!!!] sync failed - metadata/data$ASSIGNED_DBROOT\n\n"
                s3sync $cs_journal/data$ASSIGNED_DBROOT $backup_bucket/$today/storagemanager/journal/data$ASSIGNED_DBROOT "   + journal/data$ASSIGNED_DBROOT\n" "\n\n[!!!] sync failed - journal/data$ASSIGNED_DBROOT\n\n"
            fi;
            
        else
            # For compression keep track of dirs & files to include and compress/stream in the end - $compress_paths
            compress_paths="$cs_cache/data$ASSIGNED_DBROOT $cs_metadata/data$ASSIGNED_DBROOT $cs_journal/data$ASSIGNED_DBROOT "
        fi

        # PM1 mostly backups everything else
        if [ $ASSIGNED_DBROOT == "1" ]; then
            
            # Backup CS configurations
            if [[ -z "$compress_format" ]]; then
                s3sync $CS_CONFIGS_PATH/ $backup_bucket/$today/configs/ "   + $CS_CONFIGS_PATH/\n" "\n\n[!!!] sync failed - $CS_CONFIGS_PATH/\n\n";
            else
                compress_paths+="$CS_CONFIGS_PATH "
            fi

            # Backup CS bucket data
            if ! $skip_bucket_data; then
                printf " - Saving Columnstore data ...                       "
                s3sync $protocol://$bucket $backup_bucket/$today/columnstoreData " Done \n"
            else 
                printf " - [!] Skipping columnstore bucket data \n"
            fi

            # Backup MariaDB Server Data
            if ! $skip_mdb; then
                # Handle compressed mariadb backup
                if [[ -n "$compress_format" ]]; then
                    printf " - Compressing MariaDB data local -> bucket...       "
                    mbd_prefix="$backup_bucket/$today/$split_file_mdb_prefix.$compress_format"
                    case $compress_format in
                        pigz)
                            # Handle Cloud 
                            if [ $cloud == "gcp" ]; then
                                if ! mariabackup --user=root --backup --slave-info --stream xbstream --parallel $PARALLEL_THREADS --ftwrl-wait-timeout=$timeout --ftwrl-wait-threshold=999999 --extra-lsndir=/tmp/checkpoint_out 2>>$log_file | pigz -p $PARALLEL_THREADS 2>> $log_file | split -d -a 5 -b 250M --filter="gsutil cp - ${mbd_prefix}_\$FILE 2>$log_file" - chunk 2>$log_file; then
                                    handle_early_exit_on_backup "\nFailed mariadb-backup --backup --stream xbstream --parallel $PARALLEL_THREADS --ftwrl-wait-timeout=$timeout --ftwrl-wait-threshold=999999 --extra-lsndir=/tmp/checkpoint_out 2>>$log_file | pigz -p $PARALLEL_THREADS 2>> $log_file | split -d -a 5 -b 250M --filter=\"gsutil cp - ${mbd_prefix}_\$FILE 2>$log_file\" - chunk 2>$log_file \n"
                                fi
                                
                            elif [ $cloud == "aws" ]; then
                                if ! mariabackup --user=root --backup --slave-info --stream xbstream --parallel $PARALLEL_THREADS --ftwrl-wait-timeout=$timeout --ftwrl-wait-threshold=999999 --extra-lsndir=/tmp/checkpoint_out 2>>$log_file | pigz -p $PARALLEL_THREADS 2>> $log_file | split -d -a 5 -b 250M --filter="aws s3 cp - ${mbd_prefix}_\$FILE 2>$log_file 1>&2" - chunk 2>$log_file; then
                                    handle_early_exit_on_backup "\nFailed mariadb-backup --backup --stream xbstream --parallel $PARALLEL_THREADS --ftwrl-wait-timeout=$timeout --ftwrl-wait-threshold=999999 --extra-lsndir=/tmp/checkpoint_out 2>>$log_file | pigz -p $PARALLEL_THREADS 2>> $log_file | split -d -a 5 -b 250M --filter=\"aws s3 cp - ${mbd_prefix}_\$FILE 2>$log_file 1>&2\" - chunk 2>$log_file\n"
                                fi
                            fi
                            printf " Done @ $mbd_prefix\n"
                            ;;
                        *)  # unknown option
                            handle_early_exit_on_backup "\nunknown compression flag: $compress_format\n" 
                    esac

                else
                    # Handle uncompressed mariadb backup
                    printf " - Syncing MariaDB data ... \n"
                    rm -rf $backup_location$today/mysql
                    if mkdir -p $backup_location$today/mysql ; then
                        if eval "mariabackup --user=root --backup --slave-info --target-dir=$backup_location$today/mysql/ $extra_cmd_args"; then
                            printf "   + mariadb-backup @ $backup_location$today/mysql/ \n"
                        else
                            handle_early_exit_on_backup "\nFailed mariadb-backup --backup --target-dir=$backup_location$today/mysql --user=root\n" true
                        fi

                        s3rm $backup_bucket/$today/mysql/
                        s3sync $backup_location$today/mysql $backup_bucket/$today/mysql/ "   + mariadb-backup to bucket @ $backup_bucket/$today/mysql/ \n"
                        rm -rf $backup_location$today/mysql
                    else 
                        handle_early_exit_on_backup "\nFailed making directory for MariaDB backup\ncommand: mkdir -p $backup_location$today/mysql\n"
                    fi
                fi
            else 
                echo "[!] Skipping mariadb-backup"
            fi
        fi

        # Backup mariadb server configs
        if ! $skip_mdb; then
            if [[ -z "$compress_format" ]]; then
                printf " - Syncing MariaDB configurations \n"
                s3sync $MARIADB_SERVER_CONFIGS_PATH/ $backup_bucket/$today/configs/mysql/$pm/ "   + $MARIADB_SERVER_CONFIGS_PATH/\n" "\n\n[!!!] sync failed - try alone and debug\n\n"
            else
                compress_paths+="$MARIADB_SERVER_CONFIGS_PATH"
            fi

            # Backup mariadb server replication details
            slave_status=$(mariadb -e "show slave status\G" 2>/dev/null)
            if [[ $? -eq 0 ]]; then
                if [[ -n "$slave_status" ]]; then
                    mariadb -e "show slave status\G" | grep -E "Master_Host|Master_User|Master_Port|Gtid_IO_Pos" > replication_details.txt
                    if [[ -z "$compress_format" ]]; then
                        s3cp replication_details.txt $backup_bucket/$today/configs/mysql/$pm/replication_details.txt
                    else    
                        compress_paths+=" replication_details.txt"
                    fi
                fi
            else
                handle_early_exit_on_backup "[!] - Failed to execute the MariaDB command:   mariadb -e 'show slave status\G' \n"
            fi

        fi

        # Handles streaming the compressed columnstore local files
        if [[ -n "$compress_format" ]]; then
            cs_prefix="$backup_bucket/$today/$split_file_cs_prefix.$compress_format"
            compressed_split_size="250M";
            case $compress_format in
                pigz) 
                    printf " - Compressing CS local files -> bucket ...          "
                    if [ $cloud == "gcp" ]; then
                        if ! eval "tar cf - $compress_paths 2>>$log_file | pigz -p $PARALLEL_THREADS 2>> $log_file | split -d -a 5 -b $compressed_split_size --filter=\"gsutil cp - ${cs_prefix}_\\\$FILE 2>$log_file 1>&2\" - chunk 2>$log_file"; then
                            handle_early_exit_on_backup "[!] - Compression/Split/Upload Failed \n"
                        fi
                        
                    elif [ $cloud == "aws" ]; then
                        if ! eval "tar cf - $compress_paths 2>>$log_file | pigz -p $PARALLEL_THREADS 2>> $log_file | split -d -a 5 -b $compressed_split_size --filter=\"aws s3 cp - ${cs_prefix}_\\\$FILE 2>$log_file 1>&2\" - chunk 2>$log_file"; then
                            handle_early_exit_on_backup "[!] - Compression/Split/Upload Failed \n"
                        fi
                    fi
                    
                    # Cleanup temp files
                    if [ -f replication_details.txt ]; then
                        rm -rf replication_details.txt
                    fi

                    printf " Done @ $cs_prefix\n"
                    ;;
                *)  # unknown option
                    handle_early_exit_on_backup "\nunknown compression flag: $compress_format\n" 
            esac
        fi

        # Create restore job file & update incremental log
        if  $incremental ; then 
            now=$(date "+%m-%d-%Y +%H:%M:%S")
            increment_file="incremental_history.txt"
            
            # download S3 file, append to it and reupload
            if [[ $(s3ls "$backup_bucket/$today/$increment_file")  ]]; then
                s3cp $backup_bucket/$today/$increment_file $increment_file
            fi
            echo "$pm updated on $now" >> $increment_file
            s3cp $increment_file $backup_bucket/$today/$increment_file
            echo "[+] Updated $backup_bucket/$today/$increment_file"
            rm -rf $increment_file
            final_message="Incremental Backup Complete"
        else
            # Create restore job file
            extra_flags=""
            if [[ -n "$compress_format" ]]; then extra_flags+=" -c $compress_format"; fi; 
            if $skip_mdb; then extra_flags+=" --skip-mariadb-backup"; fi; 
            if $skip_bucket_data; then extra_flags+=" --skip-bucket-data"; fi; 
            if [ -n "$s3_url" ];  then extra_flags+=" -url $s3_url"; fi;
            echo "$0 restore -l $today -s $storage -bb $backup_bucket -dbs $DBROOT_COUNT -m $mode -nb $protocol://$bucket $extra_flags --quiet --continue"  > restoreS3.job
            s3cp restoreS3.job $backup_bucket/$today/restoreS3.job
            rm -rf restoreS3.job
        fi
        final_message+=" @ $backup_bucket/$today"
    
    fi
    
    clear_read_lock
    end=$(date +%s)
    total_runtime=$((end - start))
    other=$total_runtime
    
    printf "\nRuntime Summary\n"
    echo "--------------------"
    if [[ -n "$applying_retention_end" && $applying_retention_end -ne 0 ]]; then
        applying_retention_time=$((applying_retention_end - applying_retention_start))
        printf "%-20s %-15s\n" "Applying Retention:" "$(human_readable_time $applying_retention_time)"
        other=$((other - applying_retention_time))
    fi

    if [[ -n "$poll_active_sql_writes_end" && $poll_active_sql_writes_end -ne 0 ]]; then
        poll_sql_writes_time=$((poll_active_sql_writes_end - poll_active_sql_writes_start))
        printf "%-20s %-15s\n" "Polling SQL Writes:" "$(human_readable_time $poll_sql_writes_time)"
        other=$((other - poll_sql_writes_time))
    fi
        
    if [[ -n "$poll_active_cpimports_end" && $poll_active_cpimports_end -ne 0 ]]; then
        poll_cpimports_time=$((poll_active_cpimports_end - poll_active_cpimports_start))
        printf "%-20s %-15s\n" "Polling cpimports:" "$(human_readable_time $poll_cpimports_time)"
        other=$((other - poll_cpimports_time)) 
    fi

    if [[ -n "$columnstore_backup_end" && $columnstore_backup_end -ne 0 ]]; then
        columnstore_backup_time=$((columnstore_backup_end - columnstore_backup_start))
        printf "%-20s %-15s\n" "Columnstore Backup:" "$(human_readable_time $columnstore_backup_time)"
        other=$((other - columnstore_backup_time))  
    fi

    if [[ -n "$mariadb_backup_end" && $mariadb_backup_end -ne 0 ]]; then
        mariadb_backup_time=$((mariadb_backup_end - mariadb_backup_start))
        printf "%-20s %-15s\n" "MariaDB Backup:" "$(human_readable_time $mariadb_backup_time)" 
        other=$((other - mariadb_backup_time))  
    fi

    if (( other < 0 )); then
        printf "%-20s %-15s\n" "Other:" "N/A (invalid data)"
    else
        if [ $other -gt 0 ]; then
            printf "%-20s %-15s\n" "Other:" "<$(human_readable_time $other)"
        else
            printf "%-20s %-15s\n" "Other:" "<1 sec"
        fi
    fi

    printf "\nTotal Runtime: $(human_readable_time $((end-start))) \n"
    printf "$final_message\n\n"
}

load_default_restore_variables() {
    # What date folder to load from the backup_location
    load_date=''

    # Where the backup to load is found
    # Example: /mnt/backups/
    backup_location="/tmp/backups/"

    # Is this backup on the same or remote server compared to where this script is running
    # Options: "Local" or "Remote"
    backup_destination="Local"

    # Used only if backup_destination=Remote
    # The user/credentials that will be used to scp the backup files
    # Example: "centos@10.14.51.62"
    scp=""

    # What storage topogoly was used by Columnstore in the backup - found in storagemanager.cnf
    # Options: "LocalStorage" or "S3" 
    storage="LocalStorage"

    # Flag for high available systems (meaning shared storage exists supporting the topology so that each node sees all data)
    HA=false

    # When set to true skips the enforcement that new_bucket should be empty prior to starting a restore
    continue=false 

    # modes ['direct','indirect'] - direct backups run on the columnstore nodes themselves. indirect run on another machine that has read-only mounts associated with columnstore/mariadb
    mode="direct"

    # Name of the Configuration file to load variables from - relative or full path accepted
    config_file=""

    # Only used if storage=S3
    # Name of the bucket to copy into to store the backup S3 data
    # Example: "backup-mcs-bucket"
    backup_bucket=''

    # Fixed Paths
    MARIADB_PATH="/var/lib/mysql"
    CS_CONFIGS_PATH="/etc/columnstore"
    DBRM_PATH="/var/lib/columnstore/data1/systemFiles/dbrm"
    STORAGEMANAGER_PATH="/var/lib/columnstore/storagemanager"
    STORAGEMANGER_CNF="$CS_CONFIGS_PATH/storagemanager.cnf"

    # Configurable Paths
    cs_metadata=$(grep ^metadata_path $STORAGEMANGER_CNF | cut -d "=" -f 2 | tr -d " ")
    cs_journal=$(grep ^journal_path $STORAGEMANGER_CNF  | cut -d "=" -f 2 | tr -d " ")
    cs_cache=$(grep -A25 "\[Cache\]" $STORAGEMANGER_CNF | grep ^path | cut -d "=" -f 2 | tr -d " ")

    if [ ! -f /var/lib/columnstore/local/module ]; then  pm="pm1"; else pm=$(cat /var/lib/columnstore/local/module);  fi;
    pm_number=$(echo "$pm" | tr -dc '0-9')

    # Defines the set of new bucket information to copy the backup to when restoring
    new_bucket=''
    new_region=''
    new_key=''
    new_secret=''

    # Used by on premise S3 vendors
    # Example: "http://127.0.0.1:8000"
    s3_url=""
    no_verify_ssl=false


    # Number of DBroots
    # Integer usually 1 or 3
    DBROOT_COUNT=1

    # Google Cloud
    # PARALLEL_UPLOAD=50
    RETRY_LIMIT=3

    # Compression Variables
    compress_format=""
    split_file_mdb_prefix="mariadb-backup.tar"
    split_file_cs_prefix="cs-localfiles.tar"
    timeout=999999
    log_file="backup.log"
    col_xml_backup="Columnstore.xml.source_backup"
    cmapi_backup="cmapi_server.conf.source_backup"
    PARALLEL_THREADS=4

    # Other
    final_message="Restore Complete"
    mariadb_user="mysql"
    columnstore_user="mysql"
    skip_mdb=false
    skip_bucket_data=false
    quiet=false
    extra_s3_args=""
    extra_cmd_args=""
    rsync_flags=" -av";
    list_backups=false
}

parse_restore_variables() {
    # Dynamic Arguments
    while [[ $# -gt 0 ]]; do
        key="$1"

        case $key in
            restore)
                shift # past argument
                ;;
            -l|--load)
                load_date="$2"
                shift # past argument
                shift # past value
                ;;
            -bl|--backup-location)
                backup_location="$2"
                shift # past argument
                shift # past value
                ;;
            -bd|--backup-destination)
                backup_destination="$2"
                shift # past argument
                shift # past value
                ;;
            -scp|--secure-copy-protocol)
                scp="$2"
                shift # past argument
                shift # past value
                ;;
            -bb|--backup-bucket)
                backup_bucket="$2"
                shift # past argument
                shift # past value
                ;;
            -url| --endpoint-url)
                s3_url="$2"
                shift # past argument
                shift # past value
                ;;
            -s|--storage)
                storage="$2"
                shift # past argument
                shift # past value
                ;;
            -dbs|--dbroots)
                DBROOT_COUNT="$2"
                shift # past argument
                shift # past value
                ;;
            -pm | --nodeid)
                pm="$2"
                pm_number=$(echo "$pm" | tr -dc '0-9')
                shift # past argument
                shift # past value
                ;;
            -nb | --new-bucket)
                new_bucket="$2"
                shift # past argument
                shift # past value
                ;;
            -nr | --new-region)
                new_region="$2"
                shift # past argument
                shift # past value
                ;;
            -nk | --new-key)
                new_key="$2"
                shift # past argument
                shift # past value
                ;;
            -ns | --new-secret)
                new_secret="$2"
                shift # past argument
                shift # past value
                ;;
            -P|--parallel)
                PARALLEL_THREADS="$2"
                shift # past argument
                shift # past value
                ;;
            -ha | --highavilability)
                HA=true
                shift # past argument
                ;;
            -cont| --continue)
                continue=true
                shift # past argument
                ;;
            -f|--config-file)
                config_file="$2"
                if [ "$config_file" == "" ] || [ ! -f "$config_file" ]; then
                    handle_early_exit_on_restore "\n[!!!] Invalid Configuration File: $config_file\n"
                fi  
                shift # past argument
                shift # past value
                ;;
            -smdb | --skip-mariadb-backup)
                skip_mdb=true
                shift # past argument
                ;;
            -sb   | --skip-bucket-data)
                skip_bucket_data=true
                shift # past argument
                ;;
            -m  | --mode)
                mode="$2"
                shift # past argument
                shift # past value
                ;;
            -c    | --compress)
                compress_format="$2"
                shift # past argument
                shift # past value
                ;;
            -q    | --quiet)
                quiet=true
                shift # past argument
                ;;
            -nv-ssl| --no-verify-ssl)
                no_verify_ssl=true 
                shift # past argument
                ;;
            -li | --list)
                list_backups=true
                shift # past argument
                ;;
            -h|--help|-help|help)
                print_restore_help_text;
                exit 1;
                ;;
            *)  # unknown option
                handle_early_exit_on_restore "\nunknown flag: $1\n"
        esac
    done

    # Load config file
    if [ -n "$config_file" ] && [ -f "$config_file" ]; then
        if ! source "$config_file"; then
            handle_early_exit_on_restore "\n[!!!] Failed to source configuration file: $config_file\n" true
        fi
    fi
}

print_restore_help_text()
{
    echo "
    Columnstore Restore

        -l   | --load                   What backup to load
        -bl  | --backup-location        Directory where the backup was saved
        -bd  | --backup-destination     If the directory is 'Local' or 'Remote' to this script
        -dbs | --dbroots                Number of database roots in the backup
        -scp | --secure-copy-protocol   scp connection to remote server if -bd 'Remote'
        -bb  | --backup_bucket          Bucket name for where to find the S3 backups
        -url | --endpoint-url           On premise url to s3 storage api example: http://127.0.0.1:8000
        -nv-ssl| --no-verify-ssl)       Skips verifying ssl certs, useful for on premise s3 storage
        -s   | --storage                The storage used by columnstore data 'LocalStorage' or 'S3'
        -pm  | --nodeid                 Forces the handling of the restore as this node as opposed to whats detected on disk
        -nb  | --new-bucket             Defines the new bucket to copy the s3 data to from the backup bucket. 
                                        Use -nb if the new restored cluster should use a different bucket than the backup bucket itself
        -nr  | --new-region             Defines the region of the new bucket to copy the s3 data to from the backup bucket   
        -nk  | --new-key                Defines the aws key to connect to the new_bucket  
        -ns  | --new-secret             Defines the aws secret of the aws key to connect to the new_bucket  
        -f   | --config-file            Path to backup configuration file to load variables from - see load_default_restore_variables() for defaults
        -cont| --continue               This acknowledges data in your --new_bucket is ok to delete when restoring S3
        -smdb| --skip-mariadb-backup    Skip restoring mariadb server via mariadb-backup - ideal for only restoring columnstore
        -sb  | --skip-bucket-data       Skip restoring columnstore data in the bucket - ideal if looking to only restore mariadb server
        -q   | --quiet                  Silence verbose copy command outputs
        -c   | --compress               Hint that the backup is compressed in X format - Options: [ pigz ]
        -li  | --list                   List available backups in the backup location
        -ha  | --highavilability        Hint for if shared storage is attached @ below on all nodes to see all data
                                            HA LocalStorage ( /var/lib/columnstore/dataX/ )
                                            HA S3           ( /var/lib/columnstore/storagemanager/ )  

        Local Storage Examples:
            ./$0 restore -s LocalStorage -bl /tmp/backups/ -bd Local -l 12-29-2021
            ./$0 restore -s LocalStorage -bl /tmp/backups/ -bd Remote -scp root@172.31.6.163 -l 12-29-2021
        
        S3 Storage Examples:
            ./$0 restore -s S3 -bb s3://my-cs-backups  -l 12-29-2021
            ./$0 restore -s S3 -bb gs://on-premise-bucket -l 12-29-2021 -url http://127.0.0.1:8000
            ./$0 restore -s S3 -bb s3://my-cs-backups  -l 08-16-2022 -nb s3://new-data-bucket -nr us-east-1 -nk AKIAxxxxxxx3FHCADF -ns GGGuxxxxxxxxxxnqa72csk5 -ha
    ";

    # Hidden flags
    # -m | --mode           Options ['direct','indirect'] - direct backups run on the columnstore nodes themselves. indirect run on another machine that has read-only mounts associated with columnstore/mariadb

}

print_restore_variables()
{

    printf "\nRestore Variables\n"
    if [ -n "$config_file" ] && [ -f "$config_file" ]; then
        printf "%-${s1}s  %-${s2}s\n" "Configuration File:" "$config_file";
    fi
    if [ $mode == "indirect" ] || $skip_mdb || $skip_bucket_data; then 
        echo "------------------------------------------------"
        echo "Skips: MariaDB($skip_mdb) Bucket($skip_bucket_data)";
    fi;
    echo "------------------------------------------------"
    if [[ -n "$compress_format" ]]; then
        echo "Compression:        true"
        echo "Compression Format: $compress_format";
    else 
        echo "Compression:        false"
    fi
    if [ $storage == "LocalStorage" ]; then 
        echo "Backup Location:    $backup_location"
        echo "Backup Destination: $backup_destination"
        echo "Scp:                $scp"
        echo "Storage:            $storage"
        echo "Load Date:          $load_date"
        echo "timestamp:          $(date)"
        echo "DB Root Count:      $DBROOT_COUNT"
        echo "PM:                 $pm"
        echo "PM Number:          $pm_number"

    elif [ $storage == "S3" ]; then
        echo "Backup Location:    $backup_location"  
        echo "Storage:            $storage"
        echo "Load Date:          $load_date"
        echo "timestamp:          $(date)"
        echo "PM:                 $pm"
        echo "PM Number:          $pm_number"
        echo "Active bucket:      $( grep -m 1 "^bucket =" $STORAGEMANGER_CNF)"
        echo "Active endpoint:    $( grep -m 1 "^endpoint =" $STORAGEMANGER_CNF)"
        echo "Backup Bucket:      $backup_bucket"
        echo "------------------------------------------------"
        echo "New Bucket:         $new_bucket"
        echo "New Region:         $new_region"
        echo "New Key:            $new_key"
        echo "New Secret:         $new_secret"
        echo "High Availability:   $HA"
    fi
    echo "------------------------------------------------"

}

validation_prechecks_for_restore() {

    echo "Prechecks ..."
    if [ $storage != "LocalStorage" ] && [ $storage != "S3" ]; then handle_early_exit_on_restore "Invalid script variable storage: $storage\n"; fi   
    if [ -z "$load_date" ]; then handle_early_exit_on_restore "\n[!!!] Required field --load: $load_date - is empty\n" ; fi
    if [ -z "$mode" ]; then handle_early_exit_on_restore "\n[!!!] Required field --mode: $mode - is empty\n" ; fi
    if [ "$mode" != "direct" ] && [ "$mode" != "indirect" ] ; then handle_early_exit_on_restore "\n[!!!] Invalid field --mode: $mode\n"; fi
    mariadb_user=$(stat -c "%U" /var/lib/mysql/)
    columnstore_user=$(stat -c "%U" /var/lib/columnstore/)

    # Adjust s3api flags for on premise/custom endpoints
    add_s3_api_flags=""
    if [ -n "$s3_url" ];  then add_s3_api_flags+=" --endpoint-url $s3_url"; fi
    if $no_verify_ssl; then add_s3_api_flags+=" --no-verify-ssl"; fi;

    # If remote backup - Validate that scp works 
    if [ $backup_destination == "Remote" ]; then
        if ssh $scp echo ok 2>&1 ;then
            printf 'SSH Works\n\n'
        else
            handle_early_exit_on_restore "Failed Command: ssh $scp"
        fi
    fi

    # Make sure the database is offline
    if [ "$mode" == "direct" ]; then 
        if [ -z $(pidof PrimProc) ]; then 
            printf " - Columnstore Status ...               Offline\n"; 
        else 
            handle_early_exit_on_restore "\n[X] Columnstore is ONLINE - please turn off \n\n";  
        fi
        
        if [ -z $(pidof mariadbd) ]; then 
            printf " - MariaDB Server Status ...            Offline\n"; 
        else 
            handle_early_exit_on_restore "\n[X] MariaDB is ONLINE - please turn off \n\n"; 
        fi
    fi

    # If cmapi installed - make cmapi turned off
    check_package_managers
    cmapi_installed_command=""
    case $package_manager in
        yum ) 
            cmapi_installed_command="yum list installed MariaDB-columnstore-cmapi &> /dev/null;";
            ;;
        apt )
            cmapi_installed_command="dpkg-query -s mariadb-columnstore-cmapi &> /dev/null;";
            ;;
        *)  # unknown option
            echo "\npackage manager not implemented: $package_manager\n"
            exit 2;
    esac

    if eval $cmapi_installed_command ; then
        if ! sudo mcs cmapi is-ready &> /dev/null ; then 
            printf " - Columnstore Management API Status .. Offline\n"; 
        else 
            handle_early_exit_on_restore "\n[X] Cmapi is ONLINE - please turn off \n\n"; 
        fi
    else
        printf " - Columnstore Management API Status .. Not Installed - Skipping\n";  
    fi

    # Validate additional relevant arguments per storage option
    if [ $storage == "LocalStorage" ]; then 
        
        if [ -z "$backup_location" ]; then handle_early_exit_on_restore "Invalid --backup_location: $backup_location - is empty";  fi
        if [ -z "$backup_destination" ]; then handle_early_exit_on_restore "Invalid --backup_destination: $backup_destination - is empty"; fi
        if [ $backup_destination == "Remote" ] && [ -d $backup_location$load_date ]; then echo "Switching to '-bd Local'"; backup_destination="Local"; fi
        if [ $backup_destination == "Local" ]; then
            
            if [ ! -d $backup_location ]; then handle_early_exit_on_restore "Invalid directory --backup_location: $backup_location - does not exist"; fi
            if [ -z "$(ls -A "$backup_location")" ]; then echo "Invalid --backup_location: $backup_location - directory is empty."; fi;
            if [ ! -d $backup_location$load_date ]; then handle_early_exit_on_restore "Invalid directory --load: $backup_location$load_date - does not exist"; else printf " - Backup directory exists\n"; fi
        fi;

        local files=$(ls $backup_location$load_date)
        if [[ -n "$compress_format" ]]; then   
            case "$compress_format" in
                pigz) 
                    flag_cs_local=false 
                    flag_mysql=$skip_mdb

                    while read -r line; do
                        folder_name=$(echo "$line" | awk '{ print $NF }')
                        case "$folder_name" in
                            *mariadb-backup.tar.pigz*) flag_mysql=true ;;
                            *cs-localfiles.tar.pigz*) flag_cs_local=true ;;
                        esac
                    done <<< "$files"

                    if $flag_mysql && $flag_cs_local ; then
                        echo " - Backup subdirectories exist"
                    else
                        echo "[X] Backup is missing subdirectories: "
                        if ! $flag_cs_local; then echo "cs-localfiles.tar.pigz"; fi;
                        if ! $flag_mysql; then echo "mariadb-backup.tar.pigz"; fi;
                        handle_early_exit_on_restore "[!] Backup is missing subdirectories... \nls $backup_location$load_date\n\n"
                    fi
                ;;
                *)  # unknown option
                    handle_early_exit_on_backup "\nUnknown compression flag: $compress_format\n" 
            esac
        else 
            
            flag_configs=false
            flag_data=false
            flag_mysql=$skip_mdb
            files=$(ls $backup_location$load_date )
            while read -r line; do
                folder_name=$(echo "$line" | awk '{ print $NF }')
           
                case "$folder_name" in
                    *configs*) flag_configs=true ;;
                    *data*) flag_data=true ;;
                    *mysql*) flag_mysql=true ;;
                esac
            done <<< "$files"

            if $flag_configs && $flag_data && $flag_mysql; then
                echo " - Backup subdirectories exist"
            else
                echo "[X] Backup is missing subdirectories: "
                if ! $flag_configs; then echo "configs"; fi;
                if ! $flag_data; then echo "data"; fi;
                if ! $flag_mysql; then echo "mysql"; fi;
                handle_early_exit_on_restore " Backup is missing subdirectories... \nls $backup_location$load_date\n"
            fi
        fi
    
    elif [ $storage == "S3" ]; then 
        
        # Check for concurrency settings - https://docs.aws.amazon.com/cli/latest/topic/s3-config.html
        if [ $cloud == "aws" ]; then cli_concurrency=$(sudo grep max_concurrent_requests ~/.aws/config 2>/dev/null ); if [ -z "$cli_concurrency" ]; then echo "[!!!] check: '~/.aws/config' - We recommend increasing s3 concurrency for better throughput to/from S3. This value should scale with avaialble CPU and networking capacity"; printf "example: aws configure set default.s3.max_concurrent_requests 200\n";  fi; fi;

        # Validate addtional relevant arguments
        if [ -z "$backup_bucket" ]; then handle_early_exit_on_restore "Invalid --backup_bucket: $backup_bucket - is empty";  fi
        
        # Prepare commands for each cloud
        if [ $cloud == "gcp" ]; then
            check1="$gsutil ls $backup_bucket";
            check2="$gsutil ls -b $new_bucket";
            check3="$gsutil ls $new_bucket";
            check4="$gsutil ls $backup_bucket/$load_date";
            check5="$gsutil ls $backup_bucket/$load_date/";
        else 
            check1="$awscli $add_s3_api_flags s3 ls $backup_bucket"
            check2="$awscli $add_s3_api_flags s3 ls | grep $new_bucket"
            check3="$awscli $add_s3_api_flags s3 ls $new_bucket"
            check4="$awscli $add_s3_api_flags s3 ls $backup_bucket/$load_date"
            check5="$awscli $add_s3_api_flags s3 ls $backup_bucket/$load_date/"
        fi;

        # Check aws cli access to bucket
        if $check1 > /dev/null ; then
            echo -e " - Success listing backup bucket"
        else 
            handle_early_exit_on_restore "[X] Failed to list bucket contents...\n$check1\n"
        fi

        # New bucket exists and empty check
        if [ "$new_bucket" ] && [ $pm == "pm1" ]; then 
            # Removing as storage.buckets.get permission likely not needed
            # new_bucket_exists=$($check2); if [ -z "$new_bucket_exists" ]; then handle_early_exit_on_restore "[!!!] Did not find new bucket - Check:  $check2\n"; fi; echo "[+] New Bucket exists"; 
            # Throw warning if new bucket is NOT empty
            if ! $continue; then nb_contents=$($check3 | wc -l); 
                if [ $nb_contents -lt 1 ]; then 
                    echo " - New Bucket is empty"; 
                else 
                    echo "[!!!] New bucket is NOT empty... $nb_contents files exist... exiting"; 
                    echo "add "--continue" to skip this exit"; 
                    echo -e "\nExample empty bucket command:\n     aws s3 rm $new_bucket --recursive\n     gsutil -m rm -r $new_bucket/* \n"; 
                    handle_early_exit_on_restore "Please empty bucket or add --continue \n"; 
                fi; 
            else 
                echo " - [!] Skipping empty new_bucket check"; 
            fi; 
        fi

        # Check if s3 bucket load date exists
        if $check4 > /dev/null ; then
            echo -e " - Backup directory exists"
        else 
            handle_early_exit_on_restore "\n[X] Backup directory load date ($backup_bucket/$load_date) DOES NOT exist in S3 \nCheck - $check4 \n\n";
        fi

        # Check if s3 bucket load date subdirectories exist
       if folders=$($check5) > /dev/null ; then
            if [[ -n "$compress_format" ]]; then
                # when compressed, storagemanager/configs wont exist but rather pigz_chunkXXX does. and mysql could be split up
                case "$compress_format" in
                    pigz) 
                        flag_cs_local=false 
                        flag_mysql=$skip_mdb
                        flag_columnstoreData=$skip_bucket_data

                        while read -r line; do
                            folder_name=$(echo "$line" | awk '{ print $NF }')
                            case "$folder_name" in
                                *columnstoreData*) flag_columnstoreData=true ;;
                                *mariadb-backup.tar.pigz*) flag_mysql=true ;;
                                *cs-localfiles.tar.pigz*) flag_cs_local=true ;;
                            esac
                        done <<< "$folders"

                        if $flag_pigz_chunk && $flag_mysql && $flag_columnstoreData ; then
                            echo " - Backup subdirectories exist"
                        else
                            echo "[X] Backup is missing subdirectories: "
                            if ! $flag_cs_local; then echo "cs-localfiles.tar.pigzXXX"; fi;
                            if ! $flag_mysql; then echo "mariadb-backup.tar.pigzXXXX"; fi;
                            if ! $flag_columnstoreData; then echo "columnstoreData"; fi;
                            handle_early_exit_on_restore " Backup is missing subdirectories... \n$check5\n"
                        fi
                    ;;
                    *)  # unknown option
                        handle_early_exit_on_backup "\nunknown compression flag: $compress_format\n" 
                esac
            else
                flag_configs=false
                flag_storagemanager=false
                flag_mysql=$skip_mdb
                flag_columnstoreData=$skip_bucket_data

                while read -r line; do
                    folder_name=$(echo "$line" | awk '{ print $NF }')
                    #echo "DEBUG: $folder_name"
                    case "$folder_name" in
                        *storagemanager*) flag_storagemanager=true ;;
                        *mysql*) flag_mysql=true ;;
                        *columnstoreData*) flag_columnstoreData=true ;;
                        *configs*) flag_configs=true ;;
                        #*) echo "unknown: $folder_name" ;;
                    esac
                done <<< "$folders" 

                if $flag_storagemanager && $flag_mysql && $flag_columnstoreData && $flag_configs; then
                    echo " - Backup subdirectories exist"
                else
                    echo "[X] Backup is missing subdirectories: "
                    if ! $flag_storagemanager; then echo "storagemanager"; fi;
                    if ! $flag_mysql; then echo "mysql"; fi;
                    if ! $flag_columnstoreData; then echo "columnstoreData"; fi;
                    if ! $flag_configs; then echo "configs"; fi;
                    handle_early_exit_on_restore " Backup is missing subdirectories... \n$check5\n"
                fi
            fi;
        else 
            handle_early_exit_on_restore " Failed to list backup contents...\n$check5\n"
        fi;
    fi;

    if $quiet; then extra_cmd_args+="  2> /dev/null"; rsync_flags=" -a"; fi
}

# Restore Columnstore.xml by updating critical parameters 
# Depends on $CS_CONFIGS_PATH/$col_xml_backup
restore_columnstore_values() {
    
    printf "\nRestore Columnstore.xml Values"
    if ! $quiet; then printf "\n"; else printf "...      "; fi;
    columnstore_config_pairs_to_transfer=(
        "HashJoin TotalUmMemory"
        "DBBC NumBlocksPct"
        "CrossEngineSupport User"
        "CrossEngineSupport Password"
        "HashJoin AllowDiskBasedJoin"
        "RowAggregation AllowDiskBasedAggregation"
        "SystemConfig MemoryCheckPercent"
        "JobList RequestSize"
        "JobList ProcessorThreadsPerScan"
        "DBBC NumThreads"
        "PrimitiveServers ConnectionsPerPrimProc"
    )

    # mcsGetConfig -c Columnstore.xml.source_backup  HashJoin TotalUmMemory
    for pair in "${columnstore_config_pairs_to_transfer[@]}"; do
        level_one=$(echo "$pair" | cut -d ' ' -f 1)
        level_two=$(echo "$pair" | cut -d ' ' -f 2)
        
        # Get the source value using mcsGetConfig -c
        source_value=$(mcsGetConfig -c "$CS_CONFIGS_PATH/$col_xml_backup" "$level_one" "$level_two")
        
        if [ -n "$source_value" ]; then
            # Set the value in the active Columnstore.xml using mcsSetConfig
            if mcsSetConfig "$level_one" "$level_two" "$source_value"; then
                # echo instead of printf to avoid escaping % from HashJoin TotalUmMemory
                if ! $quiet; then echo " - Set $level_one $level_two $source_value"; fi; 
            else
                printf "\n[!] Failed to Set $level_one $level_two $source_value \n";
            fi
        else
            if ! $quiet; then printf " - N/A: $level_one $level_two \n"; fi; 
        fi
    done
    if $quiet; then printf " Done\n"; fi;
}

run_restore()
{
    # Branch logic based on topology
    if [ $storage == "LocalStorage" ]; then 

        # Branch logic based on where the backup resides
        if [ $backup_destination == "Local" ]; then
            
            # MariaDB Columnstore Restore
            printf "\nRestore MariaDB Columnstore LocalStorage\n"

            # Handle Compressed Backup Restore
            if [[ -n "$compress_format" ]]; then

                # Clear appropriate dbroot
                i=1;
                while [ $i -le $DBROOT_COUNT ]; do
                    if [[ $pm == "pm$i" || $HA == true ]]; then
                        printf " - Deleting Columnstore Data$i ...      "; 
                        rm -rf /var/lib/columnstore/data$i/*; 
                        printf " Done \n"; 
                    fi
                    ((i++))
                done    

                tar_flags="xf"
                if ! $quiet; then tar_flags+="v"; fi;
                cs_prefix="$backup_location$load_date/$split_file_cs_prefix.$compress_format"
                case $compress_format in
                    pigz) 
                        printf " - Decompressing CS Files -> local ... "
                        cd /
                        if ! eval "pigz -dc -p $PARALLEL_THREADS $cs_prefix | tar $tar_flags - "; then
                            handle_early_exit_on_restore "Failed to decompress and untar columnstore localfiles\ncommand:\npigz -dc -p $PARALLEL_THREADS $cs_prefix | tar xf - \n\n"
                        fi
                        printf " Done \n"
                        ;;
                    *)  # unknown option
                        handle_early_exit_on_backup "\nunknown compression flag: $compress_format\n" 
                esac

                # Set permissions after restoring files
                i=1;
                while [ $i -le $DBROOT_COUNT ]; do
                    if [[ $pm == "pm$i" || $HA == true ]]; then
                        printf " - Chowning Columnstore Data$i ...      "; 
                        chown $columnstore_user:$columnstore_user -R /var/lib/columnstore/data$i/; 
                        printf " Done \n"; 
                    fi
                    ((i++))
                done

            else
                # Handle Non-Compressed Backup Restore
                # Loop through per dbroot
                i=1;
                while [ $i -le $DBROOT_COUNT ]; do
                    if [[ $pm == "pm$i" || $HA == true ]]; then
                        printf " - Restoring Columnstore Data$i ...     ";
                        rm -rf /var/lib/columnstore/data$i/*; 
                        rsync $rsync_flags $backup_location$load_date/data$i/ /var/lib/columnstore/data$i/;
                        chown $columnstore_user:$columnstore_user -R /var/lib/columnstore/data$i/; 
                        printf " Done Data$i \n"; 
                    fi
                    ((i++))
                done    
                
                # Put configs in place  
                printf " - Columnstore Configs ...             "
                rsync $rsync_flags $backup_location$load_date/configs/storagemanager.cnf $STORAGEMANGER_CNF
                rsync $rsync_flags $backup_location$load_date/configs/Columnstore.xml $CS_CONFIGS_PATH/$col_xml_backup
                rsync $rsync_flags $backup_location$load_date/configs/mysql/$pm/ $MARIADB_SERVER_CONFIGS_PATH/
                if [ -f "$backup_location$load_date/configs/cmapi_server.conf" ]; then 
                    rsync $rsync_flags $backup_location$load_date/configs/cmapi_server.conf $CS_CONFIGS_PATH/$cmapi_backup ; 
                fi;
                printf " Done\n" 
            fi
        
        elif [ $backup_destination == "Remote" ]; then
            printf "[~] Copy MySQL Data..."
            tmp="localscpcopy-$load_date"
            rm -rf $backup_location$tmp/mysql/
            mkdir -p $backup_location$tmp/mysql/
            rsync $rsync_flags $scp:$backup_location/$load_date/mysql/ $backup_location$tmp/mysql/
            printf "[+] Copy MySQL Data... Done\n"

            # Loop through per dbroot
            printf "[~] Columnstore Data..."; i=1;
            while [ $i -le $DBROOT_COUNT ]; do
                if [[ $pm == "pm$i" || $HA == true ]]; then 
                    rm -rf /var/lib/columnstore/data$i/; 
                    rsync -av $scp:$backup_location$load_date/data$i/ /var/lib/columnstore/data$i/ ; 
                    chown $columnstore_user:$columnstore_user -R /var/lib/columnstore/data$i/;
                fi
                ((i++))
            done
            printf "[+] Columnstore Data... Done\n" 

            # Put configs in place
            printf " - Columnstore Configs ...             "
            rsync $rsync_flags $backup_location$load_date/configs/storagemanager.cnf $STORAGEMANGER_CNF
            rsync $rsync_flags $backup_location$load_date/configs/Columnstore.xml $CS_CONFIGS_PATH/$col_xml_backup  
            rsync $rsync_flags $backup_location$load_date/configs/mysql/$pm/ $MARIADB_SERVER_CONFIGS_PATH/ 
            if [ -f "$backup_location$load_date/configs/cmapi_server.conf" ]; then 
                rsync $rsync_flags $backup_location$load_date/configs/cmapi_server.conf $CS_CONFIGS_PATH/$cmapi_backup ; 
            fi;
            printf " Done\n"
            load_date=$tmp
        else 
            handle_early_exit_on_restore "Invalid Script Variable --backup_destination: $backup_destination"
        fi

        if ! $skip_mdb; then
            # MariaDB Server Restore 
            printf "\nRestore MariaDB Server\n"
            if [[ -n "$compress_format" ]]; then
                # Handle compressed mariadb-backup restore
                mbd_prefix="$backup_location$load_date/$split_file_mdb_prefix.$compress_format"
                case $compress_format in
                    pigz) 
                        printf " - Decompressing MariaDB Files -> $MARIADB_PATH ... "
                        rm -rf $MARIADB_PATH/*
                        cd /
                        if ! eval "pigz -dc -p $PARALLEL_THREADS $mbd_prefix |  mbstream -p $PARALLEL_THREADS -x -C $MARIADB_PATH"; then
                            handle_early_exit_on_restore "Failed to decompress mariadb backup\ncommand:\npigz -dc -p $PARALLEL_THREADS $mbd_prefix |  mbstream -p $PARALLEL_THREADS -x -C $MARIADB_PATH \n"
                        fi;
                        printf " Done \n"
                        printf " - Running mariabackup --prepare ...   "
                        if eval "mariabackup --prepare --target-dir=$MARIADB_PATH $extra_cmd_args"; then 
                            printf " Done\n"; 
                        else 
                            handle_early_exit_on_restore "Failed to --prepare\nmariabackup --prepare --target-dir=$MARIADB_PATH"; 
                        fi; 
                        ;;
                    *)  # unknown option
                        handle_early_exit_on_backup "\nUnknown compression flag: $compress_format\n" 
                esac
            else
                eval "mariabackup --prepare --target-dir=$backup_location$load_date/mysql/ $extra_cmd_args"
                rm -rf /var/lib/mysql/*
                eval "mariabackup --copy-back --target-dir=$backup_location$load_date/mysql/ $extra_cmd_args"
            fi
        else
            echo "[!] Skipping mariadb server restore"
        
        fi;
        printf " - Chowning MariaDB Files ...          ";
        chown -R $mariadb_user:$mariadb_user /var/lib/mysql/
        chown -R $columnstore_user:$mariadb_user /var/log/mariadb/
        printf " Done \n"

    elif [ $storage == "S3" ]; then

        printf "\nRestore MariaDB Columnstore S3\n"

        # Sync the columnstore data from the backup bucket to the new bucket
        if ! $skip_bucket_data && [ "$new_bucket" ] && [ $pm == "pm1" ]; then 
            printf "[+] Starting Bucket Sync:\n - $backup_bucket/$load_date/columnstoreData to $new_bucket\n"
            s3sync $backup_bucket/$load_date/columnstoreData/ $new_bucket/ " - Done with S3 Bucket sync\n"
        else 
            printf "[!] Skipping Columnstore Bucket\n"
        fi;
    
        printf "[+] Starting Columnstore Configurations & Metadata: \n"
        if [[ -n "$compress_format" ]]; then
            cs_prefix="$backup_bucket/$load_date/$split_file_cs_prefix.$compress_format"
            case $compress_format in
                pigz) 
                    printf " - Decompressing CS Files bucket -> local ... "
                    if [ $cloud == "gcp" ]; then
                        # gsutil supports simple "cat prefix*" to standard out unlike awscli
                        cd /
                        if ! eval "$gsutil  cat $cs_prefix* | gzip -dc | tar xf - "; then
                            handle_early_exit_on_restore "Failed to decompress and untar columnstore localfiles\ncommand:\n$gsutil cat $cs_prefix* | gzip -dc | tar xf - \n\n"
                        fi
                    elif [ $cloud == "aws" ]; then
                        
                        # List all the pigz compressed chunks in the S3 prefix - should I clear prior local files?
                        chunk_list=$($awscli s3 ls "$cs_prefix" | awk '{print $NF}')
                        cd /
                    
                        # Use process substitution to concatenate the compressed chunks and pipe to pigz and extract using tar
                        if ! eval "pigz -dc -p $PARALLEL_THREADS <(for chunk in \$chunk_list; do $awscli s3 cp \"$backup_bucket/$load_date/\$chunk\" - ; done) | tar xf - "; then
                            handle_early_exit_on_restore "Failed to decompress and untar columnstore localfiles\ncommand:\npigz -dc -p $PARALLEL_THREADS <(for chunk in \$chunk_list; do $awscli s3 cp \"$backup_bucket/$load_date/\$chunk\" - ; done) | tar xf - \n\n"
                        fi
                    fi
                    printf " Done \n"
                    ;;
                *)  # unknown option
                    handle_early_exit_on_backup "\nunknown compression flag: $compress_format\n" 
            esac
         
        else
            # Columnstore.xml and cmapi are renamed so that only specific values are restored 
            s3cp $backup_bucket/$load_date/configs/Columnstore.xml $CS_CONFIGS_PATH/$col_xml_backup
            s3cp $backup_bucket/$load_date/configs/cmapi_server.conf $CS_CONFIGS_PATH/$cmapi_backup
            s3cp $backup_bucket/$load_date/configs/storagemanager.cnf $STORAGEMANGER_CNF 
            s3sync $backup_bucket/$load_date/storagemanager/cache/data$pm_number $cs_cache/data$pm_number/ " - Done cache/data$pm_number/\n"
            s3sync $backup_bucket/$load_date/storagemanager/metadata/data$pm_number $cs_metadata/data$pm_number/ " - Done metadata/data$pm_number/\n"
            if ! $skip_mdb; then s3sync $backup_bucket/$load_date/configs/mysql/$pm/ $MARIADB_SERVER_CONFIGS_PATH/ " - Done $MARIADB_SERVER_CONFIGS_PATH/\n"; fi

            if s3ls "$backup_bucket/$today/storagemanager/journal/data$ASSIGNED_DBROOT" > /dev/null 2>&1; then
                s3sync $backup_bucket/$load_date/storagemanager/journal/data$pm_number $cs_journal/data$pm_number/ " - Done journal/data$pm_number/\n"
            else 
                echo " - Done journal/data$pm_number was empty"
            fi
        fi;

        # Set appropriate bucket, prefix, region, key , secret
        printf "[+] Adjusting storagemanager.cnf ... \n"
        target_bucket=$backup_bucket
        target_prefix="prefix = $load_date\/columnstoreData\/"
        if [ -n "$new_bucket" ]; then 
            target_bucket=$new_bucket;  
            target_prefix="# prefix \= cs\/";  
        fi

        if [ -n "$new_region" ];  then 
            if [ $cloud == "gcp" ]; then endpoint="storage.googleapis.com"; else endpoint="s3.$new_region.amazonaws.com"; fi;
            sed -i "s|^endpoint =.*|endpoint = ${endpoint}|g" $STORAGEMANGER_CNF; 
            sed -i "s|^region =.*|region = ${new_region}|g" $STORAGEMANGER_CNF; 
            echo " - Adjusted endpoint & region";
        fi

        # Removes prefix of the protocol and escapes / for sed
        target_bucket_name=$(echo "${target_bucket#$protocol://}" | sed "s/\//\\\\\//g")
        if [ -n "$new_key" ];  then 
            sed -i "s|aws_access_key_id =.*|aws_access_key_id = ${new_key}|g" $STORAGEMANGER_CNF; 
            echo " - Adjusted aws_access_key_id";  
        fi

        if [ -n "$new_secret" ];  then 
            sed -i "s|aws_secret_access_key =.*|aws_secret_access_key = ${new_secret}|g" $STORAGEMANGER_CNF; 
            echo " - Adjusted aws_secret_access_key"; 
        fi

        bucket=$( grep -m 1 "^bucket =" $STORAGEMANGER_CNF | sed "s/\//\\\\\//g"); 
        sed -i "s/$bucket/bucket = $target_bucket_name/g" $STORAGEMANGER_CNF;
        echo " - Adjusted bucket";
        
        prefix=$( grep -m 1 "^prefix =" $STORAGEMANGER_CNF | sed "s/\//\\\\\//g");
        if [ ! -z "$prefix" ]; then 
            sed -i "s/$prefix/$target_prefix/g" $STORAGEMANGER_CNF; 
            echo " - Adjusted prefix"; 
        fi;

        # Check permissions
        chown -R $columnstore_user:$columnstore_user /var/lib/columnstore/
        chown -R root:root $MARIADB_SERVER_CONFIGS_PATH/
    
        # Confirm S3 connection works
        if [ $mode == "direct" ]; then echo "[+] Checking S3 Connection ..."; if testS3Connection $extra_cmd_args; then echo " - S3 Connection passes" ; else handle_early_exit_on_restore "\n[X] S3 Connection issues - retest/configure\n"; fi; fi;
        printf "[+] MariaDB Columnstore Done\n\n"

        if ! $skip_mdb; then
            printf "Restore MariaDB Server\n"
            if [[ -n "$compress_format" ]]; then
                # Handle compressed mariadb-backup restore 
                mbd_prefix="$backup_bucket/$load_date/$split_file_mdb_prefix.$compress_format"
                case $compress_format in
                    pigz) 
                        printf "[+] Decompressing mariadb-backup bucket -> local ... "
                        rm -rf $MARIADB_PATH/*
                        if [ $cloud == "gcp" ]; then
                            if ! eval "$gsutil cat $mbd_prefix* | pigz -dc -p $PARALLEL_THREADS | mbstream -p $PARALLEL_THREADS -x -C $MARIADB_PATH"; then
                                 handle_early_exit_on_restore "Failed to decompress mariadb backup\ncommand:\n$gsutil cat $mbd_prefix* | pigz -dc -p $PARALLEL_THREADS | mbstream -p $PARALLEL_THREADS -x -C $MARIADB_PATH\n"
                            fi
                        elif [ $cloud == "aws" ]; then

                            chunk_list=$($awscli s3 ls "$mbd_prefix" | awk '{print $NF}')
                            cd /
                            
                            if ! eval "pigz -dc -p $PARALLEL_THREADS <(for chunk in \$chunk_list; do $awscli s3 cp "$backup_bucket/$load_date/\$chunk" -; done) |  mbstream -p $PARALLEL_THREADS -x -C $MARIADB_PATH"; then
                                handle_early_exit_on_restore "Failed to decompress mariadb backup\ncommand:\npigz -dc -p $PARALLEL_THREADS <(for chunk in \$chunk_list; do $awscli s3 cp "$backup_bucket/$load_date/\$chunk" -; done) |  mbstream -p $PARALLEL_THREADS -x -C $MARIADB_PATH \n"
                            fi;
                        fi;
                        printf " Done \n"
                        printf " - Running mariabackup --prepare ...                 "
                        if eval "mariabackup --prepare --target-dir=$MARIADB_PATH $extra_cmd_args"; then printf " Done\n"; else handle_early_exit_on_restore "Failed to --prepare\nmariabackup --prepare --target-dir=$MARIADB_PATH"; fi;
                        ;;
                    *)  # unknown option
                        handle_early_exit_on_backup "\nunknown compression flag: $compress_format\n" 
                esac
                
            else
                # Copy the MariaDB data, prepare and put in place
                printf " - Copying down MariaDB data ...                     "
                rm -rf $backup_location$load_date/mysql/   
                mkdir -p $backup_location$load_date/mysql/
                s3sync $backup_bucket/$load_date/mysql/ $backup_location$load_date/mysql " Done\n"

                # Run prepare and copy back
                printf " - Running mariabackup --prepare ...                 "
                if eval "mariabackup --prepare --target-dir=$backup_location$load_date/mysql/ $extra_cmd_args"; then 
                    printf " Done\n"; 
                else 
                    echo "failed"; 
                fi;
                rm -rf /var/lib/mysql/* > /dev/null
                printf " - Running mariabackup --copy-back ...               "
                if eval "mariabackup --copy-back --target-dir=$backup_location$load_date/mysql/ $extra_cmd_args"; then 
                    printf " Done\n"; 
                else 
                    echo "failed"; 
                fi;
            fi;

            # Check permissions
            printf " - Chowning mysql and log dirs ...                   "
            chown -R $mariadb_user:$mariadb_user /var/lib/mysql/
            chown -R $columnstore_user:$mariadb_user /var/log/mariadb/
            printf " Done \n"
            printf "[+] MariaDB Server Done\n"

        else
            echo "[!] Skipping mariadb server restore"
        fi
    fi

    if [[ $pm == "pm1" ]]; then restore_columnstore_values; fi;
    
    end=$(date +%s)
    runtime=$((end-start))
    printf "\nRuntime: $runtime\n"
    printf "$final_message\n\n"
    if ! $quiet; then 
        if [ $pm == "pm1" ]; then 
            echo -e  "Next: "
            if [ $DBROOT_COUNT -gt 1 ]; then  echo -e  "[!!] Check Replicas to manually configure mariadb replication"; fi
            echo -e  "systemctl start mariadb "
            echo -e  "systemctl start mariadb-columnstore-cmapi "
            echo -e  "mcs cluster start"
            echo -e  "mariadb -e \"show variables like '%gtid_current_pos%';\"\n\n"
        else
            echo -e  "[!!] Check this replica to manually configure mariadb replication after primary node is ready"
            echo -e  "Example:"
            echo -e  "mariadb -e \"SET GLOBAL gtid_slave_pos =  '0-1-14'\""
            echo -e  "mariadb -e \"stop slave;CHANGE MASTER TO MASTER_HOST='\$pm1' , MASTER_USER='repuser' , MASTER_PASSWORD='aBcd123%123%aBc' , MASTER_USE_GTID = slave_pos;start slave;show slave status\G\""
            echo -e  "mariadb -e  \"show slave status\G\" | grep  \"Slave_\" \n"
        fi
    fi
}

load_default_dbrm_variables() {
    # Fixed Paths
    CS_CONFIGS_PATH="/etc/columnstore"
    DBRM_PATH="/var/lib/columnstore/data1/systemFiles/dbrm"
    STORAGEMANAGER_PATH="/var/lib/columnstore/storagemanager"
    STORAGEMANGER_CNF="$CS_CONFIGS_PATH/storagemanager.cnf"

     # Default variables
    backup_base_name="dbrm_backup"
    backup_interval_minutes=90
    retention_days=0
    backup_location=/tmp/dbrm_backups
    storage=$(grep -m 1 "^service = " $STORAGEMANGER_CNF | awk '{print $3}')
    skip_storage_manager=false
    dbrm_backup_mode="once"
    quiet=false
    skip_save_brm=false
    skip_locks=false
    skip_polls=false
    poll_interval=3
    poll_max_wait=60;
    list_dbrm_backups=false
    if [ "$storage" == "S3" ]; then 
        DBRM_PATH="/var/lib/columnstore/storagemanager"
    fi
    today=$(date +%m-%d-%Y)

    # PM and PM number
    if [ ! -f /var/lib/columnstore/local/module ]; then  
        pm="pm1"; 
    else 
        pm=$(cat /var/lib/columnstore/local/module);  
    fi;
    PM_NUMBER=$(echo "$pm" | tr -dc '0-9')
    if [[ -z $PM_NUMBER ]]; then PM_NUMBER=1; fi;

    # Tracks if flush read lock has been run
    read_lock=false
    columnstore_online=false
    confirm_xmllint_installed
    # Number of DBroots
    # Integer usually 1 or 3
    DBROOT_COUNT=$(xmllint --xpath "string(//DBRootCount)" $CS_CONFIGS_PATH/Columnstore.xml)
    ASSIGNED_DBROOT=$(xmllint --xpath "string(//ModuleDBRootID$PM_NUMBER-1-3)" $CS_CONFIGS_PATH/Columnstore.xml)

    # defaults affecting issue_write_locks but dont mean anything for dbrm_backup
    skip_mdb=false
    mode="direct"
}

load_default_dbrm_restore_variables() {
    # Fixed Paths
    CS_CONFIGS_PATH="/etc/columnstore"
    DBRM_PATH="/var/lib/columnstore/data1/systemFiles/dbrm"
    STORAGEMANAGER_PATH="/var/lib/columnstore/storagemanager"
    STORAGEMANGER_CNF="$CS_CONFIGS_PATH/storagemanager.cnf"

    auto_start=true
    backup_location="/tmp/dbrm_backups"
    storage=$(grep -m 1 "^service = " $STORAGEMANGER_CNF | awk '{print $3}')
    backup_folder_to_restore=""
    skip_dbrm_backup=false
    skip_storage_manager=false
    list_dbrm_backups=false

    if [ "$storage" == "S3" ]; then 
        DBRM_PATH="/var/lib/columnstore/storagemanager"
    fi
}

print_dbrm_backup_help_text() {
    echo "
    Columnstore DBRM Backup

        -m     | --mode                  ['loop','once']; Determines if this script runs in a forever loop sleeping -i minutes or just once 
        -i     | --interval              Number of minutes to sleep when --mode loop 
        -r     | --retention-days        Retain dbrm backups created within the last X days, the rest are deleted
        -bl    | --backup-location       Path of where to save the dbrm backups on disk
        -nb    | --name-backup           Define the prefix of the backup - default: dbrm_backup+date +%Y%m%d_%H%M%S
        -ssm   | --skip-storage-manager  Skip backing up storagemanager directory
        -sbrm  | --skip-save-brm         Skip saving brm prior to running a dbrm backup - ideal for dirty backups
        -slock | --skip-locks            Skip issuing flush read locks to dbrms
        -spoll | --skip-polls            Skip polling to confirm locks are released
        -pi    | --poll-interval         Number of seconds to wait between polls to confirm
        -pmw   | --poll-max-wait         Max number of minutes for polling checks for writes to wait before exiting as a failed dbrm backup attempt
        -q     | --quiet                 Suppress non-error output
        -li    | --list                  List available dbrm backups in the backup location

        Default: ./$0 dbrm_backup -m once --retention-days 0 --backup-location /tmp/dbrm_backups

        Examples:
            ./$0 dbrm_backup --list
            ./$0 dbrm_backup --backup-location /mnt/columnstore/dbrm_backups 
            ./$0 dbrm_backup --retention-days 7 --backup-location /mnt/dbrm_backups --mode once -nb my-one-off-backup-before-upgrade
            ./$0 dbrm_backup --retention-days 7 --backup-location /mnt/dbrm_backups --mode loop --interval 90
            
        Cron Example:
            sudo crontab -e
            */60 */3 * * *  /tmp/$0 dbrm_backup -m once --retention-days 7 --backup-location /tmp/dbrm_backups  >> /tmp/dbrm_backups/cs_dbrm_backup.log  2>&1
    ";
}

print_dbrm_restore_help_text() {
    echo "
    Columnstore DBRM Restore

        -bl  | --backup-location       Path of where the dbrm backups exist on disk
        -l   | --load                  Name of the directory to restore from -bl
        -ns  | --no-start              Do not attempt columnstore startup post dbrm_restore
        -sdbk| --skip-dbrm-backup      Skip backing up dbrms before restoring
        -ssm | --skip-storage-manager  Skip restoring storagemanager directory
        -li  | --list                  List available dbrm backups in the backup location

        Default: ./$0 dbrm_restore --backup-location /tmp/dbrm_backups 

        Examples:
            ./$0 dbrm_restore --list
            ./$0 dbrm_restore --backup-location /tmp/dbrm_backups --load dbrm_backup_20240318_172842    
            ./$0 dbrm_restore --backup-location /tmp/dbrm_backups --load dbrm_backup_20240318_172842 --no-start
    ";
}

parse_dbrms_variables() {

    # Dynamic Arguments
    while [[ $# -gt 0 ]]; do
        key="$1"
        case $key in
            dbrm_backup)
                shift # past argument
                ;;
            -i|--interval)
                backup_interval_minutes="$2"
                shift # past argument
                shift # past value
                ;;
            -r|--retention-days)
                retention_days="$2"
                shift # past argument
                shift # past value
                ;;
            -bl|--backup-location)
                backup_location="$2"
                shift # past argument
                shift # past value
                ;;
            -m|--mode)
                dbrm_backup_mode="$2"
                shift # past argument
                shift # past value
                ;;
            -nb|--name-backup)
                backup_base_name="$2"
                shift # past argument
                shift # past value
                ;;
            -ssm|--skip-storage-manager)
                skip_storage_manager=true
                shift # past argument
                ;;
            -q | --quiet)
                quiet=true
                shift # past argument
                ;;
            -sbrm| --skip-save-brm)
                skip_save_brm=true
                shift # past argument
                ;;
            -slock| --skip-locks)
                skip_locks=true
                shift # past argument
                ;;
            -spoll| --skip-polls)
                skip_polls=true
                shift # past argument
                ;;
            -pi| --poll-interval)
                poll_interval="$2"
                shift # past argument
                shift # past value
                ;;
            -pmw| --poll-max-wait)
                poll_max_wait="$2"
                shift # past argument
                shift # past value
                ;;
            -li | --list)
                list_dbrm_backups=true
                shift # past argument
                ;;
            -h|--help|-help|help)
                print_dbrm_backup_help_text;
                exit 1;
                ;;
            *)  # unknown option
                printf "\nunknown flag: $1\n"
                print_dbrm_backup_help_text
                exit 1;
        esac
    done

    confirm_integer_else_fail "$retention_days" "Retention"

    

}

parse_dbrm_restore_variables() {

    # Dynamic Arguments
    while [[ $# -gt 0 ]]; do
        key="$1"
        case $key in
            dbrm_restore)
                shift # past argument
                ;;
            -bl|--backup-location)
                backup_location="$2"
                shift # past argument
                shift # past value
                ;;
            -l|--load)
                backup_folder_to_restore="$2"
                shift # past argument
                shift # past value
                ;;
            -ns  | --no-start)
                auto_start=false
                shift # past argument
                ;;
            -sdbk| --skip-dbrm-backup)
                skip_dbrm_backup=true
                shift # past argument
                ;;
            -ssm|--skip-storage-manager)
                skip_storage_manager=true
                shift # past argument
                ;;
            -li | --list)
                list_dbrm_backups=true
                shift # past argument
                ;;
            -h|--help|-help|help)
                print_dbrm_restore_help_text;
                exit 1;
                ;;
            *)  # unknown option
                printf "\nunknown flag: $1\n"
                print_dbrm_restore_help_text
                exit 1;
        esac
    done

    if [[ "${backup_location: -1}" == "/" ]]; then
        # Remove the final /
        backup_location="${backup_location%/}"
    fi
}

confirm_integer_else_fail() {
    local input="$1"
    local variable_name="$2"

    # Regular expression to match integer values
    if [[ $input =~ ^[0-9]+$ ]]; then
        return 0
    else
        printf "[!] $variable_name = '$input' is not an integer.\n\n"
        alert "[!] $variable_name = '$input' is not an integer.\n\n"
        exit 2;
    fi
}

confirm_numerical_or_decimal_else_fail() {
    local input="$1"
    local variable_name="$2"

    # Regular expression to match numerical or decimal values
    if [[ $input =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
        return 0
    else
        printf "[!] $variable_name = '$input' is not numerical or decimal.\n\n"
        alert "[!] $variable_name = '$input' is not numerical or decimal.\n\n"
        exit 2;
    fi
}

validation_prechecks_for_dbrm_backup() {
    echo "Prechecks"
    # Confirm storage not empty
    if [ -z "$storage" ]; then printf "[!] Empty storage: \ncheck: grep -m 1 \"^service = \" \$STORAGEMANGER_CNF | awk '{print \$3}' \n\n"; fi;

    # Check mode type
    errors=""
    case $dbrm_backup_mode in
        once)
             errors+="" ;;
        loop)
             errors+="" ;;
        *)  # unknown option
            printf "\nunknown mode: $dbrm_backup_mode\n"
            printf "Only 'once' & 'loop' allowed\n\n"
            print_dbrm_backup_help_text
            exit 2;
    esac

    # Check numbers
    confirm_numerical_or_decimal_else_fail "$backup_interval_minutes" "Interval"

    # Poll Variable Checks
    confirm_integer_else_fail "$poll_interval" "poll_interval"
    confirm_integer_else_fail "$poll_max_wait" "poll_max_wait"
    max_poll_attempts=$((poll_max_wait * 60 / poll_interval))
    if [ "$max_poll_attempts" -lt 1 ]; then max_poll_attempts=1; fi;

    # Detect if columnstore online
    if [ "$mode" != "direct" ] && [ "$mode" != "indirect" ] ; then printf "\n[!!!] Invalid field --mode: $mode\n"; exit 1; fi
    if [ $mode == "direct" ]; then
        if [ -z $(pidof PrimProc) ] || [ -z $(pidof WriteEngineServer) ]; then 
            printf " - Columnstore is OFFLINE \n"; 
            export columnstore_online=false; 
        else 
            printf " - Columnstore is ONLINE - safer if offline (but not required to be offline) \n"; 
            export columnstore_online=true; 
        fi
    fi;

    # Check backup location exists
    if [ ! -d $backup_location ]; then 
        echo " - Created: $backup_location"
        mkdir "$backup_location"; 
    fi;

    # Confirm bucket connection
    if [ "$storage" == "S3" ]; then
        if ! testS3Connection 1>/dev/null 2>/dev/null; then
            printf "\n[!] Failed testS3Connection\n\n"
            exit 1;
        fi
    fi;
}

validation_prechecks_before_listing_restore_options() {

    # confirm backup directory exists
    if [ ! -d $backup_location ]; then
        printf "[!!] Backups Directory does NOT exist --backup-location $backup_location \n"
        printf "ls -la $backup_location\n\n"
        exit 1;
    fi
    
    # Check if backup directory is empty
    if [ -z "$(find "$backup_location" -mindepth 1 | head )" ]; then
        printf "[!!] Backups Directory is empty --backup-location $backup_location \n"
        printf "ls -la $backup_location\n\n"
        exit 1
    fi
}

validation_prechecks_for_dbrm_restore() {
    
    printf "Prechecks\n"
    echo "--------------------------------------------------------------------------"
    # Confirm storage not empty
    if [ -z "$storage" ]; then printf "[!] Empty storage: \ncheck: grep -m 1 \"^service = \" \$STORAGEMANGER_CNF | awk '{print \$3}' \n\n"; fi;

    # Check backup directory exists
    if [ ! -d $backup_location ]; then 
        printf "[!] \$backup_location: Path of backups does Not exist\n"
        printf "Path: $backup_location\n\n"
        exit 2;
    fi

    # Check specific backup exists 
    backup_folder_to_restore_dbrms=$backup_folder_to_restore
    if [ $storage == "S3" ]; then
        backup_folder_to_restore_dbrms="${backup_folder_to_restore}/dbrms"
    fi

    if [ ! -d "${backup_location}/${backup_folder_to_restore_dbrms}" ]; then 
        printf "[!] --load: Path of backup to restore does NOT exist\n"
        printf "Path: ${backup_location}/${backup_folder_to_restore_dbrms}\n\n"
        exit 2;
    else   
        echo " - Backup directory exists"
        if [ "$(ls -A ${backup_location}/${backup_folder_to_restore_dbrms})" ]; then

            expected_files=(
                "BRM_saves_current"
                "BRM_saves_em"
                "BRM_saves_journal"
                "BRM_saves_vbbm"
                "BRM_saves_vss"
                "BRM_savesA_em"
                "BRM_savesA_vbbm"
                "BRM_savesA_vss"
                "BRM_savesB_em"
                "BRM_savesB_vbbm"
                "BRM_savesB_vss"
                "oidbitmap"
                "SMTxnID"
            )
            
            # Check if all expected files exist
            for file in "${expected_files[@]}"; do
                if [ ! -f "${backup_location}/${backup_folder_to_restore_dbrms}/${file}" ]; then
                    printf "[!] File not found: ${file} in the DBRM backup directory\n"
                    printf "Path: ${backup_location}/${backup_folder_to_restore_dbrms}/${file}\n\n"
                    exit 2;
                fi
            done

            # For S3 check storagemanager dir exists in backup unless skip storagemanager is passed
            if [ "$storage" == "S3" ] && [ $skip_storage_manager == false ]; then
                if [ ! -d "${backup_location}/${backup_folder_to_restore}/metadata" ]; then 
                    printf "\n[!!] Path Not Found: ${backup_location}/${backup_folder_to_restore}/metadata \n"
                    printf "Retry with a different backup to restore or use flag --skip-storage-manager\n\n"
                    exit 2;
                fi;
            fi
            

            printf " - Backup contains all files\n"

        else
            printf "[!] No files found in the DBRM backup directory\n"
            printf "Path: ${backup_location}/${backup_folder_to_restore_dbrms}\n\n"
            exit 2;
        fi
    fi

    # Confirm bucket connection
    if [ "$storage" == "S3" ]; then
        if testS3Connection 1>/dev/null 2>/dev/null; then
            echo " - S3 Connection works"
        else
            printf "\n[!] Failed testS3Connection\n\n"
            exit 1;
        fi
    fi;

    # Download cs_package_manager.sh if not exists
    if [ ! -f "cs_package_manager.sh" ]; then
        wget https://raw.githubusercontent.com/mariadb-corporation/mariadb-columnstore-engine/develop/cmapi/scripts/cs_package_manager.sh; chmod +x cs_package_manager.sh;
    fi; 
    if source cs_package_manager.sh source ;then 
        echo " - Sourced cs_package_manager.sh"
    else
        printf "\n[!!] Failed to source cs_package_manager.sh\n\n"
        exit 1;
        
    fi

    # Confirm the function exists and the source of cs_package_manager.sh worked
    if command -v check_package_managers &> /dev/null; then
        # The function exists, call it
        check_package_managers
    else
        echo "Error: 'check_package_managers' function not found via cs_package_manager.sh";
        exit 1;
    fi
    cs_package_manager_functions=(
        "start_cmapi"
        "start_mariadb"
        "init_cs_up"
    )

    for func in "${cs_package_manager_functions[@]}"; do
        if command -v $func &> /dev/null; then
            continue;
        else
            echo "Error: '$func' function not found via cs_package_manager.sh";
            exit 1;
        fi
    done
}

print_if_not_quiet() {
    if ! $quiet; then
        printf "$1"; 
    fi
}

process_dbrm_backup() {

    load_default_dbrm_variables
    parse_dbrms_variables "$@";
    handle_list_dbrm_backups
    
    if ! $quiet ; then

        printf "\nDBRM Backup\n"; 
        echo "--------------------------------------------------------------------------"
        echo "Skips:  save_brm($skip_save_brm)   Locks($skip_locks)  Polls($skip_polls) "
        if [ "$storage" == "S3" ]; then echo "Skips:  Storagemanager($skip_storage_manager)"; fi;
        echo "--------------------------------------------------------------------------"
        printf "CS Storage:     $storage\n"; 
        printf "Source:         $DBRM_PATH\n"; 
        printf "Backups:        $backup_location\n"; 
        if [ "$mode" == "loop" ]; then 
            printf "Interval:       $backup_interval_minutes minutes\n"; 
        fi;
        printf "Retention:      $retention_days day(s)\n"
        printf "Mode:           $dbrm_backup_mode\n"
        if ! $skip_polls && ! $skip_locks ; then 
            printf "Poll Interval:  $poll_interval seconds\n"
            printf "Poll Max Wait:  $poll_max_wait seconds\n"
        fi;

        echo "--------------------------------------------------------------------------"
    fi;

    validation_prechecks_for_dbrm_backup

    sleep_seconds=$((backup_interval_minutes * 60));
    while true; do
        # Create a new backup directory
        timestamp=$(date +%Y%m%d_%H%M%S)
        backup_folder="$backup_location/${backup_base_name}_${timestamp}"
        mkdir -p "$backup_folder"

        issue_write_locks;
        run_save_brm
        trap handle_ctrl_c_dbrm_backup SIGINT
        
        printf "\nDBRM Backup\n"
        # Copy files to the backup directory
        if [[ $skip_storage_manager == false || $storage == "LocalStorage" ]]; then
            print_if_not_quiet " - Copying $DBRM_PATH ...";
            cp -arp "$DBRM_PATH"/* "$backup_folder"
            print_if_not_quiet " Done\n";
        fi

        if [ "$storage" == "S3" ]; then
            # smcat em files to disk
            print_if_not_quiet " - Copying DBRMs from bucket ...";
            mkdir $backup_folder/dbrms/
            smls /data1/systemFiles/dbrm 2>/dev/null > $backup_folder/dbrms/dbrms.txt
            smcat /data1/systemFiles/dbrm/BRM_saves_current  2>/dev/null > $backup_folder/dbrms/BRM_saves_current
            smcat /data1/systemFiles/dbrm/BRM_saves_journal 2>/dev/null > $backup_folder/dbrms/BRM_saves_journal
            smcat /data1/systemFiles/dbrm/BRM_saves_em 2>/dev/null > $backup_folder/dbrms/BRM_saves_em
            smcat /data1/systemFiles/dbrm/BRM_saves_vbbm 2>/dev/null > $backup_folder/dbrms/BRM_saves_vbbm
            smcat /data1/systemFiles/dbrm/BRM_saves_vss 2>/dev/null > $backup_folder/dbrms/BRM_saves_vss
            smcat /data1/systemFiles/dbrm/BRM_savesA_em 2>/dev/null > $backup_folder/dbrms/BRM_savesA_em
            smcat /data1/systemFiles/dbrm/BRM_savesA_vbbm 2>/dev/null > $backup_folder/dbrms/BRM_savesA_vbbm
            smcat /data1/systemFiles/dbrm/BRM_savesA_vss 2>/dev/null > $backup_folder/dbrms/BRM_savesA_vss
            smcat /data1/systemFiles/dbrm/BRM_savesB_em 2>/dev/null > $backup_folder/dbrms/BRM_savesB_em
            smcat /data1/systemFiles/dbrm/BRM_savesB_vbbm 2>/dev/null > $backup_folder/dbrms/BRM_savesB_vbbm
            smcat /data1/systemFiles/dbrm/BRM_savesB_vss 2>/dev/null > $backup_folder/dbrms/BRM_savesB_vss
            smcat /data1/systemFiles/dbrm/oidbitmap 2>/dev/null > $backup_folder/dbrms/oidbitmap
            smcat /data1/systemFiles/dbrm/SMTxnID 2>/dev/null > $backup_folder/dbrms/SMTxnID
            smcat /data1/systemFiles/dbrm/tablelocks 2>/dev/null > $backup_folder/dbrms/tablelocks
            print_if_not_quiet " Done\n";
        fi

        if [ $retention_days -gt 0 ] ; then
            # Clean up old backups
            # example: find /tmp/dbrm_backups -maxdepth 1 -type d -name "dbrm_backup_*" -mtime +1 -exec rm -r {} \;
            print_if_not_quiet " - applying retention policy ...";
            find "$backup_location" -maxdepth 1 -type d -name "${backup_base_name}_*" -mtime +$retention_days -exec rm -r {} \;
            print_if_not_quiet " Done\n";
        fi;

        clear_read_lock
        printf "\nCreated: $backup_folder\n"

        if [ "$dbrm_backup_mode" == "once" ]; then  
            end=$(date +%s)
            runtime=$((end-start))
            if ! $quiet; then  printf "\nRuntime: $runtime\n"; fi;
            break;  
        fi;

        printf "Sleeping ... $sleep_seconds seconds\n"
        sleep "$sleep_seconds"
    done
    
    print_if_not_quiet "Complete\n\n"
}

is_cmapi_installed() {

    cmapi_installed_command=""
    case $package_manager in
        yum ) 
            cmapi_installed_command="yum list installed MariaDB-columnstore-cmapi &> /dev/null;";
            ;;
        apt )
            cmapi_installed_command="dpkg-query -s mariadb-columnstore-cmapi &> /dev/null;";
            ;;
        *)  # unknown option
            echo "\npackage manager not implemented: $package_manager\n"
            exit 2;
    esac

    if eval $cmapi_installed_command ; then
        return 0;
    else
        return 1;

    fi;
}

start_mariadb_cmapi_columnstore() {

    start_mariadb
    start_cmapi
    init_cs_up

    # For verbose debugging
    #grep -i rollbackAll /var/log/mariadb/columnstore/debug.log | tail -n 3 | awk '{ print $1, $2, $3, $(NF-2), $(NF-1), $NF }'
   
}

# Currently assumes systemd installed
shutdown_columnstore_mariadb_cmapi() {

    local print_formatting=35
    init_cs_down 
    wait_cs_down 0 

    printf "%-${print_formatting}s ... " " - Stopping MariaDB Server"
    if ! systemctl stop mariadb; then
        echo "[!!] Failed to stop mariadb"
        exit 1;
    else 
        printf "Done\n"
    fi

    if is_cmapi_installed ; then
        printf "%-${print_formatting}s ... " " - Stopping CMAPI"
        if ! systemctl stop mariadb-columnstore-cmapi; then
            echo "[!!] Failed to stop CMAPI"
            exit 1;
        else 
            printf "Done\n"
        fi
    fi
}

# Input
# $1 - directory to search
# Output
#   subdir_dbrms
#   latest_em_file
#   em_file_size
#   em_file_created
#   em_file_full_path
#   storagemanager_dir_exists
get_latest_em_from_dbrm_directory() {
    
    subdir_dbrms=""
    latest_em_file=""
    em_file_size=""
    em_file_created=""
    em_file_full_path=""
    storagemanager_dir_exists=true

    # Find the most recently modified file in the current subdirectory
    if [ $storage == "S3" ]; then
        subdir_dbrms="${1}/dbrms/"
        subdir_metadata="${1}/metadata/data1/systemFiles/dbrm/"

        # Handle missing metadata directory
        if [ ! -d $subdir_dbrms ]; then
            printf "%-45s Missing dbrms sub directory\n" "$(basename $1)"
            return 1;
        fi

        
        if [ -d "${1}/metadata" ]; then
            latest_em_meta_file=$(find "${subdir_metadata}" -maxdepth 1 -type f -name "BRM_saves*_em.meta" -exec ls -lat {} + | awk 'NR==1 {printf "%-12s %-4s %-2s %-5s %s\n", $5, $6, $7, $8, $9}'| head -n 1 )
        else
            # Handle missing metadata directory & guess the latest em file based on the largest size
            
            # Example:     find /tmp/dbrm_backups/dbrm_backup_20240605_180906/dbrms -maxdepth 1 -type f -name "BRM_saves*_em" -exec ls -lhS {} +
            latest_em_meta_file=$(find "${subdir_dbrms}" -maxdepth 1 -type f -name "BRM_saves*_em" -exec ls -lhS  {} + | awk 'NR==1 {printf "%-12s %-4s %-2s %-5s %s\n", $5, $6, $7, $8, $9}'| head -n 1)
            storagemanager_dir_exists=false
        fi

        em_meta_file_name=$(basename "$latest_em_meta_file")
        latest_em_file="$subdir_dbrms$(echo $em_meta_file_name | sed 's/\.meta$//' )"
        em_file_size=$(ls -la "$latest_em_file" | awk '{print $5}' )
        em_file_created=$(echo "$latest_em_meta_file" | awk '{print $2,$3,$4}' )
        em_file_full_path=$latest_em_file

        if [ ! -f $latest_em_file ]; then
            echo "S3 List Option: Failed to find $latest_em_file"
            exit;
        fi        
    else
        subdir_dbrms="$1"
        latest_em_file=$(find "${subdir_dbrms}" -maxdepth 1 -type f -name "BRM_saves*_em" -exec ls -lat {} + 2>/dev/null | awk 'NR==1 {printf "%-12s %-4s %-2s %-5s %s\n", $5, $6, $7, $8, $9}'| head -n 1)
        em_file_size=$(echo "$latest_em_file" | awk '{print $1}' )
        em_file_created=$(echo "$latest_em_file" | awk '{print $2,$3,$4}' )
        em_file_full_path=$(echo $latest_em_file | awk '{print $NF}' )
    fi
}

list_restore_options_from_backups() {

    if [ $storage == "S3" ]; then 
        echo " Not implemented" ; exit 1;
    else 
        
        if [ -n "$retention_days" ] && [ $retention_days -ne 0 ]; then
            echo "--------------------------------------------------------------------------"
            echo "Retention Policy: $retention_days days"
        fi;
        echo "--------------------------------------------------------------------------"
        printf "%-45s %-13s %-15s %-12s %-12s %-10s %-10s %-10s\n" "Options" "EM-Updated" "Extent Map" "EM-Size" "Journal-Size" "VBBM-Size" "VSS-Size" "Backup Days Old"

        # Iterate over subdirectories
        for subdir in "${backup_location}"/*; do
            
            if [ -f "${subdir}/cs-localfiles.tar.pigz" ]; then
                em_file_created=$( date -r "$subdir" +"%b %d %H:%M" )

                # parse from ${subdir}/backup_summary_info.txt
                if [ -f "${subdir}/backup_summary_info.txt" ]; then
                    em_file_created="$(grep -m 1 "em_file_created" "${subdir}/backup_summary_info.txt" | awk -F': ' '{print $2}')"
                    em_file_name="$(grep -m 1 "em_file_name" "${subdir}/backup_summary_info.txt" | awk '{print $2}')"
                    em_file_size="$(grep -m 1 "em_file_size" "${subdir}/backup_summary_info.txt" | awk '{print $2}')"
                    journal_file="$(grep -m 1 "journal_file" "${subdir}/backup_summary_info.txt" | awk '{print $2}')"
                    vbbm_file="$(grep -m 1 "vbbm_file" "${subdir}/backup_summary_info.txt" | awk '{print $2}')"
                    vss_file="$(grep -m 1 "vss_file" "${subdir}/backup_summary_info.txt" | awk '{print $2}')"
                    compression="$(grep -m 1 "compression" "${subdir}/backup_summary_info.txt" | awk '{print $2}')"
                else
                    em_file_created="N/A"
                    em_file_size="N/A"
                    em_file_name="N/A"
                    journal_file="N/A"
                    vbbm_file="N/A"
                    vss_file="N/A"
                    compression="N/A"
                fi   

                file_age=$(($(date +%s) - $(date -r "$subdir" +%s)))
                file_age_days=$((file_age / 86400))
                will_delete="$(find "$subdir" -mindepth 1 -maxdepth 1 -type d -name "*" -mtime +$retention_days -exec echo {} \;)"
                if [ -n "$will_delete" ]; then
                    file_age_days="$file_age_days (Will Delete)"
                fi

                printf "%-35s %-9s %-13s %-15s %-12s %-12s %-10s %-10s %-10s\n" "$(basename "$subdir")" "$compression" "$em_file_created" "$em_file_name" "$em_file_size" "$journal_file" "$vbbm_file" "$vss_file" "$file_age_days"
                continue
            fi
            
            get_latest_em_from_dbrm_directory "$subdir/data1/systemFiles/dbrm/"
               
            if [ -f "${subdir_dbrms}/BRM_saves_journal" ]; then
                em_file_name=$(basename "$em_file_full_path")
                version_prefix=${em_file_name::-3}
                journal_file=$(ls -la "${subdir_dbrms}/BRM_saves_journal" 2>/dev/null | awk 'NR==1 {print $5}' )
                vbbm_file=$(ls -la "${subdir_dbrms}/${version_prefix}_vbbm" 2>/dev/null | awk 'NR==1 {print $5}' )
                vss_file=$(ls -la "${subdir_dbrms}/${version_prefix}_vss" 2>/dev/null | awk 'NR==1 {print $5}' )

                file_age=$(($(date +%s) - $(date -r "$subdir" +%s)))
                file_age_days=$((file_age / 86400))

                # Check if the backup will be deleted given the retention policy defined
                if [ -n "$retention_days" ] && [ $retention_days -ne 0 ]; then 
                    # file_age_days=$((file_age / 60));  will_delete="$(find "$subdir" -mindepth 1 -maxdepth 1 -type d -name "*" -mmin +$retention_days -exec echo {} \;)"
                    will_delete="$(find "$subdir" -mindepth 1 -maxdepth 1 -type d -name "*" -mtime +$retention_days -exec echo {} \;)"

                    if [ -n "$will_delete" ]; then
                        file_age_days="$file_age_days (Will Delete)"
                    fi
                fi;

                printf "%-45s %-13s %-15s %-12s %-12s %-10s %-10s %-10s\n" "$(basename "$subdir")" "$em_file_created" "$em_file_name" "$em_file_size" "$journal_file" "$vbbm_file" "$vss_file" "$file_age_days"
            fi
        done
    fi


}

list_dbrm_restore_options_from_backups() {

    if [ -n "$retention_days" ] && [ $retention_days -ne 0 ]; then
        echo "--------------------------------------------------------------------------"
        echo "Retention Policy: $retention_days days"
    fi;
    echo "--------------------------------------------------------------------------"
    printf "%-45s %-13s %-15s %-12s %-12s %-10s %-10s\n" "Options" "EM-Updated" "Extent Map" "EM-Size" "Journal-Size" "VBBM-Size" "VSS-Size"

    # Iterate over subdirectories
    for subdir in "${backup_location}"/*; do

        get_latest_em_from_dbrm_directory "$subdir"

        if [ -f "${subdir_dbrms}/BRM_saves_journal" ]; then
            em_file_name=$(basename "$em_file_full_path")
            version_prefix=${em_file_name::-3}
            journal_file=$(ls -la "${subdir_dbrms}/BRM_saves_journal" 2>/dev/null | awk 'NR==1 {print $5}' )
            vbbm_file=$(ls -la "${subdir_dbrms}/${version_prefix}_vbbm" 2>/dev/null | awk 'NR==1 {print $5}' )
            vss_file=$(ls -la "${subdir_dbrms}/${version_prefix}_vss" 2>/dev/null | awk 'NR==1 {print $5}' )
            if [ $storagemanager_dir_exists == false ]; then
                vss_file+=" (No Storagemanager Dir)"
            fi; 
            
            printf "%-45s %-13s %-15s %-12s %-12s %-10s %-10s\n" "$(basename "$subdir")" "$em_file_created" "$em_file_name" "$em_file_size" "$journal_file" "$vbbm_file" "$vss_file"
        fi
    done


}

# $1 message
dynamic_list_dbrm_backups() {

    validation_prechecks_before_listing_restore_options
    printf "$1";
    list_dbrm_restore_options_from_backups "$@"
    echo "--------------------------------------------------------------------------"
    printf "Restore with ./$0 dbrm_restore --backup-location $backup_location --load <backup_folder_from_above>\n\n"
}

handle_list_dbrm_backups () {
    
    if $list_dbrm_backups; then
        dynamic_list_dbrm_backups "\nExisting DBRM Backups\n"
        exit 0
    fi;

} 

handle_empty_dbrm_restore_folder() { 

    if [ -z "$backup_folder_to_restore" ]; then
        dynamic_list_dbrm_backups "[!] Pick Option [Required]\n"
        exit 1
    fi;
}

process_dbrm_restore() {
    
    load_default_dbrm_restore_variables
    parse_dbrm_restore_variables "$@"

    # print current job variables
    printf "\nDBRM Restore Variables\n"
    echo "--------------------------------------------------------------------------"
    echo "Skips:    DBRM Backup($skip_dbrm_backup)    Storagemanager($skip_storage_manager)"
    echo "--------------------------------------------------------------------------"
    printf "CS Storage:           $storage \n" 
    printf "Backups Directory:    $backup_location \n"
    printf "Backup to Restore:    $backup_folder_to_restore \n\n"

    validation_prechecks_before_listing_restore_options
    handle_list_dbrm_backups
    handle_empty_dbrm_restore_folder
    validation_prechecks_for_dbrm_restore
    shutdown_columnstore_mariadb_cmapi

    # Take an automated backup
    if [[ $skip_dbrm_backup == false ]]; then
        printf " - Saving a DBRM backup before restoring ... \n"
        if ! process_dbrm_backup -bl $backup_location -r 0 -nb dbrms_before_restore_backup -slock --quiet ; then 
            echo "[!!] Failed to take a DBRM backup before restoring"
            echo "exiting ..."
            exit 1;
        fi;
    fi;

    # Detect newest date _em from the set, if smaller than the current one throw a warning
    get_latest_em_from_dbrm_directory "${backup_location}/${backup_folder_to_restore}"
    if [ ! -f $em_file_full_path ]; then 
        echo "[!] Failed to parse _em file: $em_file_full_path does not exist"
        exit 1;
    fi;
    echo "em_file_full_path: $em_file_full_path"
    
    echo "latest_em_file: $latest_em_file"
    echo "em_file_size: $em_file_size"
    echo "em_file_created: $em_file_created"
    echo "storagemanager_dir_exists: $storagemanager_dir_exists"
    echo "subdir_dbrms: $subdir_dbrms"
    
    em_file_name=$(basename $em_file_full_path)
    prefix="${em_file_name%%_em}"
    echo "em_file_name: $em_file_name"
    echo "prefix: $prefix"

    if [ -z "$em_file_name" ]; then
        printf "[!] Undefined EM file name\n"
        printf "find "${backup_location}/${backup_folder_to_restore_dbrms}" -maxdepth 1 -type f -name "BRM_saves*_em" -exec ls -lat {} + \n\n"
        exit 1;
    fi

    # split logic between S3 & LocalStorage
    if [ $storage == "S3" ]; then 
        process_s3_dbrm_restore
    else
        process_localstorage_dbrm_restore
    fi;
}

# $1 - File to cat/ upload into S3
# $2 - Location in storagemanager to overwrite
# example:  smput_or_error "${backup_location}/${backup_folder_to_restore_dbrms}/${prefix}_em" "/data1/systemFiles/dbrm/BRM_saves_em"
smput_or_error() {
    if ! cat "$1" | smput "$2" 2>/dev/null; then
        printf "[!] Failed to smput: $1\n"
    else
        printf "."
    fi  
}

# Depends on 
# em_file - most recent EM
# em_file_full_path - full path to most recent EM
# em_file_name - Just the file name of the EM
# prefix - Prefix of the EM file
process_s3_dbrm_restore() {
    
    printf_offset=45
    printf "\nBefore DBRMs Restore\n"
    echo "--------------------------------------------------------------------------"
    if ! command -v smls  > /dev/null; then
        printf "[!] smls not installed ... Exiting\n\n"
        exit 1
    else 
        current_status=$(smls /data1/systemFiles/dbrm/ 2>/dev/null);
        if [ $? -ne 0 ]; then
            printf "\n[!] Failed to get smls status\n\n" 
            exit 1
        fi
        echo "$current_status" | grep -E "BRM_saves_em|BRM_saves_vbbm|BRM_saves_vss|BRM_saves_journal|BRM_saves_current|oidbitmap"
    fi

    printf "\nRestoring DBRMs\n"
    echo "--------------------------------------------------------------------------"
    printf " - Desired EM:    $em_file_full_path\n"
    printf " - Copying DBRMs: ${backup_location}/${backup_folder_to_restore_dbrms} -> S3 Bucket \n"

    printf "\nPreparing\n"
    printf "%-${printf_offset}s ..." " - Clearing storagemanager caches"
    if [ ! -d "$DBRM_PATH/cache" ]; then
        echo "Directory $DBRM_PATH/cache does not exist."
        exit 1
    fi
    for cache_dir in "${DBRM_PATH}/cache"/*; do
        if [ -d "${DBRM_PATH}/cache/${cache_dir}" ]; then
            echo "   - Removing Cache: $cache_dir"
        else
            printf "."
        fi
    done
    printf " Success\n"

    printf "%-${printf_offset}s ... " " - Starting mcs-storagemanager"
    if ! systemctl start mcs-storagemanager ; then
        echo "[!!] Failed to start mcs-storagemanager "
        exit 1;
    else
        printf "Done\n"
    fi

    printf "\nRestoring\n"
    printf "%-${printf_offset}s " " - Restoring Prefix: $prefix "
    smput_or_error "${backup_location}/${backup_folder_to_restore_dbrms}/${prefix}_em" "/data1/systemFiles/dbrm/BRM_saves_em"
    smput_or_error "${backup_location}/${backup_folder_to_restore_dbrms}/${prefix}_vbbm" "/data1/systemFiles/dbrm/BRM_saves_vbbm"
    smput_or_error "${backup_location}/${backup_folder_to_restore_dbrms}/${prefix}_vss" "/data1/systemFiles/dbrm/BRM_saves_vss"
    if ! echo "BRM_saves" | smput /data1/systemFiles/dbrm/BRM_saves_current 2>/dev/null; then
        printf "[!] Failed to smput: BRM_saves_current\n"
    else
        printf "."
    fi 

    em_files=(
        "BRM_saves_journal"
        "oidbitmap"
        "SMTxnID"
        "tablelocks"
    )
    for file in "${em_files[@]}"; do
        if [ ! -f "${backup_location}/${backup_folder_to_restore_dbrms}/${file}" ]; then
            printf "[!] File not found: ${file} in the S3 DBRM backup directory\n"
            printf "Path: ${backup_location}/${backup_folder_to_restore_dbrms}/${file}\n\n"
            continue
        fi
        smput_or_error "${backup_location}/${backup_folder_to_restore_dbrms}/${file}" "/data1/systemFiles/dbrm/${file}"
    done
    printf " Success\n"

    printf "%-${printf_offset}s ... " " - Stopping mcs-storagemanager"
    if ! systemctl stop mcs-storagemanager ; then
        echo "[!!] Failed to stop mcs-storagemanager "
        exit 1;
    else
        printf "Done\n"
    fi
    printf "%-${printf_offset}s ... " " - clearShm"
    clearShm 
    printf "Done\n"
    sleep 2

    printf "%-${printf_offset}s ... " " - Starting mcs-storagemanager"
    if ! systemctl start mcs-storagemanager ; then
        echo "[!!] Failed to start mcs-storagemanager "
        exit 1;
    else
        printf "Done\n"
    fi

    manually_run_loadbrm_and_savebrm

    printf "\nAfter DBRM Restore\n"
    echo "--------------------------------------------------------------------------"
    current_status=$(smls /data1/systemFiles/dbrm/ 2>/dev/null);
    if [ $? -ne 0 ]; then
        printf "\n[!] Failed to get smls status\n\n" 
        exit 1
    fi
    echo "$current_status" | grep -E "BRM_saves_em|BRM_saves_vbbm|BRM_saves_vss|BRM_saves_journal|BRM_saves_current|oidbitmap"

    if $auto_start; then
        printf "\nStartup\n"
        echo "--------------------------------------------------------------------------"
        start_mariadb_cmapi_columnstore
    fi

    # printf "\n[+] Health Check ...\n"
    # sleep 2
    # # run a basic health check
    # mariadb -e "create database if not exists $backup_folder_to_restore"
    # mariadb $backup_folder_to_restore -e "drop table if exists t1"
    # mariadb $backup_folder_to_restore -e "create table t1 (a int) engine=columnstore"
    # mariadb $backup_folder_to_restore -e "insert into t1 values (1)"
    # mariadb $backup_folder_to_restore -e "update t1 set a=1"
    # mariadb $backup_folder_to_restore -e "delete from t1 where a=1"
    # mariadb -e "drop database if exists $backup_folder_to_restore"

    printf "\nDBRM Restore Complete\n\n"
}

# Depends on 
# em_file - most recent EM
# em_file_full_path - full path to most recent EM
# em_file_name - Just the file name of the EM
# prefix - Prefix of the EM
process_localstorage_dbrm_restore() {

    printf "\nBefore DBRMs Restore\n"
    echo "--------------------------------------------------------------------------"
    ls -la "${DBRM_PATH}" | grep -E "BRM_saves_em|BRM_saves_vbbm|BRM_saves_vss|BRM_saves_journal|BRM_saves_current"
    printf " - Clearing active DBRMs ... "
    if rm -rf $DBRM_PATH ; then
        printf "Done\n"
    else 
        echo "Failed to delete files in $DBRM_PATH "
        exit 1; 
    fi

    printf "\nRestoring DBRMs\n"
    echo "--------------------------------------------------------------------------"
    printf " - Desired EM: $em_file_full_path\n"
    printf " - Copying DBRMs: \"${backup_location}/${backup_folder_to_restore_dbrms}\" -> \"$DBRM_PATH\" \n"
    cp -arp "${backup_location}/${backup_folder_to_restore_dbrms}" $DBRM_PATH
    

    if [ "$prefix" != "BRM_saves" ]; then
        printf " - Restoring Prefix: $prefix \n"
        vbbm_name="${prefix}_vbbm"
        vss_name="${prefix}_vss"
        cp -arpf "${DBRM_PATH}/$em_file_name" "${DBRM_PATH}/BRM_saves_em"
        cp -arpf "${DBRM_PATH}/$vbbm_name"    "${DBRM_PATH}/BRM_saves_vbbm"
        cp -arpf "${DBRM_PATH}/$vss_name"     "${DBRM_PATH}/BRM_saves_vss"
    fi
    echo "BRM_saves" > "${DBRM_PATH}/BRM_saves_current"
    chown -R mysql:mysql "${DBRM_PATH}"
    clearShm
    sleep 2

    manually_run_loadbrm_and_savebrm
    
    printf "\nAfter DBRM Restore\n"
    echo "--------------------------------------------------------------------------"
    ls -la "${DBRM_PATH}" | grep -E "BRM_saves_em|BRM_saves_vbbm|BRM_saves_vss|BRM_saves_journal|BRM_saves_current"

    if $auto_start; then
        printf "\nStartup\n"
        echo "--------------------------------------------------------------------------"
        start_mariadb_cmapi_columnstore
    fi

    # printf "\n[+] Health Check ...\n"
    # sleep 2
    # # run a basic health check
    # mariadb -e "create database if not exists $backup_folder_to_restore"
    # mariadb $backup_folder_to_restore -e "drop table if exists t1"
    # mariadb $backup_folder_to_restore -e "create table t1 (a int) engine=columnstore"
    # mariadb $backup_folder_to_restore -e "insert into t1 values (1)"
    # mariadb $backup_folder_to_restore -e "update t1 set a=1"
    # mariadb $backup_folder_to_restore -e "delete from t1 where a=1"
    # mariadb -e "drop database if exists $backup_folder_to_restore"

    printf "\nDBRM Restore Complete\n\n"
}

manually_run_loadbrm_and_savebrm() {

    local print_formatting=45
    printf "%-${print_formatting}s ... " " - Running load_brm"
    if ! sudo -su mysql /usr/bin/load_brm /var/lib/columnstore/data1/systemFiles/dbrm/BRM_saves ; then
        printf "\n[!!] Failed to complete load_brm successfully\n\n"
        exit 1;
    fi;

    printf "%-${print_formatting}s ... " " - Starting mcs-controllernode"
    if ! systemctl start mcs-controllernode ; then
        echo "[!!] Failed to start mcs-controllernode "
        exit 1;
    else
        printf "Done\n"
    fi;    

    printf "%-${print_formatting}s ... " " - Confirming extent map readable"
    if ! editem -i >/dev/null ; then
        echo "[!!] Failed to run editem -i (read the EM)"
        exit 1;
    else 
        printf "Done\n"
    fi;

    printf "%-${print_formatting}s ... \n" " - Running save_brm"
    if ! sudo -u mysql /usr/bin/save_brm ; then
        echo "[!!] Failed to run save_brm"
        exit 1;
    fi

    printf "%-${print_formatting}s ... " " - Stopping mcs-controllernode"
    if ! systemctl stop mcs-controllernode; then
        echo "[!!] Failed to stop mcs-controllernode"
        exit 1;
    else
        printf "Done\n"
    fi
    
    if [ $storage == "S3" ]; then 
        printf "%-${print_formatting}s ... " " - Stopping mcs-storagemanager"
        if ! systemctl stop mcs-storagemanager ; then
            echo "[!!] Failed to stop mcs-storagemanager "
            exit 1;
        else
            printf "Done\n"
        fi
    fi;

    printf "%-${print_formatting}s ... " " - clearShm"
    clearShm 
    printf "Done\n"
    sleep 2
}

# $1 - message
dynamic_list_backups() {

    validation_prechecks_before_listing_restore_options
    printf "$1";
    list_restore_options_from_backups "$@"
    echo "--------------------------------------------------------------------------"
    printf "Restore with ./$0 restore --backup-location $backup_location --load <backup_folder_from_above>\n\n"
}



handle_list_backups () {

    if $list_backups ; then
        dynamic_list_backups "\nExisting Backups\n"
        exit 0
    fi;
}

handle_empty_restore_folder() {   

    if [ -z "$load_date" ]; then
        dynamic_list_backups "\n[!] Pick Option [Required]\n"
        exit 1
    fi;
}

handle_apply_retention_only() {

    if $apply_retention_only; then
        s1=20
        s2=20
        printf "\nApplying Retention Only Variables\n"
        echo "--------------------------------------------------------------------------"
        if [ -n "$config_file" ] && [ -f "$config_file" ]; then
            printf "%-${s1}s %-${s2}s\n" "Configuration File:" "$config_file";
        fi
        echo "--------------------------------------------------------------------------"
        printf "%-${s1}s  %-${s2}s\n" "Backup Location:" "$backup_location";
        printf "%-${s1}s  %-${s2}s\n" "Timestamp:" "$(date +%m-%d-%Y-%H:%M:%S)";
        printf "%-${s1}s  %-${s2}s\n" "Retention:" "$retention_days";
        echo "--------------------------------------------------------------------------"
        
        if [ $retention_days -eq 0 ]; then
            printf "\n[!] Are you sure retention should be set to 0?\n"
        fi;

        apply_backup_retention_policy
        exit 0
    fi;
}


process_backup()
{   
    load_default_backup_variables;
    parse_backup_variables "$@";
    handle_list_backups "$@"
    handle_apply_retention_only "$@"
    print_backup_variables;
    check_for_dependencies "backup";
    validation_prechecks_for_backup;
    apply_backup_retention_policy;
    issue_write_locks;
    run_save_brm;
    run_backup;
}

process_restore()
{
    load_default_restore_variables;
    parse_restore_variables "$@";
    handle_list_backups "$@"
    handle_empty_restore_folder "$@"
    print_restore_variables;
    check_for_dependencies "restore";
    validation_prechecks_for_restore;
    run_restore;
}

global_dependencies() {
    if ! command -v curl &> /dev/null; then
        printf "\n[!] curl not found. Please install curl\n\n"
        exit 1; 
    fi   
}

print_mcs_bk_mgr_version_info() {
    echo "MariaDB Columnstore Backup Manager"
    echo "Version: $mcs_bk_manager_version"
}

global_dependencies

case "$action" in
    'help' | '--help' | '-help' | '-h') 	
        print_action_help_text
        ;;
    'backup') 
        process_backup "$@";
        ;;
    'dbrm_backup') 	
        process_dbrm_backup "$@";
        ;;
    'dbrm_restore') 	
        process_dbrm_restore "$@";
        ;;
    'restore') 	
        process_restore "$@";
        ;;
    '-v' | 'version' | '--version')
        print_mcs_bk_mgr_version_info
        ;;
    'source' )
        return 0;
        ;;
    *) 
        printf "\nunknown action: $action\n" 
        print_action_help_text
        ;;
esac

exit 0;