#!/bin/bash # -*- coding: utf-8 -*- # # Copyright by Scott Severance # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Auto-backup script # # Ensures that too much data doesn't get lost if $program # crashes during a save. prog="$(basename $0)" timeout="300" # 5 minutes timeoutStep="5" #seconds pid= watchFiles= # Color codes for an easier-to-read output FILE_COLOR="\033[4;34m" ERROR_COLOR="\033[0;31m" MSG_COLOR="\033[0;32m" NO_COLOR="\033[0m" #Transparent - don't change TITLEBAR_START="\033]0;" TITLEBAR_END="\007" E_USAGE=65 E_NORMAL=0 E_BAD_ARGUMENT=66 function usage { echo -e "${MSG_COLOR}Usage:" echo -e "$prog program_pid filename [filename [...]]${NO_COLOR}" echo echo "$prog defends against files being ruined by misbehaving programs. To" echo "accomplish this, $prog makes a compressed copy of each file listed on" echo "the command line every $timeout seconds until the program with the specified pid" echo "terminates." echo echo "$prog keeps its backups in ~/.file-insurance/\$programName/\$filename." [ -z "$1" ] && exit $E_USAGE } function programAlive { local pid="$1" ps --pid "$pid" > /dev/null 2>&1 return $? } function getNewestFile { ls -1t "$1" | while read "crud"; do if [ -n "$crud" ]; then echo "$crud" break fi done } function makeBackup { local programName="$HOME/.file-insurance/$1" local filename="$2" echo -ne "$ERROR_COLOR" if [ ! -f "$filename" ]; then echo -e "Error: the file $filename doesn't exist. Aborting...${NO_COLOR}" exit "$E_BAD_ARGUMENT" fi [ -d "$programName" ] || mkdir -p "$programName" local dir="$programName/$filename" [ -d "$dir" ] || mkdir -p "$dir" local currentTime="$(date +'%Y-%m-%d_%H-%M-%S')" local backupFile="$dir/file-insurance.$currentTime" local lastBackupFile= if [ "$(ls -1 "$dir" | wc | awk '{print $1}')" != "0" ]; then globalTmp="$(getNewestFile "$dir")" lastBackupFile="${dir}/${globalTmp}" fi cp "$filename" "$backupFile" bzip2 "$backupFile" backupFile="${backupFile}.bz2" if [ -n "$lastBackupFile" ]; then # no sense in making multiple copies of identical files if $(diff "$lastBackupFile" "$backupFile" >/dev/null); then # diff exits 0 if there are no differences rm -f "$backupFile" fi fi echo -ne "$NO_COLOR" } function sleepCounter { # args: sleep time, time step, callback function, [callback function args...] local remaining="$1"; shift local step="$1"; shift local callback="$1"; shift while [[ $remaining > 0 ]]; do $callback "$@" (( remaining -= step )) sleep $step done } function exitIfProgramDead { if ! $(programAlive "$pid"); then echo -e "${MSG_COLOR}$pidName ($pid) is no longer running. Exiting.${NO_COLOR}" echo -e "\n\nConsider running file-insurance-cleanup to purge unnecessary files." exit "$E_NORMAL" fi } function initThis { if [ "z$1" = "z" -o "z$2" = "z" ]; then usage else pid="$1"; shift watchFiles=("$@") fi echo -ne "$ERROR_COLOR" if ! $(programAlive "$pid"); then echo "Error: invalid pid" echo usage "false" exit "$E_BAD_ARGUMENT" fi if [ -z "${watchFiles[0]}" ]; then echo "Error: no file to watch" echo usage false exit "$E_BAD_ARGUMENT" fi echo -ne "$NO_COLOR" } function main { [ "$1" = "--help" ] && usage initThis "$@" shift local pidName="$(ps --pid "$pid" --no-headers | awk '{print $4}' | sed 's/\//_/g')" echo -ne "${MSG_COLOR}Monitoring $pidName ($pid) and the file(s)" for i in "$@"; do echo -en " \"${FILE_COLOR}${i}${MSG_COLOR}\"" done echo -e "...${NO_COLOR}" echo -ne "${TITLEBAR_START}file-insurance monitoring ${pidName} (${pid})${TITLEBAR_END}" while : ; do for i in "${watchFiles[@]}"; do makeBackup "$pidName" "$i" done sleepCounter "$timeout" "$timeoutStep" "exitIfProgramDead" done } main "$@"