Updated version attached below: Version 0.22 I think the routine is pretty finished now and it has more than enough functionality for my own uses, so I probably won't be making many changes in the near future unless I hear about some serious bugs or compelling feature requests:
Here is the changelog with code following at the end: Changelog: Version 0.3 - Added ability to specify many parameters also from the command line. In particular, the rsync daemon parameters allow you to run this as a separate instance (different port, address, pid file) directly from the calling routine. This eases setting up rsyncd over ssh without having to make any changes to the host files - Added option to kill & restart existing shadow copies and rsync daemons provided they are older than N minutes - Added ability to specify which drive letters should be shadowed - Added 'usage' message - A lot more code cleanup and error checking Version 0.21: - Added USESHADOWS option to determine whether to fall back to rsyncd without shadows if shadow setup fails (or optionally not to even use shadows in the first place - Eliminated need to explicitly wait for rsyncd to setup (check rsync pid file instead) - Fixed rsyncd_kill function so that it only kills daemons (and the right one too) - Added ability to set options for rsync - Cleaned up code - Changed name of routines to shadowmountrsync and shadowexeccmd, respectively Version 0.2: First posted version Jeffrey J. Kosowsky wrote at about 04:59:17 -0500 on Sunday, December 14, 2008: > Updated version attached below: > Jeffrey J. Kosowsky wrote at about 15:52:56 -0500 on Friday, December 12, > 2008: > > The enclosed script (and a 1-line cmd.exe helper) cleanly and > > automatically sets up shadow copies, mounts them, and launches the > > rsync daemon without requiring any special configuration or changes to > > your existing (non-shadow) rsyncd.conf script. It also cleanly unwinds > > all the above when you are done rsyncing your files. > > > > As a result, you should now be able to back up any (and all) files > > that are locked under Windows such as registry files (e.g. ntuser.dat) > > and databases (e.g. Outlook). > > > > The script can easily be called directly from $Conf{DumpPreUserCmd} > > and $Conf{DumpPostUserCmd} -- see the script for examples in > > the comments. > > > > The script itself (which uses some hairy recursion to get around > > limitations in cygwin ssh, vshadow, and dosdev) is heavily documented. > > > > Enjoy and if you use it, please send me feedback, bug reports, > > enhancements, etc... > > > > Jeff > > ------------------------------------------------------------------------------- #!/bin/bash # Shadowmountrsync # Copyright Jeffrey J. Kosowsky December 2008 # Version 0.3 # Description: automagically sets up (and takes down) shadow copies, # shadow mounts, and the rsyncd daemon to allow you to seamlessly # rsync to shadow copies of all the active modules in your current # rsyncd.conf file WITHOUT any configuration changes or added settings # to your existing setup non-shadow copy rsyncd setup. In particular, # the program determines which shadows to create based upon the # modules in your existing /etc/rsyncd.conf combined with what mounts # actually exist. # Usage: (see below for more detailed usage) # shadowmountrsync # Sets up shadow copies, mounts shadows, and launches rsync # shadowmountrsync -d # Terminates rsync, kills shadow copies, unmounts, and cleans up # # If using as pre/post commands in BackupPC, try something like: #$Conf{DumpPreUserCmd} = # '$sshPath -q -x -l kosowsky $host /usr/local/bin/shadowmountrsync'; # # $Conf{DumpPostUserCmd} = # '$sshPath -q -x -l kosowsky $host /usr/local/bin/vshadow-scripts/shadowmountrsync -d'; # # NOTE: this doesn't make sense for the Restore commands since clearly # shadow copies CANNOT be restored to since by definition they are # read-only # # WARNING: This is beta software which has only been tested to work so # far on my own XP sp2 setup in a limited number of situation. In # particular, it may not work on other Windows version since the # nature of shadow copy (and vshadow and dosdev) in particular has # changed radically for Windows versions both prior to and post # WinXP. Given the nature of shadow copy, it *should* (theoretically) # not be capable of doing anything destructive to the source disk, so # it's likely that the worse that would happen is that your backups # would fail (or potentially be incomplete/corrupted). Feedback, bug # reports, and enhancements are always welcome!!! # # NOTE: the companion 1-liner 'shadowexec.cmd' file MUST be in the # same directory Also, both files need to be in the Administrators # group and with group permissions +rx # # REQUIRES: # vshadow.exe # Available from Microsoft downloads (make sure you get the # right version for your architecture!) # http://www.microsoft.com/downloads/details.aspx?FamilyID=0B4F56E4-0CCC-4626-826A-ED2C4C95C871&displaylang=en # dosdev.exe # Again available from Microsoft downloads (as part of their # reporting tools). Also, be careful of the version since # different verssions seem to have different command line # interfaces. I obtained my copy from: # http://www.ltr-data.se/files/dosdev.zip # # NOTE: The program recurses through this routine 3 times because of # the limitations first of ssh and then of vshadow. An additional # '0th' pass is used to terminate the shadow copy & clean up. # In particular, cygwin ssh logins lack the authority to create shadow # copies or set up dos devices since it is not a true authenticated # user login. On the other hand, vshadow is quite crippled in XP since # it cannot create shadow copies nor can they be easily # mounted. Dosdev is necessary because cygwin does not (yet) allow # direct mounting of shadow copy devices since they lie in the kernel # namespace. # # Since the program recurses both under the login user and under the # SYSTEM user (for ssh), you need to be attentive to permissions. The # program tries to detect and log such errors, but in particular: # vshadow, dosdev, this bash script (and its cmd.exe companion), the # log & lock file, and the shadowdir should all be readable (and where # appropriate writable & executable) by the Administrators group. Note also that # the drive letter string ALLDRIVES can be restricted if you are # likely to be mounting/unmounting temporary devices (like cds, dvds) # requiring a fixed drive letter assignment during the rsync time. You # do not need to restrict it for fixed drives (e.g. C:) since they are # automatically avoided if present at the time of shadow creation. # # The recursion operates as follows: # First, since the ssh login lacks the authority to create shadow # copies or to set up dos devices, the program schedules an 'at' job # (for the next minute) to recurse again through this routine. (If not # launched from 'ssh' then for parallelism, the recursion is directly # launched with cmd.exe). # # Then, 'vshadow' calls the routine a third time once the shadow copy # has been set up to commplete the post-processing, including setting # up device letters and mounting them and then starting the rsyncd # daemon on an appropriately modified version of the original # rsyncd.conf. This shadow -exec script needs a a 1-line helper bash # cmd script since the -exec script can't take arguments and must be # either a .cmd or .exe file. The shadow exec script (3rd pass) also # works to keep the shadow copy alive (mimicking persistence) by # launching the rsync daemon with the no-detach option. # # Finally, when the transfer is completed and rsyncd is killed, the # whole recursion unwinds and everything gets cleaned up # appropriately. # Note that pass #2 which sets up the shadow copies (which is also # visible as the 'at' job if under ssh) and pass #3 (which is the # script that mounts the shadow copies and launches rsync) remain # alive until rsyncd is terminated. Pass #1 which kicks off the # recursion hangs around until either a setup error is detected or # until the setup is completed with the launch of the rsync daemon, at # which time it returns and signals the result to the calling # process. In particular, this initial pass returns '0' on success and a # positive error code [1-8] on error. # # Indeed, the easiest/cleanest/best way to unwind the recursions and # cause everything to reset gracefully is to kill the final 'rsync' # process for which everything else ends up waiting. The '-d' option # first tries to clean up gracefully like this but if it fails then it # uses brute force to kill rsyncd, delete mounts, shutdown shadow # copies, and remove lock files. # # Given the complexity of the multiple recursions, the program has # been instrumented with plentiful (but optional) logging turned on by # setting the $LOG variable to the log file you want to use. Indeed, # the logging is fun and instructive to watch ;) # # By setting $STRICT you can ensure that shadow creation will abort if # an rsync process or shadow copy is already running. Otherwise, it # will kill existing shadows and rsync processes. It's probably a good # idea to keep it set unless you are sure that you won't be colliding # with another shadow copy process. The downside is that if something # is stuck, then the routine will abort (and optionally log the # result) rather than forcing a clean up. # # Exit/Return codes: # 0 = success # 1 = wrong input arguments # 2 = lock file exists # 3 = rsync daemon already running # 4 = active shadow copies already exist # 5 = shadow routines (e.g, dosdev, vshadow, shadowexec.cmd) not accessible # 6 = shadow permissions error # 7 = shadow copies created not equal to requested # 8 = not enough free drive letters # 9 = timeout error waiting for shadows and mounts to set up # 10 = rsync daemon failed to start properly # 11 = unknown/unspecified error ############################################################################## ##### Define variables USESHADOWS=2 #0=no shadows, use straight rsync only; #1=use shadows with rsync, error if shadows not available #2=try to use shadows first, if not available use straight rsync RSYNCDLOCK= #Should be set in rsyncd.conf file as "pid file" (override here) PATH=/bin:/usr/bin:/usr/local/bin:/c/WINDOWS/System32 RSYNC=rsync DOSDEV=dosdev VSHADOW=vshadow SHADOWDIR=/shadow #Directory where shadow mounts are set up #MUST not be any dir where other things are mounted (e.g., / or /cygdrive) #Note this counts as part of file name length so don't make path too long RSYNCDCONF=/etc/rsyncd.conf # Your original (non-shadow) version of rsyncd.conf RSYNCDCONF_SHADOW=$SHADOWDIR/rsyncd.shadow.conf #Location for modified version #of rsyncd.conf (auto-generated) SHADOWEXECCMD=shadowexec.cmd #Name of 1-liner helper script #Must be in same directory as this script STRICT=1000 #Set '1' if you want strict checking of error conditions #before starting Specifically, won't start if 'lock' #exists or 'rsync' already running or if shadows already #exist (if $USESHADOWS > 0) #Set '0' if you want to ignore (and kill) existing shadow #and/or rsync processes #Set to 'N>1' if you want to ignore any existing rsync or #shadow process provided that they are at least N minutes old SHADOWLOCK=/var/run/shadow.pid #Name of shadow lock file LOG=/var/log/shadow.log #Set if you want logging (advisable at least to start) TIMEOUT=300 #Time in seconds 1st pass waits for setting up before timing out SHORTTIMEOUT=10 #Time in seconds to wait for cleanup to finish & rsync to set up #The following allow additional (optional) params to be passed to rsync daemon RSYNCDADDRESS= RSYNCDPORT= RSYNCDBWLIMIT= RSYNCDOTHER= ALLDRIVES=$(echo {Z..A}) #Reverse alphabetical (all-caps) #Constrain to subset if you want to 'reserve' some positions SHADOWDRIVES=$(echo {A..Z}) #List of drives to shadow (all-caps) #Constrain if you *don't* want to shadow shares on certain drives ############################################################################## ##### Define some helper functions #Parse options and determine pass number function parse_opts { local OPTS=`getopt -o 0123da:b:c:hl:p:s:u: --long delete,address:,bwlimit:,conf:,help,letters:,port:,strict:,useshadows: -n '${0##*/}' -- "$@"` eval set -- "$OPTS" unset PASSNUM while true; do case "$1" in #Set PASSNUM based on *first* use -0|-d|--delete) PASSNUM=${PASSNUM:-0};; -1 ) PASSNUM=${PASSNUM:-1};; -2 ) PASSNUM=${PASSNUM:-2};; -3 ) PASSNUM=${PASSNUM:-3};; -a|--address) RSYNCDADDRESS=$2; shift;; -b|--bwlimit) RSYNCDBWLIMIT=$2; shift;; -c|--conf) RSYNCDCONF=$2; shift;; -h|--help) usage; exit;; -l|--letters) SHADOWDRIVES="$(echo $2 | tr 'a-z' 'A-Z')"; shift;; -p|--port) RSYNCDPORT=$2; shift;; -s|--strict) STRICT=$2; shift;; -u|--useshadows) USESHADOWS=$2; shift;; --) shift ; break ;; *) echo "Internal error!" ; exit 1 ;; esac shift done PASSNUM=${PASSNUM:-1} #Default to pass 1 if not set PASS="[Pass #$PASSNUM]" if [ $PPID -ne 1 -a $PASSNUM -gt 1 ] ; then [ -n "$LOG" ] && echo -e "Error: passes 2 & 3 cannot be called directly...\n" >> $LOG exit 1 fi } #Usage message function usage { cat <<EOF Usage: (see below for more detailed usage) shadowmountrsync [options] Sets up shadow copies, mounts shadows, and launches rsync shadowmount [-0|-d|--delete] [options] Terminates rsync, kills shadow copies, unmounts, and cleans up NOTE: all of the above options can be set to defaults in the script itself. The options are there to allow you to override the defaults. Also, only the [-u|--useshadows] makes sense for the terminate [-d] usage. [-h|--help] [-a|--address] [address] Override rsyncd.conf file to use given IP address [-b|--bwlimit] [kbps] Override rsyncd.conf file to limit I/O bandwidth [-l|--letters] [ABC..] Only create shadows for given letters (no spaces) [-p|--port] [port] Override rsyncd.conf file to use given port [-r|--rsyncconf] [file] Rsyncd.conf file location [-s|--strict] [N] where: N=0 Proceed and start new process even if there are existing shadow copies, rsyncd daemons, and shadow lock files N=1 Exit with error if any of the above exist N>1 Proceed if all of the above haven't been modified in >N minutes [-u|--useshadows] [N] where: N=0 Bypass shadow copy code and just start regular rsync daemon N=1 Create shadow copy and mounts - abort on error N=2 Try to create shadow copies, but revert to regular rsync daemon if not successful In the -d case, setting N=0 means that it won't attempt to kill any existing shadow copies. Description: automagically sets up (and takes down) shadow copies, shadow mounts, and the rsyncd daemon to allow you to seamlessly rsync to shadow copies of all the active modules in your current rsyncd.conf file WITHOUT any configuration changes or added settings to your existing setup non-shadow copy rsyncd setup EOF exit } # Launches passed input via 'at' to get around $USERNAME=SYSTEM # problem under ssh login where the shell lacks permsisions to run # commmands like vshadow or dosdev function at_launch { local h m s wait command command=$@ set -- $(date +"%H %M %S") h=$((10#$1)) #Note explicitly use base 10 so that 08 and 09 not interpreted as bad octal m=$((10#$2 +1)) #Advance minutes by 1 s=$((10#$3)) wait=$((60 - $s)) [ $s -gt 55 ] && let "m += 1" "wait += 60" # Make sure >5 seconds left [ $m -ge 60 ] && let "m %= 60" "h += 1" #Overflow minutes let "h %= 24" at $h:$m $(cygpath -w $(which bash.exe)) -c \"$command\" > /dev/null return $wait } #Return 0 if $SHADOWLOCK, $RSYNCDLOCK, and all the shadow images are older #than $1 (where $1 is in minutes) function get_strict_age { AGE=$(( $(date +%s) - 60*$1 )) get_rsyncdlock [ -e "$RSYNCDLOCK" ] && \ [ $(( $(stat -c %Y $RSYNCDLOCK) - AGE )) -gt 0 ] && return 1 if [ $USESHADOWS -gt 0 ] ; then [ -e "$SHADOWLOCK" ] && \ [ $(( $(stat -c %Y $SHADOWLOCK) - AGE )) -gt 0 ] && return 1 ( IFS=$'\n' for date in $($VSHADOW -q | sed -ne "s/.*- Creation Time: \(.*M\) *$/\1/p"); do [ $(( $(date -d "$date" +%s) - AGE )) -gt 0 ] && return 1 done ) fi return 0 } # Populate SHADOWMOUNT with all drive letters (plus colon) that are present # in rsyncd.conf and are actually mounted and are part of SHADOWDRIVES list function get_shadowmounts { #Extract list of drive letter (followed by :) from rsyncd.conf, #converting to uppercase local RSYNCMOUNTS=$(sed -ne "s%^[ \t]*path[ \t]*=[ \t]*/\(cygdrive/\)\?\([A-Za-z]\)\([ \t]*$\|[ \t]*#\|/\).*%\u\2%p" $RSYNCDCONF | sort | uniq | sed -e "s|$|:|") SHADOWMOUNT=() for mount in $RSYNCMOUNTS ; do #Eliminate mounts that are not actually present [ -d $mount ] && [[ "$SHADOWDRIVES" =~ "${mount%:}" ]] && \ SHADOWMOUNT=("${shadowmou...@]}" "$mount") #array done } # Populate FREEDRIVES with $1 *free* drive letters starting from list ALLDRIVES function get_freedrives { FREEDRIVES=() for letter in $ALLDRIVES ; do mount | egrep -q "^$letter:" || \ FREEDRIVES=("${freedriv...@]}" "$letter") #array [ ${#freedriv...@]} -eq $1 ] && return done } # If not set manually, then get name of rsyncd lock file from rsyncd.conf function get_rsyncdlock { [ -z "$RSYNCDLOCK" ] && RSYNCDLOCK=$(sed -ne "s/[ ]*pid file[ ]*=[ ]*\(.*[^ ]\)[ ]*\(#.*\|$\)/\1/p" $RSYNCDCONF) } # Kill rsyncd process function kill_rsyncd { # Note we only want to kill rsync daemons that are bound to our rsync pid # file. In particular, we don't want to kill regular rsync processes # or even rsync daemons with an alternative rsync pid file that may be # associated with another conf file (and different port/address). get_rsyncdlock [ -e "$RSYNCDLOCK" ] || return #No running rsyncd with our pid file local PID=$(< "$RSYNCDLOCK") # Read in PID if [ -n $PID ]; then ps -e | grep -q "^[^0-9]*${PID}.*${RSYNC}$" && kill -TERM $PID && sleep 1 ps -e | grep -q "^[^0-9]*${PID}.*${RSYNC}$" && kill -KILL $PID && sleep 1 fi [ -e "$RSYNCDLOCK" ] && rm -f "$RSYNCDLOCK" #Shouldn't be necessary... } # Delete shadows, unmount shadow mounts, and remove drive letters function remove_shadows { local shadowmounts letter ( echo Y | $VSHADOW -da ) > /dev/null 2>&1 #Delete shadow copies and as a #positive side effect kills shadow process and spawned routines shadowmounts=$(mount | sed -ne "s|^\([A-Za-z]\): on $SHADOWDIR/[A-Za-z] type.*|\1|p") for letter in $shadowmounts ; do umount -s $SHADOWDIR/$letter 2> /dev/null #Unmount $DOSDEV "${letter}:" /D 2> /dev/nuul # Note dosdev doesn't work from ssh due to permissions and probably # not necessary after shadows killed, but good hygiene done } #If possible, gracefully unwind shadows by killing the shadowexec.cmd process #Return 0 if unwinds successfully function unwind_shadows { if [ "$USESHADOWS" -gt 0 -a -e $SHADOWLOCK ]; then SCRIPTPID=$(< $SHADOWLOCK) if [[ -n "$SCRIPTPID" && $SCRIPTPID -gt 0 ]]; then [ -n "$LOG" ] && echo "--Attempting to clean up gracefully by unwinding shadow recursion...$PASS" >> $LOG ps -e | grep -q "^[^0-9]*$SCRIPTPID.*/usr/bin/bash$" \ && kill -TERM $SCRIPTPID && sleep 1 ps -e | grep -q "^[^0-9]*$SCRIPTPID.*/usr/bin/bash$" \ && kill -KILL $SCRIPTPID && sleep 1 for ((WAIT=1; WAIT <=SHORTTIMEOUT; WAIT++)) ; do #Wait for unwinding recursion to clean up by itself if [ ! -e "$SHADOWLOCK" ]; then #Successfully cleaned up by itself [ -n "$LOG" ] && echo "--Successfully unwound shadow recursion...$PASS" >> $LOG return 0 fi sleep 1 done fi fi return 1 #Not able to clean up by unwinding recursion } #Clean up manually by killing rsync daemon & shadow processes and removing locks function clean_up { kill_rsyncd [ $USESHADOWS -gt 0 ] && remove_shadows rm -f $RSYNCDCONF_SHADOW $SHADOWLOCK [ -n "$LOG" ] && echo "..Finished clean_up... $PASS" >> $LOG } ############################################################################## ##### Start of main code parse_opts "$@" [ -n "$LOG" ] && echo -e "\n[$(date +"%m/%d/%y %H:%M:%S")] [$PPID|$$][$USER|$USERNAME|$(id -un)|$(id -run)] $0 $* $PASS" >> $LOG #Log and timestamp each pass # Check permissions each time through since (potentially) different user context # i.e. make sure vshadow, dosdev and shadowexec.cmd are accessible if [ $USESHADOWS -gt 0 ]; then SHADOWEXECCMD=$(cygpath -w $( (cd -P $(dirname $0) && pwd ))/$SHADOWEXECCMD) if !(which $VSHADOW && which $DOSDEV \ && [ -x $SHADOWEXECCMD ] ) > /dev/null 2>&1 ; then [ -n "$LOG" ] && echo -e "ERROR: Can't access '$VSHADOW' or '$DOSDEV' or '${SHADOWEXECCMD//\\/\\\\}'...\n" >> $LOG #Note we need to protect the \'s in SHADOWEXECCMD if [ $PPID -eq 1 ]; then echo -5 >| $SHADOWLOCK #Signal to calling routine exit 5 elif [ $USESHADOWS -eq 1 ]; then exit 5# Shadows required so unrecoverable error else USESHADOWS=0 # Default to not using shadows fi fi fi ### PASS=0: Clean_up/termination (just a single pass independent of the others) if [ $PASSNUM -eq 0 ] ; then # Terminate & clean up [ -n "$LOG" ] && echo "***Terminating and cleaning up shadows, mounts, and locks (PID=$$)...$PASS" >> $LOG unwind_shadows # Try first to unwind recursion gracefully if [ $? -ne 0 ] ; then [ -n "$LOG" ] && echo -e "--Manually clean up...$PASS" >> $LOG clean_up #Didn't clean up by unwinding recursion, so call manually fi [ -n "$LOG" ] && echo -e "--Termination completed...$PASS\n" >> $LOG exit 0 ### PASS=1: Initial time through elif [ $PASSNUM -eq 1 ] ; then #Launch initial recursion & wait for setup to complete [ -n "$LOG" ] && echo "***Initial pass through (PID=$$)...$PASS" >> $LOG get_rsyncdlock #First do some tests... if [ $STRICT -gt 1 ] ; then #Avoid strict testing if files are aged enough get_strict_age $STRICT STRICT=$? #Sets to zero if aged sufficiently fi if [ $STRICT -eq 1 ] ; then if [ -e $SHADOWLOCK ] ; then #Lock file exists [ -n "$LOG" ] && echo -e "ERROR: Lock file exists...$PASS\n" >> $LOG exit 2 # Lock file exists elif [ -e $RSYNCDLOCK ] ; then #Rsync running [ -n "$LOG" ] && echo -e "ERROR: Rsync daemon already running...$PASS\n" >> $LOG exit 3 # Rsync daemon already running elif [ $USESHADOWS -gt 0 ] && \ ! $VSHADOW -q | grep -q "There are no shadow copies in the system" ; then [ -n "$LOG" ] && echo -e "ERROR: Active shadow copies exist...$PASS\n" >> $LOG exit 4 # Active shadow copies already exist fi fi # Force clean up if needed (may be redundant but good hygeine) unwind_shadows || clean_up #Check permissions on files we call. Also touch SHADOWLOCK # Set up SHADOWDIR and check their permissions if [ $USESHADOWS -gt 0 ] && touch $SHADOWLOCK && \ ! ( stat -c %A $0 | grep -q "^....r.x" && \ [ $(stat -c %G $0 ) = "Administrators" ] && \ stat -c %A $SHADOWEXECCMD | grep -q "^....r.x" && \ [ $(stat -c %G $SHADOWEXECCMD) = "Administrators" ] && \ mkdir -m 775 -p $SHADOWDIR && \ chown $USER.Administrators $SHADOWDIR $SHADOWLOCK $LOG && \ chmod g+rw $SHADOWLOCK $LOG ) ; then [ -n "$LOG" ] && echo "ERROR: Permissions error with '$0' or '$SHADOWEXECCMD' or '$SHADOWDIR' or '$SHADOWLOCK' or '$LOG'....$PASS" >> $LOG && echo >> $LOG rm $SHADOWLOCK #Not using shadows if [ $USESHADOWS -eq 1 ]; then #Shadows required so exit with error exit 6 # Shadow permissions error else USESHADOWS=0 # Fall back to not using shadows fi fi if [ $USESHADOWS -gt 0 ] ; then RELAUNCHCMD="$( (cd -P $(dirname $0) && pwd ))/${0##*/} -2 $*" if [ -n "$SSH_CLIENT" ] ; then #Relaunch using 'at' if you come in via ssh ($USERNAME=SYSTEM) #because then you won't have privileges to run vshadow and dosdev at_launch "$RELAUNCHCMD" #Recurse RET=$? [ -n "$LOG" ] && echo "--Relaunching as SYSTEM user via 'at' in $RET seconds...$PASS" >> $LOG else #Just relaunch directly (for consistency with recursion above) cmd.exe /C $(cygpath -w $(which bash.exe)) -c "$RELAUNCHCMD" & [ -n "$LOG" ] && echo "--Relaunching via 'cmd.exe'...$PASS" >> $LOG fi [ -n "$LOG" ] && echo "--Initial pass going into background waiting for setup to complete...$PASS" >> $LOG while ((TIMEOUT--)) ; do #Wait for setup completion or timeout [ -s "$SHADOWLOCK" ] && break sleep 1 done if [ -s "$SHADOWLOCK" ] ; then #Check to see if returned with errors RETURN=$((-$(< $SHADOWLOCK))) #Undo negative of error codes else RETURN=9 #Timeout error clean_up fi if [ $RETURN -ge 7 -a $USESHADOWS -eq 2 ] ; then USESHADOWS=0 # Fall back to trying rsync without shadows fi fi if [ $USESHADOWS -eq 0 ] ; then #Launch rsync without shadows for ((WAIT=1; WAIT <=SHORTTIMEOUT; WAIT++)) ; do # Wait for any potential unwinding shadows to clean up [ -e $SHADOWLOCK ] || break #Stop waiting when shadow lock deleted sleep 1 done [ -e $SHADOWLOCK -o -e $RSYNCDLOCK ] && clean_up # Just in case... $RSYNC --daemon --config=${RSYNCDCONF} ${RSYNCDADDRESS:+--address=$RSYNCDADDRESS} ${RSYNCDPORT:+--port=$RSYNCDPORT} ${RSYNCDBWLIMIT:+--bwlimit=$RSYNCDBWLIMIT} ${RSYNCDOTHER} RETURN=$? [ -n "$LOG" ] && echo "--Starting rsyncd daemon (WITHOUT shadow mounts) in background...$PASS" >> $LOG [ -n "$LOG" ] && echo "..Rsyncd Params: Conf=$RSYNCDCONF Address=$RSYNCDADDRESS Port=$RSYNCDPORT BWLimit=$RSYNCDBWLIMIT Other=$RSYNCDOTHER...$PASS" >> $LOG fi if [ $RETURN -le 0 ] ; then #No error for ((WAIT=1; WAIT <=SHORTTIMEOUT; WAIT++)) ; do # Wait for rsync to set up if [ -e "$RSYNCDLOCK" ]; then [ -n "$LOG" ] && echo -e "..Rsyncd started successfully (PID=$(< $RSYNCDLOCK))...$PASS\n" >> $LOG exit 0 # Exit 0 on success fi sleep 1 done [ -n "$LOG" ] && echo -e "--Rsyncd failed to start...$PASS\n" >> $LOG RETURN=10 # Rsyncd failed to start fi clean_up # Clean up on failure... exit $RETURN # Return failure code ### PASS=2: Called by at or cmd.exe from initial recursion elif [ $PASSNUM -eq 2 ] ; then #Determine shadow mounts and launch vshadow trap clean_up HUP TERM [ -n "$LOG" ] && echo "**Running inital shadow setup (PID=$$)...$PASS" >> $LOG get_shadowmounts ( echo Y | $VSHADOW -da ) > /dev/null 2>&1 #Delete old shadow copies # This should be redundant, but just in case... [ -n "$LOG" ] && echo "--Starting shadow copy and waiting for 'exec' script to terminate...$PASS" >> $LOG export ARGS="$@" #Need to export to cmd.exe subshell since can't pass $VSHADOW -exec="$SHADOWEXECCMD" ${shadowmou...@]} > /dev/null 2>&1 #NOTE: vshadow doesn't finish until script $SHADOWEXECCMD script completed [ -n "$LOG" ] && echo "--Vshadow terminated: cleaning up...$PASS" >> $LOG clean_up # Should already be mostly cleaned up by unwinding of recursion [ -n "$LOG" ] && echo -e "[$(date +"%m/%d/%y %H:%M:%S")] Done! Clean up completed...$PASS\n" >> $LOG exit 0 ### PASS=3: Called by -exec command of vshadow elif [ $PASSNUM -eq 3 ] ; then #Mount shadows & launch rsync [ -n "$LOG" ] && echo "**Running exec command spawned by vshadow (PID=$$)...$PASS" >> $LOG get_shadowmounts #Create paired array of the drive letter and the windows shadow path SHADOWPAIRS=( `$VSHADOW -q | sed -ne "s/\( *- Original Volume name:.*\[\([A-Z]\):.*\)\| *- Shadow copy device name: */\2/p"` ) NUMSHADOWS=$((${#shadowpai...@]}/2)) if [ $NUMSHADOWS -ne ${#shadowmou...@]} ] ; then [ -z "$LOG"] || echo -e "ERROR: Shadows created $NUMSHADOWS not equal to shadows requested (${#shadowmou...@]})...$PASS\n" >> $LOG echo -7 >| $SHADOWLOCK exit 7 fi get_freedrives $NUMSHADOWS [ -n "$LOG" ] && echo "..Drive letters=${freedriv...@]} $PASS" >> $LOG if [ $NUMSHADOWS -gt ${#freedriv...@]} ] ; then [ -n "$LOG" ] && echo -e "ERROR: Not enough free drive letters...$PASS\n" >> $LOG echo -8 >| $SHADOWLOCK exit 8 fi cp -f $RSYNCDCONF $RSYNCDCONF_SHADOW [ -n "$LOG" ] && echo "..Shadow mounts= $PASS" >> $LOG for (( index=0 ; index < $NUMSHADOWS; index+=1 )) ; do # Create drive letter corresponding to shadow mount $DOSDEV "${FREEDRIVES[$index]}:" "${SHADOWPAIRS[2*$index+1]}" # Mount drive letter mount -f -s "${FREEDRIVES[$index]}:" $SHADOWDIR/${FREEDRIVES[$index]} [ -n "$LOG" ] && echo " ${FREEDRIVES[$index]}:->${SHADOWPAIRS[2*$index]}: ${SHADOWPAIRS[2*$index+1]}" >> $LOG [ -n "$LOG" ] && (echo -en " " ; mount | grep "^${FREEDRIVES[$index]}:") >> $LOG #Rewrite rsyncd.conf substituting shadow paths where appropriate sed -i -e "s%^\([ \t]*path[ \t]*=[ \t]*\)/\(cygdrive/\)\?${SHADOWPAIRS[2*$index]}\([ \t]*$\|[ \t]*#\|/\)%\1$SHADOWDIR/${FREEDRIVES[$index]}\3%i" $RSYNCDCONF_SHADOW done kill_rsyncd #Should already be dead but kill again just in case... [ -n "$LOG" ] && echo "--Starting rsyncd daemon (with shadow mounts) and waiting for file transfer to complete...$PASS" >> $LOG [ -n "$LOG" ] && echo "..Rsyncd Params: Conf=$RSYNCDCONF Address=$RSYNCDADDRESS Port=$RSYNCDPORT BWLimit=$RSYNCDBWLIMIT Other=$RSYNCDOTHER...$PASS" >> $LOG echo $$ >| $SHADOWLOCK #Put shell PID in lockfile & signal successful setup $RSYNC --daemon --no-detach --config=${RSYNCDCONF_SHADOW} ${RSYNCDADDRESS:+--address=$RSYNCDADDRESS} ${RSYNCDPORT:+--port=$RSYNCDPORT} ${RSYNCDBWLIMIT:+--bwlimit=$RSYNCDBWLIMIT} ${RSYNCDOTHER} RET=$? #Rsync runs until shell or rsync killed (note we typically kill the shell) [ -n "$LOG" ] && echo "--Rsync terminated($RET): exiting & returning control to PASS=2...$PASS" >> $LOG exit $RET #Returns exit of rsync fi exit 11 #Shouldn't get here... ------------------------------------------------------------------------------- REM Shadowexec.cmd REM Copyright Jeffrey J. Kosowsky December 2008 REM Version 0.21 REM This cmd.exe script file should be in the same directory as the REM bash script: shadowmountrsync @echo OFF C:\cygwin\bin\bash.exe -c "$(cygpath.exe '%~p0\shadowmountrsync') -3 %ARGS%" ------------------------------------------------------------------------------ SF.Net email is Sponsored by MIX09, March 18-20, 2009 in Las Vegas, Nevada. The future of the web can't happen without you. Join us at MIX09 to help pave the way to the Next Web now. Learn more and register at http://ad.doubleclick.net/clk;208669438;13503038;i?http://2009.visitmix.com/ _______________________________________________ BackupPC-users mailing list BackupPC-users@lists.sourceforge.net List: https://lists.sourceforge.net/lists/listinfo/backuppc-users Wiki: http://backuppc.wiki.sourceforge.net Project: http://backuppc.sourceforge.net/