#!/bin/bash

#
# Anything-sync-daemon by graysky <graysky AT archlinux DOT us>
# Inspired by some code originally written by Colin Verot
#

# For debug, add DEBUG=1 asd
debug() {
  if [[ -v $DEBUG && $DEBUG -eq 1 ]]
  then
    echo -e "$@"
  fi
}

set -e
Error() {
  echo "Error occurred at $1"
  exit 1
}
trap 'Error $LINENO' ERR

debug "checking flock"
  command -v flock >/dev/null 2>&1 || {
  echo "I require flock but it's not installed. Aborting." >&2
  exit 1; }
debug "flock found"

BLD="\e[01m"
RED="\e[01;31m"
GRN="\e[01;32m"
BLU="\e[01;34m"
NRM="\e[00m"
VERS="6.0.0"

ASDCONF=${ASDCONF:-"/etc/asd.conf"}
DAEMON_FILE=${DAEMON_FILE:-"/run/asd"}
CRASH_RECOVERY_SUFFIX="crashrecovery"
ASDNAME="asd"
if [[ "$ASDCONF" != "/etc/asd.conf" ]]; then
    suffix=$(realpath "$ASDCONF" | sha256sum | cut -b -16)
    ASDNAME="$ASDNAME-$suffix"
    if [[ "$DAEMON_FILE" == "/run/asd" ]]; then
        DAEMON_FILE="/run/$ASDNAME"
    fi
fi
LOCKFILE="/run/$ASDNAME-lock"
# shellcheck disable=SC2015
[[ ${FLOCKER} != "$0" ]] && echo -e "${RED}Waiting for lock...${NRM}" && exec env FLOCKER="$0" flock --verbose -e "$LOCKFILE" "$0" "$*" ||

debug "${RED}ASDNAME:${NRM} $ASDNAME"
debug "${BLU}Configuration file:${NRM} $ASDCONF"
debug "${GRN}Daemon file:${NRM} $DAEMON_FILE"
debug "${RED}Lockfile:${NRM} $LOCKFILE\n"

# Setup check /etc/asd.conf
debug "Checking for existence of $ASDCONF"
if [[ -f "$ASDCONF" ]]; then
debug "Checking if asd is already running"
  if [[ ! -f "$DAEMON_FILE" ]]; then
    debug "Daemon file not found: asd is not running"
    # do nothing if asd is currently running, otherwise
    # make sure only comments and variables/arrays are defined to prevent
    # problems
    if grep -Eqv "^$|^\s*['\")#]|^[^ ]*=[^;]*" "$ASDCONF"; then
      # found text that isn't a blank line, comment, or variable present so exit
      echo -e " ${RED}ERROR:${NRM}${BLD} Syntax error(s) detected in ${BLU}$ASDCONF${NRM}"
      echo -e "${NRM}${BLD}Line number: offending comment${NRM}"
      grep -Evn "^$|^\s*['\")#]|^[^ ]*=[^;]*" "$ASDCONF"
      exit 1
    fi
  else
    debug "Daemon file found: asd is running"
  fi
  # shellcheck source=./asd.conf
  . "$ASDCONF"
else
  echo -e " ${BLD}Cannot find $ASDCONF so bailing.${NRM}"
  echo -e " ${BLD}Reinstall package to use anything-sync-daemon.${NRM}"
  exit 1
fi

# if asd is active, source the snapshot of /etc/asd.conf preferentially
if [[ -f "${DAEMON_FILE}.conf" ]]; then
  debug "\nRestoring old asd.conf configurations"
  unset WHATTOSYNC USE_OVERLAYFS USE_BACKUPS VOLATILE
  ASDCONF="${DAEMON_FILE}.conf"
  # shellcheck source=./asd.conf
  . "$ASDCONF"
fi

debug "" # just for an additional enter

