PerforceBackupProcedureLibrary.bat

@echo off
REM Copyright (c) 2009 Exemplics LLC

REM Permission is hereby granted, free of charge, to any person obtaining a copy
REM of this software and associated documentation files (the "Software"), to deal
REM in the Software without restriction, including without limitation the rights
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
REM copies of the Software, and to permit persons to whom the Software is
REM furnished to do so, subject to the following conditions:

REM The above copyright notice and this permission notice shall be included in
REM all copies or substantial portions of the Software.

REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
REM THE SOFTWARE.

REM Version 2009.2
 
goto Initialize:

REM <Functions>
 
:StartProcedure
  REM delayed expansion must be disabled so the ! character can be echoed
  setlocal DisableDelayedExpansion
  echo ^<?xml version="1.0" encoding="UTF-8"?^> > %xmlOut%
  if defined includeStyleSheet (
    echo ^<?xml-stylesheet type="text/xsl" href="PerforceBackupProcedureHTML.xsl"?^> >> %xmlOut%
  )
  echo ^<!DOCTYPE BackupProcedure^> >> %xmlOut%
  echo ^<BackupProcedure ServerShortName="%serverShortName%" CheckpointNumber="%checkpointNumber%" BackupType="%backupType%"^> >> %xmlOut%
  goto :Eof
 
:P4Info
  setlocal EnableDelayedExpansion
  pushd "%workspace%"
  echo   ^<P4Info^> >> %xmlOut%
  for /f "delims=: tokens=1,*" %%i in ('p4 info') do (
    set rawValue=%%j
    REM Strip off the leading space.
    set value=!rawValue:~1!
    echo     ^<Variable Name="%%i" Value="!value!" /^> >> %xmlOut%
  )
  echo   ^</P4Info^> >> %xmlOut%
  popd
  goto :Eof
 
:Environment
  setlocal EnableDelayedExpansion
  echo   ^<Environment^> >> %xmlOut%
           for /f "tokens=1,2 delims==" %%i in ('set') do (
             set rawValue=%%j
             REM Replace " characters with equivalent xml entities.
             set value=!rawValue:"=&quot;!
             echo     ^<Variable Name="%%i" Value="!value!" /^> >> %xmlOut%
           )
  echo   ^</Environment^> >> %xmlOut%
  goto :Eof
 
:StartSteps
  echo   ^<Steps^> >> %xmlOut%
  goto :Eof
 
:Steps
  REM Verify and checkpoint Perforce database.
  REM Do not backup if an error occurs here.
  call :Verify
  if ERRORLEVEL 1 goto :Eof
  call :SearchVerifyOutput
  if ERRORLEVEL 1 goto :Eof
  call :Checkpoint
  if ERRORLEVEL 1 goto :Eof
  REM Restore the checkpoint and verify it.
  REM Continue to backup even if these fail.
  call :Restore
  call :TestRestore
  call :Backup
  goto :Eof
 
:Verify
  REM delayed expansion must be disabled so the ! character can be echoed
  setlocal DisableDelayedExpansion
  pushd "%workspace%"
  set startTime=%DATE% %TIME:~0,-3%
  set output=%verifyOut%
  set command=p4 verify //...
 
  echo     ^<Step Name="Verify"^> >> %xmlOut%
             call :P4Set
  echo       ^<Command CurrentDirectory="%CD%"^>^<![CDATA[%command%]]^>^</Command^> >> %xmlOut%
  echo       ^<ConsoleText^>^<![CDATA[ >> %xmlOut%
 
               if defined runCommands %command% > "%output%" 2>> %xmlOut%
               set result=%ERRORLEVEL%
 
  echo       ]]^>^</ConsoleText^> >> %xmlOut%
             call :StartOutputs
               call :File "%output%" VerificationResultsFile
             call :EndOutputs
  echo       ^<Summary ResultCode="%result%" StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" /^> >> %xmlOut%
  echo     ^</Step^> >> %xmlOut%
  popd
  exit /b %result%
 
