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