# define default number of crash-recovery snapshots to save if the user did not
# and check that it is an integer if user did define it
if [[ -z "$BACKUP_LIMIT" ]]; then
  BACKUP_LIMIT=5
else
  if [[ "$BACKUP_LIMIT" =~ ^[0-9]+$ ]]; then
    # correctly setup
    /bin/true
  else
    echo -e " ${RED}ERROR:${NRM}${BLD} Bad value for BACKUP_LIMIT detected!${NRM}"
    exit 1
  fi
fi
debug "Backup limit: $BACKUP_LIMIT"

# saving current extended pattern matching setting
# command returns non zero exit code if the option is unset hence
# pipe with true
previous_extglob_setting=$(shopt -p extglob || true)

# ensuring pattern matching is enabled
shopt -s extglob

# removing any trailing slash(es) from the list of directories to sync
WHATTOSYNC=("${WHATTOSYNC[@]%%+(/)}")
VOLATILE="${VOLATILE%%+(/)}"

# setting everything back
$previous_extglob_setting
unset previous_extglob_setting

[[ -z "$VOLATILE" ]] && VOLATILE=/tmp
debug "Volatile dir: $VOLATILE"

# bail if $VOLATILE isn't tmpfs
df -T "$VOLATILE" | grep -m 1 -q '\( tmpfs \|^/dev/zram\)' || {
echo "$VOLATILE is not tmpfs/zram so running asd is pointless. Aborting." >&2
exit 1; }

[[ -z "$ENABLE_HARDLINK_SAFETY_CHECK" ]] && ENABLE_HARDLINK_SAFETY_CHECK=1
debug "Hardlink safety check: $ENABLE_HARDLINK_SAFETY_CHECK"

# simple function to determine user intent rather than using a null value
case "${USE_OVERLAYFS,,}" in
  y|yes|true|t|on|1|enabled|enable|use)
    OLFS=1
    ;;
  *)
    OLFS=0
    ;;
esac

# since the default for this one is a yes, need to force a null value to yes
[[ -z "${USE_BACKUPS,,}" ]] && USE_BACKUPS="yes"
debug "Backups enabled: $USE_BACKUPS"

case "${USE_BACKUPS,,}" in
  y|yes|true|t|on|1|enabled|enable|use)
    CRRE=1
    ;;
  *)
    CRRE=0
    ;;
esac
debug "Crash recovery enabled: $CRRE"

# determine is we are using overlayfs (v22 and below) or overlay (v23 and above)
# overlay FS v23 and later requires both an upper and a work directory, both on
# the same filesystem, but not part of the same subtree.
#
# ubuntu 15.04 has both overlay and overlayfs so prefer version 23
if [[ $OLFS -eq 1 ]]; then
  # first test if either module is manually loaded manually or hardcoded
  [[ $(grep -ciE "overlayfs$" /proc/filesystems) -eq 1 ]] && OLFSVER=22
  [[ $(grep -ciE "overlay$" /proc/filesystems) -eq 1 ]] && OLFSVER=23
  if [[ -z $OLFSVER ]]; then
    # since mount should call modprobe on invocation, check to see if either
    # module is in the tree using modinfo
    modinfo overlayfs &>/dev/null && OLFSVER=22
    modinfo overlay &>/dev/null && OLFSVER=23
    if [[ -z $OLFSVER ]]; then
      echo "overlayfs is not supported by your system, falling back to normal"
      OLFS=0
    fi
  fi
fi
debug "overlayfs enabled: $OLFS"
debug "overlayfs version: $OLFSVER"

# get distro name
# first try os-release
if [[ -f /etc/os-release ]]; then
  # shellcheck source=/dev/null
  source /etc/os-release
  if [[ -n "$PRETTY_NAME" ]]; then
    distro="$PRETTY_NAME"
  elif [[ -n "$NAME" ]]; then
    distro="$NAME"
  fi
