1 #!/bin/ksh -p 2 # 3 # CDDL HEADER START 4 # 5 # The contents of this file are subject to the terms of the 6 # Common Development and Distribution License (the "License"). 7 # You may not use this file except in compliance with the License. 8 # 9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 # or http://www.opensolaris.org/os/licensing. 11 # See the License for the specific language governing permissions 12 # and limitations under the License. 13 # 14 # When distributing Covered Code, include this CDDL HEADER in each 15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 # If applicable, add the following below this CDDL HEADER, with the 17 # fields enclosed by brackets "[]" replaced with your own identifying 18 # information: Portions Copyright [yyyy] [name of copyright owner] 19 # 20 # CDDL HEADER END 21 # 22 # Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 # Use is subject to license terms. 24 # 25 26 # NOTE: this script runs in the global zone and touches the non-global 27 # zone, so care should be taken to validate any modifications so that they 28 # are safe. 29 30 . /usr/lib/brand/shared/common.ksh 31 32 LOGFILE= 33 MSG_PREFIX="p2v: " 34 EXIT_CODE=1 35 36 usage() 37 { 38 echo "$0 [-s] [-m msgprefix] [-u] [-v] [-b patchid]* zonename" >&2 39 exit $EXIT_CODE 40 } 41 42 # Clean up on interrupt 43 trap_cleanup() 44 { 45 msg=$(gettext "Postprocessing cancelled due to interrupt.") 46 error "$msg" 47 48 if (( $zone_is_running != 0 )); then 49 error "$e_shutdown" "$ZONENAME" 50 /usr/sbin/zoneadm -z $ZONENAME halt 51 fi 52 53 exit $EXIT_CODE 54 } 55 56 # 57 # For an exclusive stack zone, fix up the network configuration files. 58 # We need to do this even if unconfiguring the zone so sys-unconfig works 59 # correctly. 60 # 61 fix_net() 62 { 63 [[ "$STACK_TYPE" == "shared" ]] && return 64 65 NETIF_CNT=$(/usr/bin/ls $ZONEROOT/etc/hostname.* 2>/dev/null | \ 66 /usr/bin/wc -l) 67 if (( $NETIF_CNT != 1 )); then 68 vlog "$v_nonetfix" 69 return 70 fi 71 72 NET=$(LC_ALL=C /usr/sbin/zonecfg -z $ZONENAME info net) 73 if (( $? != 0 )); then 74 error "$e_badinfo" "net" 75 return 76 fi 77 78 NETIF=$(echo $NET | /usr/bin/nawk '{ 79 for (i = 1; i < NF; i++) { 80 if ($i == "physical:") { 81 if (length(net) == 0) { 82 i++ 83 net = $i 84 } else { 85 multiple=1 86 } 87 } 88 } 89 } 90 END { if (!multiple) 91 print net 92 }') 93 94 if [[ -z "$NETIF" ]]; then 95 vlog "$v_nonetfix" 96 return 97 fi 98 99 OLD_HOSTNET=$(/usr/bin/ls $ZONEROOT/etc/hostname.*) 100 if [[ "$OLD_HOSTNET" != "$ZONEROOT/etc/hostname.$NETIF" ]]; then 101 safe_move $OLD_HOSTNET $ZONEROOT/etc/hostname.$NETIF 102 fi 103 } 104 105 # 106 # Disable all of the shares since the zone cannot be an NFS server. 107 # Note that we disable the various instances of the svc:/network/shares/group 108 # SMF service in the fix_smf function. 109 # 110 fix_nfs() 111 { 112 zonedfs=$ZONEROOT/etc/dfs 113 114 if [[ -h $zonedfs/dfstab || ! -f $zonedfs/dfstab ]]; then 115 error "$e_badfile" "/etc/dfs/dfstab" 116 return 117 fi 118 119 tmpfile=$(/usr/bin/mktemp -t -p /var/tmp) 120 if [[ -z "$tmpfile" ]]; then 121 error "$e_tmpfile" 122 return 123 fi 124 125 /usr/bin/nawk '{ 126 if (substr($1, 0, 1) == "#") { 127 print $0 128 } else { 129 print "#", $0 130 modified=1 131 } 132 } 133 END { 134 if (modified == 1) { 135 printf("# Modified by p2v ") 136 system("/usr/bin/date") 137 exit 0 138 } 139 exit 1 140 }' $zonedfs/dfstab >>$tmpfile 141 142 if (( $? == 0 )); then 143 if [[ ! -f $zonedfs/dfstab.pre_p2v ]]; then 144 safe_copy $zonedfs/dfstab $zonedfs/dfstab.pre_p2v 145 fi 146 safe_copy $tmpfile $zonedfs/dfstab 147 fi 148 /usr/bin/rm -f $tmpfile 149 } 150 151 # 152 # Comment out most of the old mounts since they are either unneeded or 153 # likely incorrect within a zone. Specific mounts can be manually 154 # reenabled if the corresponding device is added to the zone. 155 # 156 fix_vfstab() 157 { 158 if [[ -h $ZONEROOT/etc/vfstab || ! -f $ZONEROOT/etc/vfstab ]]; then 159 error "$e_badfile" "/etc/vfstab" 160 return 161 fi 162 163 tmpfile=$(/usr/bin/mktemp -t -p /var/tmp) 164 if [[ -z "$tmpfile" ]]; then 165 error "$e_tmpfile" 166 return 167 fi 168 169 /usr/bin/nawk '{ 170 if (substr($1, 0, 1) == "#") { 171 print $0 172 } else if ($1 == "fd" || $1 == "/proc" || $1 == "swap" || 173 $1 == "ctfs" || $1 == "objfs" || $1 == "sharefs" || 174 $4 == "nfs" || $4 == "lofs") { 175 print $0 176 } else { 177 print "#", $0 178 modified=1 179 } 180 } 181 END { 182 if (modified == 1) { 183 printf("# Modified by p2v ") 184 system("/usr/bin/date") 185 exit 0 186 } 187 exit 1 188 }' $ZONEROOT/etc/vfstab >>$tmpfile 189 190 if (( $? == 0 )); then 191 if [[ ! -f $ZONEROOT/etc/vfstab.pre_p2v ]]; then 192 safe_copy $ZONEROOT/etc/vfstab \ 193 $ZONEROOT/etc/vfstab.pre_p2v 194 fi 195 safe_copy $tmpfile $ZONEROOT/etc/vfstab 196 fi 197 /usr/bin/rm -f $tmpfile 198 } 199 200 # 201 # Collect the data needed to delete or disable SMF services before we run 202 # the 'update on attach'. For a normal zone migration, 'update on attach' 203 # will fix things up, but since we're p2v-ing a physical image there are 204 # SMF services which UoA can't handle (since those services aren't enabled 205 # in a simple zone image. 206 # 207 fix_smf_pre_uoa() 208 { 209 # 210 # Start by getting the svc manifests that are delivered by hollow 211 # pkgs then use 'svccfg inventory' to get the names of the svcs 212 # delivered by those manifests. The svc names are saved into a 213 # temporary file. 214 # 215 216 SMFTMPFILE=$(/usr/bin/mktemp -t -p /var/tmp smf.XXXXXX) 217 if [[ -z "$SMFTMPFILE" ]]; then 218 error "$e_tmpfile" 219 return 220 fi 221 222 for i in $ZONEROOT/var/sadm/pkg/* 223 do 224 pkg=$(/usr/bin/basename $i) 225 [[ ! -f $ZONEROOT/var/sadm/pkg/$pkg/save/pspool/$pkg/pkgmap ]] \ 226 && continue 227 228 /usr/bin/egrep -s "SUNW_PKG_HOLLOW=true" \ 229 $ZONEROOT/var/sadm/pkg/$pkg/pkginfo || continue 230 231 for j in $(/usr/bin/nawk '{if ($2 == "f" && 232 substr($4, 1, 17) == "var/svc/manifest/") print $4}' \ 233 $ZONEROOT/var/sadm/pkg/$pkg/save/pspool/$pkg/pkgmap) 234 do 235 svcs=$(SVCCFG_NOVALIDATE=1 \ 236 SVCCFG_REPOSITORY=$ZONEROOT/etc/svc/repository.db \ 237 /usr/sbin/svccfg inventory $ZONEROOT/$j) 238 for k in $svcs 239 do 240 echo $k /$j >> $SMFTMPFILE 241 done 242 done 243 done 244 } 245 246 # 247 # Delete or disable SMF services. 248 # Zone is booted to milestone=none when this function is called. 249 # Use the SMF data collected by fix_smf_pre_uoa() to delete the services. 250 # 251 fix_smf() 252 { 253 # 254 # Zone was already booted to milestone=none, wait until SMF door exists. 255 # 256 for i in 0 1 2 3 4 5 6 7 8 9 257 do 258 [[ -r $ZONEROOT/etc/svc/volatile/repository_door ]] && break 259 sleep 5 260 done 261 262 if [[ $i -eq 9 && ! -r $ZONEROOT/etc/svc/volatile/repository_door ]]; 263 then 264 error "$e_nosmf" 265 /usr/bin/rm -f $SMFTMPFILE 266 return 267 fi 268 269 insttmpfile=$(/usr/bin/mktemp -t -p /var/tmp instsmf.XXXXXX) 270 if [[ -z "$insttmpfile" ]]; then 271 error "$e_tmpfile" 272 /usr/bin/rm -f $SMFTMPFILE 273 return 274 fi 275 276 vlog "$v_rmhollowsvcs" 277 while read fmri mfst 278 do 279 # Delete the svc. 280 vlog "$v_delsvc" "$fmri" 281 echo "/usr/sbin/svccfg delete -f $fmri" 282 echo "/usr/sbin/svccfg delhash -d $mfst" 283 echo "rm -f $mfst" 284 done < $SMFTMPFILE > $ZONEROOT/tmp/smf_rm 285 286 /usr/sbin/zlogin -S $ZONENAME /bin/sh /tmp/smf_rm >/dev/null 2>&1 287 288 /usr/bin/rm -f $SMFTMPFILE 289 290 # Get a list of the svcs that now exist in the zone. 291 /usr/sbin/zlogin -S $ZONENAME /usr/bin/svcs -aH | \ 292 /usr/bin/nawk '{print $3}' >>$insttmpfile 293 294 [[ -n $LOGFILE ]] && \ 295 printf "[$(date)] ${MSG_PREFIX}${v_svcsinzone}\n" >&2 296 [[ -n $LOGFILE ]] && cat $insttmpfile >&2 297 298 # 299 # Fix network services if shared stack. 300 # 301 if [[ "$STACK_TYPE" == "shared" ]]; then 302 vlog "$v_fixnetsvcs" 303 304 NETPHYSDEF="svc:/network/physical:default" 305 NETPHYSNWAM="svc:/network/physical:nwam" 306 307 /usr/bin/egrep -s "$NETPHYSDEF" $insttmpfile 308 if (( $? == 0 )); then 309 vlog "$v_enblsvc" "$NETPHYSDEF" 310 /usr/sbin/zlogin -S $ZONENAME \ 311 /usr/sbin/svcadm enable $NETPHYSDEF || \ 312 error "$e_dissvc" "$NETPHYSDEF" 313 fi 314 315 /usr/bin/egrep -s "$NETPHYSNWAM" $insttmpfile 316 if (( $? == 0 )); then 317 vlog "$v_dissvc" "$NETPHYSNWAM" 318 /usr/sbin/zlogin -S $ZONENAME \ 319 /usr/sbin/svcadm disable $NETPHYSNWAM || \ 320 error "$e_enblsvc" "$NETPHYSNWAM" 321 fi 322 323 for i in $(/usr/bin/egrep network/routing $insttmpfile) 324 do 325 # Disable the svc. 326 vlog "$v_dissvc" "$i" 327 /usr/sbin/zlogin -S $ZONENAME \ 328 /usr/sbin/svcadm disable $i || \ 329 error "$e_dissvc" $i 330 done 331 fi 332 333 # 334 # Disable well-known services that don't run in a zone. 335 # 336 vlog "$v_rminvalidsvcs" 337 for i in $(/usr/bin/egrep -hv "^#" \ 338 /usr/lib/brand/native/smf_disable.lst \ 339 /etc/brand/native/smf_disable.conf) 340 do 341 # Skip svcs not installed in the zone. 342 /usr/bin/egrep -s "$i:" $insttmpfile || continue 343 344 # Disable the svc. 345 vlog "$v_dissvc" "$i" 346 /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svcadm disable $i || \ 347 error "$e_dissvc" $i 348 done 349 350 # 351 # Since zones can't be NFS servers, disable all of the instances of 352 # the shares svc. 353 # 354 for i in $(/usr/bin/egrep network/shares/group $insttmpfile) 355 do 356 vlog "$v_dissvc" "$i" 357 /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svcadm disable $i || \ 358 error "$e_dissvc" $i 359 done 360 361 /usr/bin/rm -f $insttmpfile 362 } 363 364 # 365 # Remove well-known pkgs that do not work inside a zone. 366 # 367 rm_pkgs() 368 { 369 /usr/bin/cat <<-EOF > $ZONEROOT/tmp/admin || fatal "$e_adminf" 370 mail= 371 instance=overwrite 372 partial=nocheck 373 runlevel=nocheck 374 idepend=nocheck 375 rdepend=nocheck 376 space=nocheck 377 setuid=nocheck 378 conflict=nocheck 379 action=nocheck 380 basedir=default 381 EOF 382 383 for i in $(/usr/bin/egrep -hv "^#" /usr/lib/brand/native/pkgrm.lst \ 384 /etc/brand/native/pkgrm.conf) 385 do 386 [[ ! -d $ZONEROOT/var/sadm/pkg/$i ]] && continue 387 388 vlog "$v_rmpkg" "$i" 389 /usr/sbin/zlogin -S $ZONENAME \ 390 /usr/sbin/pkgrm -na /tmp/admin $i >&2 || error "$e_rmpkg" $i 391 done 392 } 393 394 # 395 # Zoneadmd writes a one-line index file into the zone when the zone boots, 396 # so any information about installed zones from the original system will 397 # be lost at that time. Here we'll warn the sysadmin about any pre-existing 398 # zones that they might want to clean up by hand, but we'll leave the zonepaths 399 # in place in case they're on shared storage and will be migrated to 400 # a new host. 401 # 402 warn_zones() 403 { 404 zoneconfig=$ZONEROOT/etc/zones 405 406 if [[ -h $zoneconfig/index || ! -f $zoneconfig/index ]]; then 407 error "$e_badfile" "/etc/zones/index" 408 return 409 fi 410 411 NGZ=$(/usr/bin/nawk -F: '{ 412 if (substr($1, 0, 1) == "#" || $1 == "global") 413 continue 414 415 if ($2 == "installed") 416 printf("%s ", $1) 417 }' $zoneconfig/index) 418 419 # Return if there are no installed zones to warn about. 420 [[ -z "$NGZ" ]] && return 421 422 log "$v_rmzones" "$NGZ" 423 424 NGZP=$(/usr/bin/nawk -F: '{ 425 if (substr($1, 0, 1) == "#" || $1 == "global") 426 continue 427 428 if ($2 == "installed") 429 printf("%s ", $3) 430 }' $zoneconfig/index) 431 432 log "$v_rmzonepaths" 433 434 for i in $NGZP 435 do 436 log " %s" "$i" 437 done 438 } 439 440 unset LD_LIBRARY_PATH 441 PATH=/usr/sbin:/usr/bin 442 export PATH 443 444 # 445 # ^C Should cleanup; if the zone is running, it should try to halt it. 446 # 447 zone_is_running=0 448 trap trap_cleanup INT 449 450 # 451 # Parse the command line options. 452 # 453 unset backout 454 OPT_U= 455 OPT_V= 456 OPT_M= 457 OPT_L= 458 while getopts "b:uvm:l:" opt 459 do 460 case "$opt" in 461 b) if [[ -n "$backout" ]]; then 462 backout="$backout -b $OPTARG" 463 else 464 backout="-b $OPTARG" 465 fi 466 ;; 467 u) OPT_U="-u";; 468 v) OPT_V="-v";; 469 m) MSG_PREFIX="$OPTARG"; OPT_M="-m \"$OPTARG\"";; 470 l) LOGFILE="$OPTARG"; OPT_L="-l \"$OPTARG\"";; 471 *) usage;; 472 esac 473 done 474 shift OPTIND-1 475 476 (( $# < 1 )) && usage 477 478 (( $# > 2 )) && usage 479 480 [[ -n $LOGFILE ]] && exec 2>>$LOGFILE 481 482 ZONENAME=$1 483 ZONEPATH=$2 484 ZONEROOT=$ZONEPATH/root 485 486 e_badinfo=$(gettext "Failed to get '%s' zone resource") 487 e_badfile=$(gettext "Invalid '%s' file within the zone") 488 e_tmpfile=$(gettext "Unable to create temporary file") 489 v_mkdirs=$(gettext "Creating mount points") 490 v_nonetfix=$(gettext "Cannot update /etc/hostname.{net} file") 491 v_update=$(gettext "Updating the zone software to match the global zone...") 492 v_updatedone=$(gettext "Zone software update complete") 493 e_badupdate=$(gettext "Updating the Zone software failed") 494 v_adjust=$(gettext "Updating the image to run within a zone") 495 v_stacktype=$(gettext "Stack type '%s'") 496 v_booting=$(gettext "Booting zone to single user mode") 497 e_badboot=$(gettext "Zone boot failed") 498 e_nosmf=$(gettext "ERROR: SMF repository unavailable.") 499 e_nosingleuser=$(gettext "ERROR: zone did not finish booting to single-user.") 500 v_svcsinzone=$(gettext "The following SMF services are installed:") 501 v_rmhollowsvcs=$(gettext "Deleting SMF services from hollow packages") 502 v_fixnetsvcs=$(gettext "Adjusting network SMF services") 503 v_rminvalidsvcs=$(gettext "Disabling invalid SMF services") 504 v_delsvc=$(gettext "Delete SMF svc '%s'") 505 e_delsvc=$(gettext "deleting SMF svc '%s'") 506 v_enblsvc=$(gettext "Enable SMF svc '%s'") 507 e_enblsvc=$(gettext "enabling SMF svc '%s'") 508 v_dissvc=$(gettext "Disable SMF svc '%s'") 509 e_dissvc=$(gettext "disabling SMF svc '%s'") 510 e_adminf=$(gettext "Unable to create admin file") 511 v_rmpkg=$(gettext "Remove package '%s'") 512 e_rmpkg=$(gettext "removing package '%s'") 513 v_rmzones=$(gettext "The following zones in this image will be unusable: %s") 514 v_rmzonepaths=$(gettext "These zonepaths could be removed from this image:") 515 v_unconfig=$(gettext "Performing zone sys-unconfig") 516 e_unconfig=$(gettext "sys-unconfig failed") 517 v_halting=$(gettext "Halting zone") 518 e_shutdown=$(gettext "Shutting down zone %s...") 519 e_badhalt=$(gettext "Zone halt failed") 520 v_exitgood=$(gettext "Postprocessing successful.") 521 e_exitfail=$(gettext "Postprocessing failed.") 522 523 # 524 # Do some validation on the paths we'll be accessing 525 # 526 safe_dir etc 527 safe_dir etc/dfs 528 safe_dir etc/zones 529 safe_dir var 530 531 # Now do the work to update the zone. 532 533 # Before booting the zone we may need to create a few mnt points, just in 534 # case they don't exist for some reason. 535 # 536 # Whenever we reach into the zone while running in the global zone we 537 # need to validate that none of the interim directories are symlinks 538 # that could cause us to inadvertently modify the global zone. 539 vlog "$v_mkdirs" 540 if [[ ! -f $ZONEROOT/tmp && ! -d $ZONEROOT/tmp ]]; then 541 mkdir -m 1777 -p $ZONEROOT/tmp || exit $EXIT_CODE 542 fi 543 if [[ ! -f $ZONEROOT/var/run && ! -d $ZONEROOT/var/run ]]; then 544 mkdir -m 1755 -p $ZONEROOT/var/run || exit $EXIT_CODE 545 fi 546 if [[ ! -f $ZONEROOT/var/tmp && ! -d $ZONEROOT/var/tmp ]]; then 547 mkdir -m 1777 -p $ZONEROOT/var/tmp || exit $EXIT_CODE 548 fi 549 if [[ ! -h $ZONEROOT/etc && ! -f $ZONEROOT/etc/mnttab ]]; then 550 /usr/bin/touch $ZONEROOT/etc/mnttab || exit $EXIT_CODE 551 /usr/bin/chmod 444 $ZONEROOT/etc/mnttab || exit $EXIT_CODE 552 fi 553 if [[ ! -f $ZONEROOT/proc && ! -d $ZONEROOT/proc ]]; then 554 mkdir -m 755 -p $ZONEROOT/proc || exit $EXIT_CODE 555 fi 556 if [[ ! -f $ZONEROOT/dev && ! -d $ZONEROOT/dev ]]; then 557 mkdir -m 755 -p $ZONEROOT/dev || exit $EXIT_CODE 558 fi 559 if [[ ! -h $ZONEROOT/etc && ! -h $ZONEROOT/etc/svc && ! -d $ZONEROOT/etc/svc ]] 560 then 561 mkdir -m 755 -p $ZONEROOT/etc/svc/volatile || exit $EXIT_CODE 562 fi 563 564 # Check for zones inside of image. 565 warn_zones 566 fix_smf_pre_uoa 567 568 # 569 # Run update on attach. State is currently 'incomplete' so use the private 570 # force-update option. 571 # 572 log "$v_update" 573 /usr/sbin/zoneadm -z $ZONENAME attach -U $backout >&2 574 if (( $? != 0 )); then 575 /usr/bin/rm -f $SMFTMPFILE 576 fatal "$e_badupdate" 577 else 578 log "$v_updatedone" 579 fi 580 581 log "$v_adjust" 582 583 # 584 # Any errors in these functions are not considered fatal. The zone can be 585 # be fixed up manually afterwards and it may need some additional manual 586 # cleanup in any case. 587 # 588 589 STACK_TYPE=$(/usr/sbin/zoneadm -z $ZONENAME list -p | \ 590 /usr/bin/nawk -F: '{print $7}') 591 if (( $? != 0 )); then 592 error "$e_badinfo" "stacktype" 593 fi 594 vlog "$v_stacktype" "$STACK_TYPE" 595 596 fix_net 597 fix_nfs 598 fix_vfstab 599 600 vlog "$v_booting" 601 602 # 603 # Boot the zone so that we can do all of the SMF updates needed on the zone's 604 # repository. 605 # 606 607 zone_is_running=1 608 609 # The 'update on attach' left the zone installed. 610 /usr/sbin/zoneadm -z $ZONENAME boot -f -- -m milestone=none 611 if (( $? != 0 )); then 612 error "$e_badboot" 613 /usr/bin/rm -f $SMFTMPFILE 614 fatal "$e_exitfail" 615 fi 616 617 # cleanup SMF services 618 fix_smf 619 620 # remove invalid pkgs 621 rm_pkgs 622 623 vlog "$v_halting" 624 /usr/sbin/zoneadm -z $ZONENAME halt 625 if (( $? != 0 )); then 626 error "$e_badhalt" 627 failed=1 628 fi 629 zone_is_running=0 630 631 if [[ -z $failed && -n $OPT_U ]]; then 632 # 633 # We're sys-unconfiging the zone. This will halt the zone, however 634 # there are problems with sys-unconfig and it usually hangs when the 635 # zone is booted to milestone=none. This is why we previously halted 636 # the zone. We now boot to milestone=single-user. Again, the 637 # sys-unconfig can hang if the zone is still in the process of 638 # booting when we try to run sys-unconfig. Wait until the boot is 639 # done, which we do by checking for sulogin, or waiting 30 seconds, 640 # whichever comes first. 641 # 642 643 vlog "$v_unconfig" 644 645 zone_is_running=1 646 /usr/sbin/zoneadm -z $ZONENAME boot -- -m milestone=single-user 647 if (( $? != 0 )); then 648 error "$e_badboot" 649 fatal "$e_exitfail" 650 fi 651 652 for i in 0 1 2 3 4 5 6 7 8 9 653 do 654 sleep 10 655 /usr/sbin/zlogin $ZONENAME \ 656 /usr/bin/svcs -H svc:/milestone/single-user:default 2>&1 | 657 /usr/bin/nawk '{ 658 if ($1 == "online") 659 exit 0 660 else 661 exit 1 662 }' && break 663 done 664 665 if (( $i == 9 )); then 666 vlog "$e_nosingleuser" 667 fi 668 669 echo "yes" | /usr/sbin/zlogin -S $ZONENAME \ 670 /usr/sbin/sys-unconfig >/dev/null 2>&1 671 if (( $? != 0 )); then 672 error "$e_unconfig" 673 failed=1 674 fi 675 fi 676 677 678 if [[ -n $failed ]]; then 679 fatal "$e_exitfail" 680 fi 681 682 vlog "$v_exitgood" 683 exit 0 684