:SearchVerifyOutput
  REM Look for lines ending in ! (MISSING! or BAD!)
  REM delayed expansion must be disabled so the ! character can be echoed
  setlocal DisableDelayedExpansion
  set startTime=%DATE% %TIME:~0,-3%
  set command=findstr /e ! "%verifyOut%"
 
  echo     ^<Step Name="SearchVerifyOutput"^> >> %xmlOut%
  echo       ^<Command CurrentDirectory="%CD%"^>^<![CDATA[%command%]]^>^</Command^> >> %xmlOut%
             call :StartInputs
               call :File "%verifyOut%" VerificationResultsFile
             call :EndInputs
  echo       ^<ConsoleText^>^<![CDATA[ >> %xmlOut%
 
               if defined runCommands (
                 %command% >> %xmlOut% 2>&1
                 if ERRORLEVEL 1 (
                   REM not finding a line ending with ! is a good thing
                   set result=0
                 ) else (
                   set result=1
                 )
               ) else (
                 REM Succeed if in test mode
                 set result=0
               )
 
  echo       ]]^>^</ConsoleText^> >> %xmlOut%
  echo       ^<Summary ResultCode="%result%" StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" /^> >> %xmlOut%
  echo     ^</Step^> >> %xmlOut%
  exit /b %result%
 
:Checkpoint
  REM delayed expansion must be disabled so the ! character can be echoed
  setlocal DisableDelayedExpansion
  pushd "%workspace%"
  set startTime=%DATE% %TIME:~0,-3%
  set output=%checkpoint%
  set command=p4 admin checkpoint -z
 
  echo     ^<Step Name="Checkpoint"^> >> %xmlOut%
             call :P4Set
  echo       ^<Command CurrentDirectory="%CD%"^>^<![CDATA[%command%]]^>^</Command^> >> %xmlOut%
  echo       ^<ConsoleText^>^<![CDATA[ >> %xmlOut%
 
               if defined runCommands %command% >> %xmlOut% 2>&1
               set result=%ERRORLEVEL%
 
  echo       ]]^>^</ConsoleText^> >> %xmlOut%
             call :StartOutputs
               call :File "%output%" CheckpointFile
             call :EndOutputs
  echo       ^<Summary ResultCode="%result%" StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" /^> >> %xmlOut%
  echo     ^</Step^> >> %xmlOut%
  popd
  exit /b %result%
 
:Restore
  REM Test restore the checkpoint
  REM delayed expansion must be disabled so the ! character can be echoed
  setlocal DisableDelayedExpansion
  REM Start with a clean folder
  rd /q/s "%restoreOut%" 2> nul
  mkdir "%restoreOut%" 2> nul
 
  pushd "%restoreOut%"
  set startTime=%DATE% %TIME:~0,-3%
  set command=p4d -jr -z "%checkpoint%"
 
  echo     ^<Step Name="Restore"^> >> %xmlOut%
             call :P4Set
             call :StartInputs
               call :File "%checkpoint%" CheckpointFile
             call :EndInputs
  echo       ^<Command CurrentDirectory="%CD%"^>^<![CDATA[%command%]]^>^</Command^> >> %xmlOut%
  echo       ^<ConsoleText^>^<![CDATA[ >> %xmlOut%
 
               if defined runCommands %command% >> %xmlOut% 2>&1
               set result=%ERRORLEVEL%
 
  echo       ]]^>^</ConsoleText^> >> %xmlOut%
  echo       ^<Summary ResultCode="%result%" StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" /^> >> %xmlOut%
  echo     ^</Step^> >> %xmlOut%
  popd
  exit /b %result%
 
:TestRestore
  setlocal EnableDelayedExpansion

  REM Verify test restore of the checkpoint
  pushd "%restoreOut%"
  set startTime=%DATE% %TIME:~0,-3%
 
  echo     ^<Step Name="TestRestore"^> >> %xmlOut%
  echo       ^<Command CurrentDirectory="%CD%"^>^</Command^> >> %xmlOut%

             REM Verify directory contents (db.*)
             set actual=
             for %%i in (*) do set actual=!actual! %%i
             set expected= db.archmap db.boddate db.bodtext db.change db.changex db.counters db.depot db.desc db.domain db.fix db.fixrev db.group db.have db.integ db.integed db.ixdate db.ixtext db.job db.jobdesc db.label db.locks db.logger db.message db.monitor db.protect db.resolve db.rev db.revcx db.revdx db.revhx db.review db.revpx db.revsx db.traits db.trigger db.user db.view db.working
 
             if "!actual!"=="%expected%" (
               set result=0
             ) else (
               set result=1
             )
  echo       ^<Summary ResultCode="%result%" StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" Actual="!actual!" Expected="%expected%"/^> >> %xmlOut%
  echo     ^</Step^> >> %xmlOut%
  popd
  exit /b %result%
 