else
  # if not os-release try issue
  if [[ -n $(sed 's| \\.*$||' /etc/issue | head -n 1) ]]; then
    distro="$(sed 's| \\.*$||' /etc/issue | head -n 1)"
  else
    # fuck it
    distro=
  fi
fi

header() {
  [[ -z "$distro" ]] && echo -e "${BLD}Anything-sync-daemon v$VERS${NRM}" ||
    echo -e "${BLD}Anything-sync-daemon v$VERS${NRM}${BLD} on $distro${NRM}"
  echo
}

dep_check() {
  # Function is used to ensure all dependencies are installed
  debug "\n${BLU}Checking dependencies${NRM}"
  debug "checking rsync"
  command -v rsync >/dev/null 2>&1 || {
  echo "I require rsync but it's not installed. Aborting." >&2
  exit 1; }
  debug "checking awk"
  command -v awk >/dev/null 2>&1 || {
  echo "I require awk but it's not installed. Aborting." >&2; exit 1; }
  debug "checking pv"
  command -v pv >/dev/null 2>&1 || {
  echo "I require pv but it's not installed. Aborting." >&2; exit 1; }
  debug "checking tar"
  command -v tar >/dev/null 2>&1 || {
  echo "I require tar but it's not installed. Aborting." >&2; exit 1; }
  debug "checking zstd"
  command -v zstd >/dev/null 2>&1 || {
  echo "I require zstd but it's not installed. Aborting." >&2; exit 1; }
  if [[ $OLFS -eq 1 ]]; then
    [[ $OLFSVER -ge 22 ]] || {
    echo -e " ${BLD}Your kernel requires either the ${BLU}overlay${NRM}${BLD} or ${BLU}overlayfs${NRM}${BLD} module to use${NRM}"
    echo -e " ${BLD}to use asd's in overlay mode. Cannot find either in your kernel so compile it in and${NRM}"
    echo -e " ${BLD}try again or remove the option from ${BLU}$ASDCONF${NRM}${BLD}. ${RED}Aborting!${NRM}" >&2; exit 1;}
  fi
}

