| 1 | #!/bin/ksh | 
|---|
| 2 | # IBM_PROLOG_BEGIN_TAG | 
|---|
| 3 | # This is an automatically generated prolog. | 
|---|
| 4 | # | 
|---|
| 5 | # | 
|---|
| 6 | # | 
|---|
| 7 | # Licensed Materials - Property of IBM | 
|---|
| 8 | # | 
|---|
| 9 | # (C) COPYRIGHT International Business Machines Corp. 2005,2006 | 
|---|
| 10 | # All Rights Reserved | 
|---|
| 11 | # | 
|---|
| 12 | # US Government Users Restricted Rights - Use, duplication or | 
|---|
| 13 | # disclosure restricted by GSA ADP Schedule Contract with IBM Corp. | 
|---|
| 14 | # | 
|---|
| 15 | # IBM_PROLOG_END_TAG | 
|---|
| 16 | # @(#)93 1.10 src/avs/fs/mmfs/ts/admin/mmapplypolicy.sh, mmfs, avs_rgpfs24, rgpfs240610b 2/17/06 16:50:52 | 
|---|
| 17 | ################################################################################ | 
|---|
| 18 | # | 
|---|
| 19 | # Usage: | 
|---|
| 20 | #   mmapplypolicy {Device|Directory} [-P PolicyFile] [-I {yes|defer|test}] | 
|---|
| 21 | #                 [-L n] [-D yyyy-mm-dd[@hh:mm[:ss]]] [-s WorkDirectory] | 
|---|
| 22 | # | 
|---|
| 23 | # where: | 
|---|
| 24 | #   Device            The device name of the file system from which files | 
|---|
| 25 | #                     are to be deleted or migrated. | 
|---|
| 26 | # | 
|---|
| 27 | #   Directory         The fully-qualified path name of a GPFS file system | 
|---|
| 28 | #                     subtree form which files are to be deleted or migrated. | 
|---|
| 29 | # | 
|---|
| 30 | #   -P PolicyFilename The name of the file containing the policy rules to be | 
|---|
| 31 | #                     applied.  If not specified, the policy rules currently | 
|---|
| 32 | #                     in effect for the file system will be used. | 
|---|
| 33 | # | 
|---|
| 34 | #   -I yes|defer|test  Determines what actions mmapplypolicy will take on files: | 
|---|
| 35 | # | 
|---|
| 36 | #                      If -I yes is specified, all applicable MIGRATE and DELETE | 
|---|
| 37 | #                      policy rules will be executed, and the data movement | 
|---|
| 38 | #                      between pools is done during the execution of the | 
|---|
| 39 | #                      mmapplypolicy command.  This is the default action. | 
|---|
| 40 | # | 
|---|
| 41 | #                      If -I defer is specified, all applicable MIGRATE and | 
|---|
| 42 | #                      DELETE policy rules will be executed, but the actual | 
|---|
| 43 | #                      data movement between pools is deferred until the next | 
|---|
| 44 | #                      mmrestripefs command. | 
|---|
| 45 | # | 
|---|
| 46 | #                      If -I test is specified, all policy rules are evaluated, | 
|---|
| 47 | #                      but the mmapplypolicy command only displays the actions | 
|---|
| 48 | #                      that would be executed had -I defer or -I yes been | 
|---|
| 49 | #                      specified.  This option is intended for testing the | 
|---|
| 50 | #                      effects of particular policy rules; there is no actual | 
|---|
| 51 | #                      deletion of files or data movement between pools. | 
|---|
| 52 | # | 
|---|
| 53 | #   -L n             Controls the level of information displayed by the command. | 
|---|
| 54 | #                    n should be one of the following values: | 
|---|
| 55 | #                      0 - Only display serious errors. | 
|---|
| 56 | #                      1 - Display some information as the command executes, | 
|---|
| 57 | #                            but not for each file.  This is the default. | 
|---|
| 58 | #                      2 - Display each chosen file and the scheduled migration | 
|---|
| 59 | #                            or deletion action. | 
|---|
| 60 | #                      3 - And display each candidate file and the applicable | 
|---|
| 61 | #                            rule. | 
|---|
| 62 | #                      4 - And display each explicitly EXCLUDEd file and the | 
|---|
| 63 | #                            applicable rule. | 
|---|
| 64 | #                      5 - And display the attributes of candidate and EXCLUDEd | 
|---|
| 65 | #                            files. | 
|---|
| 66 | #                      6 - And display non-candidate files and their attributes. | 
|---|
| 67 | # | 
|---|
| 68 | #   -D yyyy-mm-dd[@hh:mm[:ss]]   Specifies the date and (UTC) time to be used | 
|---|
| 69 | #                      by the mmapplypolicy command when evaluating the policy | 
|---|
| 70 | #                      rules.  The default is the current date and time. | 
|---|
| 71 | #                      If only a date is specified, the time will default to | 
|---|
| 72 | #                      00:00:00. | 
|---|
| 73 | # | 
|---|
| 74 | #   -s WorkDirectory  The directory to be used for temporary storage during | 
|---|
| 75 | #                     command processing.  The default directory is /tmp. | 
|---|
| 76 | # | 
|---|
| 77 | ################################################################################ | 
|---|
| 78 |  | 
|---|
| 79 | # Include global declarations and service routines. | 
|---|
| 80 | . /usr/lpp/mmfs/bin/mmglobfuncs | 
|---|
| 81 | . /usr/lpp/mmfs/bin/mmsdrfsdef | 
|---|
| 82 | . /usr/lpp/mmfs/bin/mmfsfuncs | 
|---|
| 83 |  | 
|---|
| 84 | sourceFile="mmapplypolicy.sh" | 
|---|
| 85 | [[ -n $DEBUG || -n $DEBUGmmapplypolicy ]] && set -x | 
|---|
| 86 | $mmTRACE_ENTER "$*" | 
|---|
| 87 |  | 
|---|
| 88 | # Local work files.  Names should be of the form: | 
|---|
| 89 | #   fn=${tmpDir}fn.${mmcmd}.$$ | 
|---|
| 90 |  | 
|---|
| 91 | tspolicyFile=${tmpDir}tspolicyFile.${mmcmd}.$$ | 
|---|
| 92 |  | 
|---|
| 93 | LOCAL_FILES=" $tspolicyFile " | 
|---|
| 94 |  | 
|---|
| 95 |  | 
|---|
| 96 | # Local variables | 
|---|
| 97 | usageMsg=457 | 
|---|
| 98 |  | 
|---|
| 99 |  | 
|---|
| 100 | # Local functions | 
|---|
| 101 |  | 
|---|
| 102 |  | 
|---|
| 103 | ########################################################################## | 
|---|
| 104 | # | 
|---|
| 105 | # Function:  Verifies the correctness of the input date-time string. | 
|---|
| 106 | # | 
|---|
| 107 | # Input:     $1 - input date-time string | 
|---|
| 108 | # | 
|---|
| 109 | # Output:    The verified string: yyyy-mm-dd@hh:mm:ss | 
|---|
| 110 | # | 
|---|
| 111 | # Returns:   0 - no errors encountered | 
|---|
| 112 | #            1 - invalid date-time string | 
|---|
| 113 | # | 
|---|
| 114 | ########################################################################## | 
|---|
| 115 | function parseDateTime  # <dtString> | 
|---|
| 116 | { | 
|---|
| 117 | typeset sourceFile="mmapplypolicy.sh" | 
|---|
| 118 | [[ -n $DEBUG || -n $DEBUGparseDateTime ]] && set -x | 
|---|
| 119 | typeset dt=$1 | 
|---|
| 120 |  | 
|---|
| 121 | typeset dateTimeString | 
|---|
| 122 |  | 
|---|
| 123 | if [[ $dt = [0-9][0-9][0-9][0-9]-0[1-9]-[0-3][0-9] ]] | 
|---|
| 124 | then | 
|---|
| 125 | dateTimeString="${dt}@00:00:00" | 
|---|
| 126 | elif [[ $dt = [0-9][0-9][0-9][0-9]-1[012]-[0-3][0-9] ]] | 
|---|
| 127 | then | 
|---|
| 128 | dateTimeString="${dt}@00:00:00" | 
|---|
| 129 | elif [[ $dt = [0-9][0-9][0-9][0-9]-0[1-9]-[0-3][0-9]@[012][0-9]:[0-5][0-9] ]] | 
|---|
| 130 | then | 
|---|
| 131 | dateTimeString="${dt}:00" | 
|---|
| 132 | elif [[ $dt = [0-9][0-9][0-9][0-9]-1[012]-[0-3][0-9]@[012][0-9]:[0-5][0-9] ]] | 
|---|
| 133 | then | 
|---|
| 134 | dateTimeString="${dt}:00" | 
|---|
| 135 | elif [[ $dt = [0-9][0-9][0-9][0-9]-0[1-9]-[0-3][0-9]@[012][0-9]:[0-5][0-9]:[0-5][0-9] ]] | 
|---|
| 136 | then | 
|---|
| 137 | dateTimeString="$dt" | 
|---|
| 138 | elif [[ $dt = [0-9][0-9][0-9][0-9]-1[012]-[0-3][0-9]@[012][0-9]:[0-5][0-9]:[0-5][0-9] ]] | 
|---|
| 139 | then | 
|---|
| 140 | dateTimeString="$dt" | 
|---|
| 141 | else | 
|---|
| 142 | # Unexpected date-time string. | 
|---|
| 143 | return 1 | 
|---|
| 144 | fi | 
|---|
| 145 |  | 
|---|
| 146 | # Return the result. | 
|---|
| 147 | print -- "$dateTimeString" | 
|---|
| 148 | return 0 | 
|---|
| 149 |  | 
|---|
| 150 | }  #----- end of function parseDateTime ---------------------- | 
|---|
| 151 |  | 
|---|
| 152 |  | 
|---|
| 153 |  | 
|---|
| 154 | ####################### | 
|---|
| 155 | # Mainline processing | 
|---|
| 156 | ####################### | 
|---|
| 157 |  | 
|---|
| 158 | ################################# | 
|---|
| 159 | # Process the command arguments. | 
|---|
| 160 | ################################# | 
|---|
| 161 | [[ $arg1 = '-?' || $arg1 = '-h' || $arg1 = '--help' || $arg1 = '--' ]] &&  \ | 
|---|
| 162 | syntaxError "help" $usageMsg | 
|---|
| 163 |  | 
|---|
| 164 | [[ $argc -lt 1  ]] &&  \ | 
|---|
| 165 | syntaxError "missingArgs" $usageMsg | 
|---|
| 166 |  | 
|---|
| 167 | # Determine what was specified on the command line. | 
|---|
| 168 | if [[ $arg1 = /* && $arg1 != +(/)dev+(/)* ]] | 
|---|
| 169 | then | 
|---|
| 170 | # If the first argument starts with a "/" but not "/dev/", | 
|---|
| 171 | # it is assumed to be a fully-qualified directory name. | 
|---|
| 172 | directory=$arg1 | 
|---|
| 173 | else | 
|---|
| 174 | # Otherwise, the argument is assumed to be a file system device name. | 
|---|
| 175 | device=$arg1 | 
|---|
| 176 | deviceName=${device##+(/)dev+(/)}  # Name stripped of /dev/ prefix. | 
|---|
| 177 | fi  # end of if [[ $arg1 = "/"* && $arg1 != +(/)dev+(/)* ]] | 
|---|
| 178 |  | 
|---|
| 179 | # Move past the required parameters. | 
|---|
| 180 | shift 1 | 
|---|
| 181 |  | 
|---|
| 182 | # Process the rest of the options. | 
|---|
| 183 | while getopts :D:I:L:P:s: OPT | 
|---|
| 184 | do | 
|---|
| 185 | case $OPT in | 
|---|
| 186 | D) [[ -n $Dflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT" | 
|---|
| 187 | Dflag="-$OPT"; Darg=$OPTARG; | 
|---|
| 188 | dateTimeString=$(parseDateTime "$Darg") | 
|---|
| 189 | [[ -z $dateTimeString ]] &&  \ | 
|---|
| 190 | syntaxError "invalidOption" $usageMsg "-$OPT $OPTARG" | 
|---|
| 191 | ;; | 
|---|
| 192 |  | 
|---|
| 193 | I) [[ -n $Iflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT" | 
|---|
| 194 | Iflag="-$OPT"; Iarg=$OPTARG; | 
|---|
| 195 | [[ $Iarg != yes && $Iarg != defer && $Iarg != test ]] &&  \ | 
|---|
| 196 | syntaxError "invalidOption" $usageMsg "-$OPT $OPTARG" | 
|---|
| 197 | ;; | 
|---|
| 198 |  | 
|---|
| 199 | L) [[ -n $Lflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT" | 
|---|
| 200 | Lflag="-$OPT"; Larg=$OPTARG; | 
|---|
| 201 | listingLevel=$(checkIntRange $Lflag $Larg 0 6) | 
|---|
| 202 | [[ $? -ne 0 ]] && cleanupAndExit | 
|---|
| 203 | ;; | 
|---|
| 204 |  | 
|---|
| 205 | P) [[ -n $Pflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT" | 
|---|
| 206 | Pflag="-$OPT"; Parg=$OPTARG; | 
|---|
| 207 | policyFile="$Parg" | 
|---|
| 208 | ;; | 
|---|
| 209 |  | 
|---|
| 210 | s) [[ -n $sflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT" | 
|---|
| 211 | sflag="-$OPT"; sarg=$OPTARG; | 
|---|
| 212 | workDirectory="$sarg" | 
|---|
| 213 | ;; | 
|---|
| 214 |  | 
|---|
| 215 | +[DILPs]) # Invalid option. | 
|---|
| 216 | syntaxError "invalidOption" $usageMsg $OPT | 
|---|
| 217 | ;; | 
|---|
| 218 |  | 
|---|
| 219 | :) # Missing argument. | 
|---|
| 220 | syntaxError "missingValue" $usageMsg $OPTARG | 
|---|
| 221 | ;; | 
|---|
| 222 |  | 
|---|
| 223 | *) # Invalid option. | 
|---|
| 224 | syntaxError "invalidOption" $usageMsg $OPTARG | 
|---|
| 225 | ;; | 
|---|
| 226 | esac | 
|---|
| 227 | done  # end of while getopts :t: OPT do | 
|---|
| 228 | shift OPTIND-1 | 
|---|
| 229 |  | 
|---|
| 230 | [[ $# != 0 ]] && syntaxError "extraArg" $usageMsg $1 | 
|---|
| 231 |  | 
|---|
| 232 | # Check the policy file input parameter. | 
|---|
| 233 | if [[ -n $policyFile && $policyFile != DEFAULT ]] | 
|---|
| 234 | then | 
|---|
| 235 | if [[ ! -f "$policyFile" ]] | 
|---|
| 236 | then | 
|---|
| 237 | # Can't read the input file. | 
|---|
| 238 | printErrorMsg 43 $mmcmd "$policyFile" | 
|---|
| 239 | cleanupAndExit | 
|---|
| 240 | fi | 
|---|
| 241 | if [[ ! -s "$policyFile" ]] | 
|---|
| 242 | then | 
|---|
| 243 | # Input file is empty. | 
|---|
| 244 | printErrorMsg 329 $mmcmd "$policyFile" | 
|---|
| 245 | cleanupAndExit | 
|---|
| 246 | fi | 
|---|
| 247 | fi | 
|---|
| 248 |  | 
|---|
| 249 | # Check the working directory input parameter. | 
|---|
| 250 | if [[ -n $workDirectory && $workDirectory != DEFAULT ]] | 
|---|
| 251 | then | 
|---|
| 252 | if [[ ! -d "$workDirectory" ]] | 
|---|
| 253 | then | 
|---|
| 254 | # Directory does not exist. | 
|---|
| 255 | printErrorMsg 574 $mmcmd "$workDirectory" | 
|---|
| 256 | cleanupAndExit | 
|---|
| 257 | fi | 
|---|
| 258 | else | 
|---|
| 259 | workDirectory="/tmp" | 
|---|
| 260 | fi | 
|---|
| 261 |  | 
|---|
| 262 | # Assign a default value for -I. | 
|---|
| 263 | [[ -z $Iarg ]] && Iarg=yes | 
|---|
| 264 |  | 
|---|
| 265 | # Assign a listing level if the user did not specify one. | 
|---|
| 266 | [[ -z $listingLevel ]] && listingLevel=1 | 
|---|
| 267 |  | 
|---|
| 268 |  | 
|---|
| 269 | ##################################################################### | 
|---|
| 270 | # Set up trap exception handling and ensure that the local copy of | 
|---|
| 271 | # the mmsdrfs is up-to-date.  There is no need to lock mmsdrfs file. | 
|---|
| 272 | ##################################################################### | 
|---|
| 273 | trap pretrap2 HUP INT QUIT KILL | 
|---|
| 274 | gpfsInitOutput=$(gpfsInit nolock) | 
|---|
| 275 | setGlobalVar $? $gpfsInitOutput | 
|---|
| 276 |  | 
|---|
| 277 |  | 
|---|
| 278 | ########################################################### | 
|---|
| 279 | # If the target of mmapplypolicy is a directory sub-tree, | 
|---|
| 280 | # find the file system to which this directory belongs. | 
|---|
| 281 | ########################################################### | 
|---|
| 282 | if [[ -n $directory ]] | 
|---|
| 283 | then | 
|---|
| 284 | if [[ ! -d "$directory" ]] | 
|---|
| 285 | then | 
|---|
| 286 | # Directory does not exist. | 
|---|
| 287 | printErrorMsg 574 $mmcmd "$directory" | 
|---|
| 288 | cleanupAndExit | 
|---|
| 289 | fi | 
|---|
| 290 |  | 
|---|
| 291 | device=$(findDeviceName "$directory") | 
|---|
| 292 | [[ $? -ne 0 ]] && cleanupAndExit | 
|---|
| 293 | fi | 
|---|
| 294 |  | 
|---|
| 295 |  | 
|---|
| 296 | ###################################################### | 
|---|
| 297 | # Ensure the file system exists, that it belongs to | 
|---|
| 298 | # this cluster, and that it is mounted on this node. | 
|---|
| 299 | ###################################################### | 
|---|
| 300 | findFSoutput=$(findFS "$device" $mmsdrfsFile) | 
|---|
| 301 | [[ -z $findFSoutput ]] && cleanupAndExit | 
|---|
| 302 |  | 
|---|
| 303 | # Parse the output from the findFS function. | 
|---|
| 304 | set -f ; set -- $findFSoutput ; set +f | 
|---|
| 305 | fqDeviceName=$1 | 
|---|
| 306 | deviceName=$2 | 
|---|
| 307 | fsHomeCluster=$3 | 
|---|
| 308 |  | 
|---|
| 309 | # Exit with a message if the command was invoked for a remote file system. | 
|---|
| 310 | if [[ $fsHomeCluster != $HOME_CLUSTER ]] | 
|---|
| 311 | then | 
|---|
| 312 | # Command is not allowed for remote file systems. | 
|---|
| 313 | printErrorMsg 106 $mmcmd "$device" "$fsHomeCluster" | 
|---|
| 314 | cleanupAndExit | 
|---|
| 315 | fi | 
|---|
| 316 |  | 
|---|
| 317 | # Verify that the file system is mounted. | 
|---|
| 318 | mountPoint=$(findMountpoint "$fqDeviceName") | 
|---|
| 319 | if [[ -z $mountPoint ]] | 
|---|
| 320 | then | 
|---|
| 321 | # The filesystem is not mounted.  Issue an error and quit. | 
|---|
| 322 | printErrorMsg 563 $mmcmd "$device" "$ourNodeName" | 
|---|
| 323 | cleanupAndExit | 
|---|
| 324 | fi | 
|---|
| 325 |  | 
|---|
| 326 |  | 
|---|
| 327 | ############################################### | 
|---|
| 328 | # If a policy file was not specified, retrieve | 
|---|
| 329 | # the current policy file for the file system. | 
|---|
| 330 | ############################################### | 
|---|
| 331 | if [[ -z $policyFile ]] | 
|---|
| 332 | then | 
|---|
| 333 | # Invoke the tslspolicy command. | 
|---|
| 334 | $tslspolicy "$fqDeviceName" -L >$tspolicyFile | 
|---|
| 335 | rc=$(remapRC $?) | 
|---|
| 336 |  | 
|---|
| 337 | if [[ $rc -eq $MM_DaemonDown ]] | 
|---|
| 338 | then | 
|---|
| 339 | # GPFS is down on this node. | 
|---|
| 340 | printErrorMsg 109 $mmcmd | 
|---|
| 341 | elif [[ $rc -eq $MM_QuorumWait ]] | 
|---|
| 342 | then | 
|---|
| 343 | # GPFS is not ready for commands. | 
|---|
| 344 | printErrorMsg 110 $mmcmd | 
|---|
| 345 | elif [[ $rc -eq $MM_ConnectionReset ]] | 
|---|
| 346 | then | 
|---|
| 347 | # An internode connection was reset. | 
|---|
| 348 | printErrorMsg 257 $mmcmd | 
|---|
| 349 | else | 
|---|
| 350 | # Either the command worked, or it is an unexpected error. | 
|---|
| 351 | : # [[ $rc -ne 0 ]] && printErrorMsg 113 "$mmcmd" "tslspolicy" $rc | 
|---|
| 352 | fi  # end of if [[ $rc -eq $MM_DaemonDown ]] | 
|---|
| 353 |  | 
|---|
| 354 | if [[ $rc -ne 0 || ! -s $tspolicyFile ]] | 
|---|
| 355 | then | 
|---|
| 356 | # The policy file cannot be determined. | 
|---|
| 357 | printErrorMsg 456 $mmcmd | 
|---|
| 358 | [[ $rc -eq 0 ]] && rc=1 | 
|---|
| 359 | cleanupAndExit $rc | 
|---|
| 360 | else | 
|---|
| 361 | # The command must have worked. | 
|---|
| 362 | policyFile=$tspolicyFile | 
|---|
| 363 | fi  # end of if [[ $rc -ne 0 || ! -s $tspolicyFile ]] | 
|---|
| 364 | fi  # end of if [[ -z $policyFile ]] | 
|---|
| 365 |  | 
|---|
| 366 |  | 
|---|
| 367 | #################################################################### | 
|---|
| 368 | # Determine the argument list for the tsmigrate command invocation. | 
|---|
| 369 | #################################################################### | 
|---|
| 370 | if [[ -n $directory ]] | 
|---|
| 371 | then | 
|---|
| 372 | argList="$directory -P $policyFile -I $Iarg -L $listingLevel -s $workDirectory" | 
|---|
| 373 | else | 
|---|
| 374 | argList="$fqDeviceName -P $policyFile -I $Iarg -L $listingLevel -s $workDirectory" | 
|---|
| 375 | fi | 
|---|
| 376 | [[ -n $dateTimeString ]] && argList="${argList} -D $dateTimeString" | 
|---|
| 377 |  | 
|---|
| 378 |  | 
|---|
| 379 |  | 
|---|
| 380 | ################################################## | 
|---|
| 381 | # Invoke the tsmigrate command on the local node. | 
|---|
| 382 | ################################################## | 
|---|
| 383 | $tsmigrate $argList | 
|---|
| 384 | rc=$(remapRC $?) | 
|---|
| 385 |  | 
|---|
| 386 | if [[ $rc -eq $MM_DaemonDown ]] | 
|---|
| 387 | then | 
|---|
| 388 | # GPFS is down on this node. | 
|---|
| 389 | printErrorMsg 109 $mmcmd | 
|---|
| 390 | elif [[ $rc -eq $MM_QuorumWait ]] | 
|---|
| 391 | then | 
|---|
| 392 | # GPFS is not ready for commands. | 
|---|
| 393 | printErrorMsg 110 $mmcmd | 
|---|
| 394 | elif [[ $rc -eq $MM_ConnectionReset ]] | 
|---|
| 395 | then | 
|---|
| 396 | # An internode connection was reset. | 
|---|
| 397 | printErrorMsg 257 $mmcmd | 
|---|
| 398 | else | 
|---|
| 399 | # Either the command worked, or it is an unexpected error. | 
|---|
| 400 | : # [[ $rc -ne 0 ]] && printErrorMsg 113 "$mmcmd" "tsmigrate" $rc | 
|---|
| 401 | fi  # end of if [[ $rc -eq $MM_FsNotFound ]] | 
|---|
| 402 |  | 
|---|
| 403 | # if [[ $rc -ne 0 ]] | 
|---|
| 404 | # then | 
|---|
| 405 | #   # tsmigrate failed. | 
|---|
| 406 | #   printErrorMsg 104 $mmcmd tsmigrate | 
|---|
| 407 | #   cleanupAndExit $rc | 
|---|
| 408 | # fi | 
|---|
| 409 |  | 
|---|
| 410 | cleanupAndExit $rc | 
|---|
| 411 |  | 
|---|