:Backup
  setlocal EnableDelayedExpansion
  pushd "%workspace%"
  set licenseFile=%serverRoot%\license
  set description=%backupType% backup of %serverRoot% checkpoint %checkpointNumber% %DATE% %TIME:~0,-3%
  set job=%serverShortName%  %checkpointNumber%
  set backupFile=%backupDirectory%\%serverShortName%.%checkpointNumber%.%backupType%.bkf
  set backupLog=%outputPathPrefix%.Backup.txt

  REM /v:yes   = verify data after backup
  REM /l:f     = full log
  REM /snap:on = snapshot of the data at the time the backup began
  set verifyData=yes
  set logType=f
  set snap=on

  REM Where backup items list will be saved
  set backupSelectionFile=%outputPathPrefix%.bks
 
  set startTime=%DATE% %TIME:~0,-3%
  set output=%backupFile%
 
  echo     ^<Step Name="Backup"^> >> %xmlOut%
             call :StartInputs
               REM "cmd /u /c echo" is used to output the backup items
               REM to the backup selection file in "unicode" format.
               if exist "%licenseFile%" (
                 REM licensed server
                 call :File "%licenseFile%" ServerLicenseFile
                 cmd /u /c echo %licenseFile% > "%backupSelectionFile%"
                 call :File "%checkpoint%" CheckpointFile
                 cmd /u /c echo %checkpoint% >> "%backupSelectionFile%"
               ) else (
                 call :File "%checkpoint%" CheckpointFile
                 cmd /u /c echo %checkpoint% > "%backupSelectionFile%"
               )
               for /f "tokens=4,*" %%i in ('p4 depots') do (
                 set localOrSpec=false
                 if %%i==local set localOrSpec=true
                 if %%i==spec  set localOrSpec=true
                 if !localOrSpec!==true (
                   REM Depot Description starts with '.
                   REM The path we want comes before that.
                   for /f "tokens=1 delims='" %%n in ("%%j") do (
                     set rawValue=%%n
                     REM strip the trailing "/... "
                     set versionedFileTree=!rawValue:~0,-5!
                     set isAbsolute=false
                     REM Path is absolute path if it starts with / or drive letter.
                     REM The first case should not happen on Windows
                     if "!versionedFileTree:~0,1!"=="/" set isAbsolute=true
                     if "!versionedFileTree:~1,1!"==":" set isAbsolute=true
                     if !isAbsolute!==true (
                       REM replace pathname delimiter / with \
                       set versionedFileTree=!versionedFileTree:/=\!
                     ) else (
                       set versionedFileTree=%serverRoot%\!versionedFileTree!
                     )
                     REM Check to see if the directory exists.
                     REM It will not if no files have been added to its depot.
                     dir /b "!versionedFileTree!" > nul 2>&1
                     if not ERRORLEVEL 1 (
                       REM The directory does exist.
                       REM "cmd /u /c echo" is used to output the
                       REM backup items to the backup selection file in
                       REM "unicode" format. Directories must end with "\".
                       cmd /u /c echo !versionedFileTree!\ >> "%backupSelectionFile%"
                       call :Directory "!versionedFileTree!" DepotVersionedFileTree
                     )
                   )
                 )
               )
             call :EndInputs
             set command=ntbackup backup "@%backupSelectionFile%" /m %backupType% /d "%description%" /j "%job%" /v:%verifyData% /l:%logType% /snap:%snap% /f "%backupFile%"
             setlocal DisableDelayedExpansion
  echo       ^<Command CurrentDirectory="%CD%"^>^<![CDATA[%command%]]^>^</Command^> >> %xmlOut%
  echo       ^<ConsoleText^>^<![CDATA[ >> %xmlOut%
 
               if defined runCommands %command% >> %xmlOut% 2>&1
               set result=%ERRORLEVEL%
 
  echo       ]]^>^</ConsoleText^> >> %xmlOut%
             REM Copy the ntbackup log file to the log directory
             pushd "%USERPROFILE%\Local Settings\Application Data\Microsoft\Windows NT\NTBackup\data"
             REM Find the backup log corresponding to this backup
             for %%i in (backup*.log) do (
               REM Use "type" to convert from UNICODE to ANSI for findstr
               REM The backup logs contains the backupFile name. Restore logs do not.
               type "%%i" | findstr /c:"%backupFile%" > nul
               if NOT ERRORLEVEL 1 (
                 REM string found
                 set ntbackupLog=%%i
                 goto :Done
               )
             )
             :Done
             copy /y "%ntbackupLog%" "%backupLog%" > nul
             popd
             call :StartOutputs
               call :File "%backupSelectionFile%" BackupSelectionFile
               call :File "%output%" BackupFile
               call :File "%backupLog%" BackupLogFile
             call :EndOutputs
  echo       ^<Summary ResultCode="%result%" StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" /^> >> %xmlOut%
  echo     ^</Step^> >> %xmlOut%
  popd
  exit /b %result%
 
:EndSteps
  echo   ^</Steps^> >> %xmlOut%
  goto :Eof
 
:Storage
  echo   ^<Storage^> >> %xmlOut%
           call :Directory "%serverRoot%" ServerRoot
           call :Directory "%logDirectory%" LogDirectory
           call :Directory "%backupDirectory%" BackupDirectory
  echo   ^</Storage^> >> %xmlOut%
  goto :Eof
 
:EndProcedure
  echo ^</BackupProcedure^> >> %xmlOut%
  goto :Eof
 
:P4Set
  echo       ^<P4Set^> >> %xmlOut%
               for /f "tokens=1,2 delims==" %%i in ('p4 set') do (
                 echo         ^<Variable Name="%%i" Value="%%j" /^> >> %xmlOut%
               )
  echo       ^</P4Set^> >> %xmlOut%
  goto :Eof
 
:StartInputs
  echo       ^<Inputs^> >> %xmlOut%
  goto :Eof
 
:File
  setlocal
  REM strip quotes
  set pathname=%~1
  set kind=%~2
  if exist "%pathname%" (
    for /f "skip=5 tokens=4" %%i in ('dir /-c /q /4 "%pathname%"') do (
      echo          ^<File Bytes="%%i" Pathname="%pathname%" Kind="%kind%" /^> >> %xmlOut%
      REM stop at first match
      goto :Eof
    )
  ) else (
    echo          ^<File Bytes="" Pathname="%pathname%" Kind="%kind%" /^> >> %xmlOut%
  )
  goto :Eof
 
:Directory
  setlocal EnableDelayedExpansion
  REM strip quotes
  set pathname=%~1
  set kind=%~2
  set bytesFree=
  if exist "%pathname%" (
    for /f "skip=8 tokens=3" %%i in ('dir /ad /-c "%pathname%"') do (
      set bytesFree=%%i
    )
  )
  echo     ^<Directory BytesFree="!bytesFree!" Pathname="%pathname%" Kind="%kind%" /^> >> %xmlOut%
  goto :Eof
 
:EndInputs
  echo       ^</Inputs^> >> %xmlOut%
  goto :Eof
 
:StartOutputs
  echo       ^<Outputs^> >> %xmlOut%
  goto :Eof
 
:EndOutputs
  echo       ^</Outputs^> >> %xmlOut%
  goto :Eof

REM </Functions>

REM <Initialize>
:Initialize

REM Setup the environment
setlocal EnableDelayedExpansion

REM get the checkpoint number
pushd "%workspace%"
for /f "delims=: tokens=1,*" %%i in ('p4 info') do (
  if "%%i"=="Server root" (
    set rawValue=%%j
    REM Strip off the leading space.
    set serverRoot=!rawValue:~1!
  )
)
for /f %%i in ('p4 counter journal') do set lastCheckpointNumber=%%i
set /a checkpointNumber=%lastCheckpointNumber%+1
popd

REM These need to be global for use in multiple steps

REM Where checkpoint file is located
set checkpoint=%serverRoot%\checkpoint.%checkpointNumber%.gz

REM Path and file name prefix to use for output files.
set outputPathPrefix=%logDirectory%\%serverShortName%.%checkpointNumber%

REM Where p4 verify results will be saved
set verifyOut=%outputPathPrefix%.Verify.txt

REM Where script results will be stored in xml format
set xmlOut="%outputPathPrefix%.BackupProcedure.xml"

REM Where a test restore of the checkpoint will be performed
set restoreOut=%outputPathPrefix%.Restore

REM </Initialize>

REM <Main>
set startTime=%DATE% %TIME:~0,-3%
call :StartProcedure
  call :P4Info
  call :Environment
  call :StartSteps
    call :Steps
  call :EndSteps
  call :Storage
  echo   ^<Summary StartTime="%startTime%" EndTime="%DATE% %TIME:~0,-3%" /^> >> %xmlOut%
call :EndProcedure
REM </Main>
 
view plain text source