config_check() {
  debug "\n${GRN}Checking configs${NRM}"
  # nothing to do if these are empty
  if [[ -z "${WHATTOSYNC[0]}" ]]; then
    echo -e " ${BLD}Must define at least one directory in ${NRM}${BLU}$ASDCONF${NRM}"
    exit 1
  fi

  # make sure the user defined real dirs
  for DIR in "${WHATTOSYNC[@]}"; do
    debug "DIR: $DIR"
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    [[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
      BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
    debug "BACKUP: $BACKUP"
    debug "BACK_OLD: $BACK_OLD"
    if [[ ! -d "$DIR" ]]; then
      if [[ ! -d "$BACK_OLD" ]]; then
        echo -e "${BLD}Bad entry in your WHATTOSYNC array detected:${NRM}"
        echo -e " ${BLD}${RED}$DIR${NRM}"
        echo -e "${BLD}Edit ${BLU}$ASDCONF${NRM}${BLD} correcting the mistake and try again.${NRM}"
        exit 1
      fi
    else
      # sanity check for hardlinks
      if [[ ! -d "$BACK_OLD" && $ENABLE_HARDLINK_SAFETY_CHECK -ne 0 && -n $(find "$DIR" -type f -links +1) ]]; then
        echo -e "$DIR:\n${RED} Presence of hardlinks found, asd might break them:${NRM}"
        exit 1
      else
        debug "No hardlinks found"
      fi
    fi
  done
  debug "Configs seem to be fine"
}

root_check() {
  # we call this to ensure that only the root user is calling the
  # function why care? both the sync and unsync functions require
  # root access to $DAEMON_FILE Running as unprivileged user will
  # fuck up the sync process resulting in unhappy users

  debug "\n${RED}Checking root permissions${NRM}"
  if [[ $EUID -ne 0 ]]; then
    echo -e " ${BLD}This function must be called as root!${NRM}" 1>&2
    exit 1
  fi
  debug "Have root permissions"
}

ungraceful_state_check() {
  # if the machine was ungracefully shutdown then the backup will be
  # on the filesystem and the link to tmpfs will be on the filesystem
  # but the contents will be empty we need to simply remove the link
  # and rotate the backup into place
  debug "\n${BLU}checking ungraceful state${NRM}"
  local DIR USER BACKUP TMP
  for DIR in "${WHATTOSYNC[@]}"; do
    # did user define a real dir
    # this is the hdd bound backup in case of power failure
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    [[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
      BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
    if [[ -d "$BACKUP" ]]; then
      USER=$(stat -c %U "$BACKUP")
    else
      USER=$(stat -c %U "$DIR")
    fi
    TMP="$VOLATILE/$ASDNAME-$USER$DIR"
    UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
    WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
    debug "DIR: $DIR\nBACKUP: $BACKUP\nBACK_OLD: $BACK_OLD\nUSER: $USER\nTMP: $TMP"

    if [[ -e "$TMP"/.flagged || ! -d "$BACKUP" ]]; then
      debug "No ungraceful state detected"
      # all is well so continue
      continue
    else
      echo "Ungraceful state detected for $DIR so fixing"
      NOW=$(date +%Y%m%d_%H%M%S)

      debug "unmounting $DIR"
      mountpoint -q "$DIR" && umount -R -l "$DIR"
      debug "unmounting $BACKUP"
      mountpoint -q "$BACKUP" && umount -R -l "$BACKUP" && rm -rf "$BACKUP" && debug "removed $BACKUP dir"

      mountpoint -q "$TMP" && umount "$TMP" && rm -rf "$TMP" "$UPPER" "$WORK" && debug "unmounted overlay dirs"

      if [[ -d "$BACK_OLD" ]]; then
        if [[ $CRRE -eq 1 ]]; then
          debug "copying $BACK_OLD to $BACKUP-$CRASH_RECOVERY_SUFFIX-$NOW.tar.zstd"
          tar cf - -C "${BACK_OLD%/*}" "${BACK_OLD##*/}" | pv -s "$(du -sb "$BACK_OLD" | awk '{print $1}')" | zstd > "$BACKUP-$CRASH_RECOVERY_SUFFIX-$NOW.tar.zstd"
        fi
        rm -rf "$BACK_OLD" && debug "deleting the $BACK_OLD directory"
      fi
    fi
  done
}

cleanup() {
  debug "\n${BLU}Cleaning up crashrecoveries${NRM}"
  local DIR USER GROUP BACKUP TMP
  for DIR in "${WHATTOSYNC[@]}"; do
    # this is the hdd bound backup in case of power failure
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    USER=$(stat -c %U "$DIR")
    GROUP=$(id -g "$USER")
    TMP="$VOLATILE/$ASDNAME-$USER$DIR"
    UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
    WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
    debug "DIR: $DIR\nUSER: $USER\nGROUP: $GROUP\nTMP: $TMP\nUPPER: $UPPER\nWORK: $WORK"

    mapfile -t CRASHArr < <(find "${BACKUP%/*}" -maxdepth 1 -name "${BACKUP##*/}-$CRASH_RECOVERY_SUFFIX-*" 2>/dev/null|sort -r)

    if [[ ${#CRASHArr[@]} -gt 0 ]]; then
      echo -e "${BLD}Deleting ${#CRASHArr[@]} crashrecovery dir(s) for sync target ${BLU}$DIR${NRM}"
      for backup in "${CRASHArr[@]}"; do
        echo -e "${BLD}${RED} $backup${NRM}"
        debug "removing $backup"
        rm -rf "$backup"
      done
      unset CRASHArr
    else
      echo -e "${BLD}Found no crashrecovery dirs for: ${BLU}$DIR${NRM}${BLD}${NRM}"
    fi
    echo
  done
}

enforce() {
  debug "\n${BLU}Enforcing number of backups${NRM}"
  local DIR BACKUP
  for DIR in "${WHATTOSYNC[@]}"; do
    # this is the hdd bound backup in case of power failure
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    debug "DIR: $DIR\nBACKUP: $BACKUP"
    mapfile -t CRASHArr < <(find "${BACKUP%/*}" -maxdepth 1 -name "${BACKUP##*/}-$CRASH_RECOVERY_SUFFIX-*" 2>/dev/null|sort -r)

    if [[ ${#CRASHArr[@]} -gt $BACKUP_LIMIT ]]; then
      debug "The backups are greater than $BACKUP_LIMIT"
      for remove in "${CRASHArr[@]:$BACKUP_LIMIT}"; do
        debug "removing $remove"
        rm -rf "$remove"
      done
    else
      debug "The backups are less than $BACKUP_LIMIT, nothing to do"
    fi
    unset CRASHArr
  done
}

do_sync() {
  debug "\n${GRN}Syncing files${NRM}"

  # make a snapshot of /etc/asd.conf and redefine its location to tmpfs while
  # asd is running to keep any edits made to the live /etc/asd.conf from
  # potentially orphaning the tmpfs copies thus preserving the data
  [[ ! -f "${DAEMON_FILE}.conf" ]] && cp "$ASDCONF" "${DAEMON_FILE}.conf" && debug "copied $ASDCONF to ${DAEMON_FILE}.conf"

  # sync to tmpfs and back again
  local DIR USER GROUP BACKUP TMP
  for DIR in "${WHATTOSYNC[@]}"; do
    # this is the hdd bound backup in case of power failure
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    [[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
      BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
    USER=$(stat -c %U "$DIR")
    GROUP=$(id -g "$USER")
    TMP="$VOLATILE/$ASDNAME-$USER$DIR"
    UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
    WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
    debug "\nDIR: $DIR\nUSER: $USER\nGROUP: $GROUP\nTMP: $TMP\nUPPER: $UPPER\nWORK: $WORK\n"

    # make tmpfs container
    if [[ -d "$DIR" ]]; then
      # retain permissions on sync target
      PREFIXP=$(stat -c %a "$DIR")
      [[ -r "$TMP" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$TMP"
      [[ -r "$BACKUP" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$BACKUP"

      if [[ $OLFS -eq 1 ]]; then
        debug "ensuring overlay directories"
        if [[ $OLFSVER -eq 23 ]]; then
          [[ -r "$UPPER" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$UPPER"
          [[ -r "$WORK" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$WORK"
        elif [[ $OLFSVER -eq 22 ]]; then
          [[ -r "$UPPER" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$UPPER"
        fi
      fi

      # sync the tmpfs targets to the disc
      if [[ -e "$TMP"/.flagged ]]; then
        debug "Syncing $TMP and $BACKUP"
        # don't do inplace sync
        set -x
        rsync -aX --delete-after --exclude .flagged "$TMP/" "$BACKUP/" --info=progress2
        set +x
      else
        # backup target and link to tmpfs container
        debug "Bind mounting $DIR -> $BACKUP"
        mount --rbind --make-private -o noatime "$DIR" "$BACKUP"
        if [[ $CRRE -eq 1 ]];then
          debug "Creating new linked backup directory $BACK_OLD"
          tempfile=$(mktemp)

          set -x
          # this copies all the files
          find "$BACKUP" -type l -printf '%P\n' > "$tempfile"
          rm -rf "$BACK_OLD" && rsync -aX --no-links --link-dest="$DIR" "$DIR/" "$BACK_OLD/" --info=progress2

          # this is used to handle the symlinks
          rsync -aXl --files-from="$tempfile" "$DIR/" "$BACK_OLD/" --info=progress2

          set +x
          rm "$tempfile"
        fi

        # initial sync
        if [[ $OLFS -eq 1 ]]; then
          debug "Mounting overlay directory"
          if [[ $OLFSVER -eq 23 ]]; then
            mount -t overlay overlaid -olowerdir="$BACKUP",upperdir="$UPPER",workdir="$WORK" "$TMP"
          elif [[ $OLFSVER -eq 22 ]]; then
            mount -t overlayfs overlaid -olowerdir="$BACKUP",upperdir="$UPPER" "$TMP"
          fi
        else
          debug "Doing initial sync with $BACKUP and $TMP"
          set -x
          rsync -aXl --append "$BACKUP/" "$TMP/" --info=progress2
          set +x
        fi
        debug "bind mounting $TMP -> $DIR"
        mount --rbind --make-private -o noatime "$TMP" "$DIR"

        touch "$TMP"/.flagged
      fi
    fi
  done
  echo -e "${BLD}Sync successful${NRM}"

  debug "creating $DAEMON_FILE"
  touch "$DAEMON_FILE"
}

do_unsync() {
  debug "\n${RED}Unsyncing files${NRM}"

  local DIR USER BACKUP TMP
  for DIR in "${WHATTOSYNC[@]}"; do
    # this is the hdd bound backup in case of power failure
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    [[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
      BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
    USER=$(stat -c %U "$DIR")
    GROUP=$(id -g "$USER")
    TMP="$VOLATILE/$ASDNAME-$USER$DIR"
    UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
    WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
    debug "DIR: $DIR\nUSER: $USER\nGROUP: $GROUP\nTMP: $TMP\nUPPER: $UPPER\nWORK: $WORK\n"

    # remove link and move data from tmpfs to disk
    if mountpoint -q "$DIR"; then
      # this assumes that the backup is always
      # updated so be sure to invoke a sync before an unsync
      debug "unmounting $DIR"
      umount -R -f -l "$DIR"
      debug "unmounting $BACKUP"
      umount -R -f -l "$BACKUP" && rm -rf "$BACKUP" && debug "removing $BACKUP"

      if [[ $OLFS -eq 1 ]] && mountpoint -q "$TMP"; then
        umount -l "$TMP" && debug "unmount $TMP"
        rm -rf "$TMP" "$UPPER" "$WORK" && debug "removing overlayfs folders"
      else
        [[ -d "$TMP" ]] && rm -rf "$TMP" && debug "removing $TMP"
      fi
      [[ $CRRE -eq 1 ]] && rm -rf "$BACK_OLD" && debug "removing $BACK_OLD"
    fi
  done

  # delete daemon file in the end, so that unsync can be run again
  # incase of some failure midway
  # since unsync also requires sync before, if any of the DIRS are unsynced during last run,
  # they will be synced again before unsyncing not leading to any breakage

  debug "Removing $DAEMON_FILE and ${DAEMON_FILE}.conf"
  rm -f "$DAEMON_FILE" "${DAEMON_FILE}.conf"

  echo -e "${BLD}Unsync successful${NRM}"
}

parse() {
  if [[ -f /usr/lib/systemd/system/asd.service ]]; then
    # running systemd
    asd_state=$(systemctl show -p ActiveState --value asd)
    resync_state=$(systemctl show -p ActiveState --value asd-resync.timer)
    [[ "$asd_state" = "active" ]] && asd_color="${GRN}" || asd_color="${RED}"
    [[ "$resync_state" = "active" ]] && resync_color="${GRN}" || resync_color="${RED}"
    echo -e " ${BLD}Systemd service is currently ${asd_color}$asd_state${NRM}${BLD}.${NRM}"
    echo -e " ${BLD}Systemd resync service is currently ${resync_color}$resync_state${NRM}${BLD}.${NRM}"
  else
    # using other init system + cron job for resync
    [[ -x /etc/cron.hourly/asd-update ]] && resync_state="present" || resync_state="not present"
    [[ "$resync_state" = "present" ]] && resync_color="${GRN}" || resync_color="${RED}"
    echo -e " ${BLD}Daemon pid file is $([[ -f $DAEMON_FILE ]] &&
      echo -e "${GRN}"present"${NRM}""${BLD}" || echo -e "${RED}"not present"${NRM}""${BLD}").${NRM}"
    echo -e " ${BLD}Resync cronjob is ${resync_color}${resync_state}${NRM}${BLD}.${NRM}"
  fi
  [[ $OLFS -eq 1 ]] &&
    echo -e "${BLD} Overlayfs v$OLFSVER is currently ${GRN}active${NRM}${BLD}.${NRM}" ||
    echo -e "${BLD} Overlayfs technology is currently ${RED}inactive${NRM}${BLD}.${NRM}"
  echo
  echo -e "${BLD}Asd will manage the following per ${BLU}${ASDCONF}${NRM}${BLD} settings:${NRM}"
  echo
  local DIR USER GROUP BACKUP TMP
  for DIR in "${WHATTOSYNC[@]}"; do
    # this is the hdd bound backup in case of power failure
    [[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
      BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
    USER=$(stat -c %U "$DIR")
    GROUP=$(id -g "$USER")
    TMP="$VOLATILE/$ASDNAME-$USER$DIR"
    UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
    WORK="$VOLATILE/.$ASDNAME-$USER$DIR"

    # sync target dir size
    if [[ -d "$DIR" ]]; then
      echo -en " ${BLD}owner/group id:"
      echo -e "$(tput cr)""$(tput cuf 20)" "$USER/$GROUP${NRM}"
      echo -en " ${BLD}target to manage:"
      echo -e "$(tput cr)""$(tput cuf 20)" "${GRN}$DIR${NRM}"
      echo -en " ${BLD}sync target:"
      echo -e "$(tput cr)""$(tput cuf 20)" "${BLU}$BACKUP${NRM}"
      echo -en " ${BLD}tmpfs target:"
      echo -e "$(tput cr)""$(tput cuf 20)" "${RED}$TMP${NRM}"
      echo -en " ${BLD}dir size:"
      psize=$(du -Dh --max-depth=0 "$DIR" 2>/dev/null | awk '{ print $1 }')
      echo -e "$(tput cr)$(tput cuf 20) $psize${NRM}"
      if [[ $OLFS -eq 1 ]]; then
        rwsize=$(du -Dh --max-depth=0 "$UPPER" 2>/dev/null | awk '{ print $1 }')
        echo -en " ${BLD}overlayfs size:"
        echo -e "$(tput cr)$(tput cuf 20) $rwsize${NRM}"
      fi
      echo -en " ${BLD}recovery dirs:"
      mapfile -t CRASHArr < <(find "${BACKUP%/*}" -maxdepth 1 -name "${BACKUP##*/}-$CRASH_RECOVERY_SUFFIX-*" 2>/dev/null|sort -r)
      if [[ "${#CRASHArr[@]}" -eq 0 ]]; then
        echo -e "$(tput cr)$(tput cuf 20) none${NRM}"
      else
        echo -e "$(tput cr)$(tput cuf 20) ${RED}${#CRASHArr[@]}${NRM}${BLD} <- delete with the c option${NRM}"
        for backup in "${CRASHArr[@]}"; do
          psize=$(du -Dh --max-depth=0 "$backup" 2>/dev/null | awk '{ print $1 }')
          echo -en " ${BLD} dir path/size:"
          echo -e "$(tput cr)$(tput cuf 20) ${BLU}$backup ${NRM}${BLD}($psize)${NRM}" 
        done
      fi
      unset CRASHArr
      echo
    fi
  done
}

case "$1" in
  p|P|Parse|parse|Preview|preview|debug)
    header
    dep_check
    config_check
    parse
    ;;
  c|C|clean|Clean)
    header
    dep_check
    config_check
    cleanup
    ;;
  sync)
    if [[ ! -f "$DAEMON_FILE" ]]; then
      root_check
      dep_check
      config_check
      ungraceful_state_check
      do_sync
      enforce
    else
      echo -e "${GRN}ASD is already running${NRM}"
    fi
    ;;
  resync)
    if [[ -f "$DAEMON_FILE" ]]; then
      root_check
      ungraceful_state_check
      do_sync
    else
      echo -e "${RED}ASD is not running${NRM}"
    fi
    ;;
  unsync)
    # make sure the daemon ran to setup the links
    if [[ -f "$DAEMON_FILE" ]]; then
      root_check
      ungraceful_state_check
      do_sync
      do_unsync
    else
      echo -e "${RED}ASD is not running${NRM}"
    fi
    ;;
  *)
    echo -e "${BLD}Anything-sync-daemon v$VERS${NRM}"
    echo
    echo -e " ${BLD}$0 ${NRM}${GRN}[option]${NRM}"
    echo -e " ${BLD} ${NRM}${GRN}preview${NRM}${BLD}  Parse config file (${NRM}${BLU}${ASDCONF}${NRM}${BLD}) to see what will be managed.${NRM}"
    echo -e " ${BLD} ${NRM}${GRN}clean${NRM}${BLD}		Clean (delete without prompting) ALL crashrecovery dirs.${NRM}"
    echo -e " ${BLD} ${NRM}${GRN}resync${NRM}${BLD} Synchronize the tmpfs and media bound copy. Must be run as root user.${NRM}"
    echo -e " ${BLD} ${NRM}${RED}sync${NRM}${BLD}   Force a manual sync. Must be run as root user and NOT recommended.${NRM}"
    echo -e " ${BLD} ${NRM}${RED}unsync${NRM}${BLD} Force a manual unsync. Must be run as root user and NOT recommended.${NRM}"
    echo
    echo -e " ${BLD}It is ${RED}HIGHLY DISCOURAGED${NRM}${BLD} to directly call $0 to sync or to unsync.${NRM}"
    if [[ -f /usr/lib/systemd/system/asd.service ]]; then
      echo -e " ${BLD}Instead, use systemd to start/stop anything-sync-daemon.${NRM}"
      echo
      echo -e " ${BLD}systemctl ${NRM}${GRN}[option]${NRM}${BLD} asd asd-resync${NRM}"
      echo -e " ${BLD} ${NRM}${GRN}start${NRM}${BLD}    Turn on daemon; make symlinks and actively manage targets in tmpfs.${NRM}"
      echo -e " ${BLD} ${NRM}${GRN}stop${NRM}${BLD}   Turn off daemon; remove symlinks and rotate tmpfs data back to disc.${NRM}"
      echo -e " ${BLD} ${NRM}${GRN}enable${NRM}${BLD} Autostart daemon when system comes up.${NRM}"
      echo -e " ${BLD} ${NRM}${GRN}disable${NRM}${BLD}  Remove daemon from the list of autostart daemons.${NRM}"
    elif [[ -f /etc/init.d/asd ]]; then
      echo -e " ${BLD}Instead, use the init system to start/stop anything-sync-daemon.${NRM}"
      echo
      echo -e " ${BLD}sudo service asd ${NRM}${GRN}[option]${NRM}${BLD} or /etc/init.d/asd ${NRM}${GRN}[option]${NRM}"
      echo -e " ${BLD} ${NRM}${GRN}start${NRM}${BLD}  Turn on daemon; make symlinks and actively manage targets in tmpfs.${NRM}"
      echo -e " ${BLD} ${NRM}${GRN}stop${NRM}${BLD} Turn off daemon; remove symlinks and rotate tmpfs data back to disc.${NRM}"
    fi
    ;;
esac
exit 0

# vim:set ts=2 sw=2 et:
