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 # Delete or disable SMF services.
202 # Zone is booted to milestone=none when this function is called.
203 #
204 fix_smf()
205 {
206 #
207 # Delete services that are delivered in hollow pkgs.
208 #
209 # Start by getting the svc manifests that are delivered by hollow
210 # pkgs then use 'svccfg inventory' to get the names of the svcs
211 # delivered by those manifests. The svc names are saved into a
212 # temporary file. We then login to the zone and delete them from SMF
213 # so that the various dependencies also get cleaned up properly.
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 /var/sadm/pkg/*
223 do
224 pkg=$(/usr/bin/basename $i)
225 [[ ! -f /var/sadm/pkg/$pkg/save/pspool/$pkg/pkgmap ]] && \
226 continue
227
228 manifests=$(/usr/bin/nawk '{if ($2 == "f" &&
229 substr($4, 1, 17) == "var/svc/manifest/") print $4}' \
230 /var/sadm/pkg/$pkg/save/pspool/$pkg/pkgmap)
231
232 if [[ -n "$manifests" ]]; then
233 /usr/bin/egrep -s "SUNW_PKG_HOLLOW=true" \
234 /var/sadm/pkg/$pkg/pkginfo || continue
235
236 for j in $manifests
237 do
238 svcs=$(SVCCFG_NOVALIDATE=1 /usr/sbin/svccfg \
239 inventory /$j)
240 for k in $svcs
241 do
242 case $k in
243 *:default)
244 # ignore default instance
245 ;;
246 *)
247 echo $k >> $smftmpfile
248 ;;
249 esac
250 done
251 done
252 fi
253 done
254
255 #
256 # Zone was already booted to milestone=none, wait until SMF door exists.
257 #
258 for i in 0 1 2 3 4 5 6 7 8 9
259 do
260 [[ -r $ZONEROOT/etc/svc/volatile/repository_door ]] && break
261 sleep 5
262 done
263
264 if [[ $i -eq 9 && ! -r $ZONEROOT/etc/svc/volatile/repository_door ]];
265 then
266 error "$e_nosmf"
267 /usr/bin/rm -f $smftmpfile
268 return
269 fi
270
271 insttmpfile=$(/usr/bin/mktemp -t -p /var/tmp instsmf.XXXXXX)
272 if [[ -z "$insttmpfile" ]]; then
273 error "$e_tmpfile"
274 /usr/bin/rm -f $smftmpfile
275 return
276 fi
277
278 # Get a list of the svcs that exist in the zone.
279 /usr/sbin/zlogin -S $ZONENAME /usr/bin/svcs -aH | \
280 /usr/bin/nawk '{print $3}' >>$insttmpfile
281
282 [[ -n $LOGFILE ]] && \
283 printf "[$(date)] ${MSG_PREFIX}${v_svcsinzone}\n" >&2
284 [[ -n $LOGFILE ]] && cat $insttmpfile >&2
285
286 vlog "$v_rmhollowsvcs"
287 for i in $(cat $smftmpfile)
288 do
289 # Skip svcs not installed in the zone.
290 /usr/bin/egrep -s "$i:" $insttmpfile || continue
291
292 # Delete the svc.
293 vlog "$v_delsvc" "$i"
294 /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svccfg delete $i >&2 \
295 || error "$e_delsvc" $i
296 done
297
298 /usr/bin/rm -f $smftmpfile
299
300 #
301 # Fix network services if shared stack.
302 #
303 if [[ "$STACK_TYPE" == "shared" ]]; then
304 vlog "$v_fixnetsvcs"
305
306 NETPHYSDEF="svc:/network/physical:default"
307 NETPHYSNWAM="svc:/network/physical:nwam"
308
309 /usr/bin/egrep -s "$NETPHYSDEF" $insttmpfile
310 if (( $? == 0 )); then
311 vlog "$v_enblsvc" "$NETPHYSDEF"
312 /usr/sbin/zlogin -S $ZONENAME \
313 /usr/sbin/svcadm enable $NETPHYSDEF || \
314 error "$e_dissvc" "$NETPHYSDEF"
315 fi
316
317 /usr/bin/egrep -s "$NETPHYSNWAM" $insttmpfile
318 if (( $? == 0 )); then
319 vlog "$v_dissvc" "$NETPHYSNWAM"
320 /usr/sbin/zlogin -S $ZONENAME \
321 /usr/sbin/svcadm disable $NETPHYSNWAM || \
322 error "$e_enblsvc" "$NETPHYSNWAM"
323 fi
324
325 for i in $(/usr/bin/egrep network/routing $insttmpfile)
326 do
327 # Disable the svc.
328 vlog "$v_dissvc" "$i"
329 /usr/sbin/zlogin -S $ZONENAME \
330 /usr/sbin/svcadm disable $i || \
331 error "$e_dissvc" $i
332 done
333 fi
334
335 #
336 # Disable well-known services that don't run in a zone.
337 #
338 vlog "$v_rminvalidsvcs"
339 for i in $(/usr/bin/egrep -hv "^#" \
340 /usr/lib/brand/native/smf_disable.lst \
341 /etc/brand/native/smf_disable.conf)
342 do
343 # Skip svcs not installed in the zone.
344 /usr/bin/egrep -s "$i:" $insttmpfile || continue
345
346 # Disable the svc.
347 vlog "$v_dissvc" "$i"
348 /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svcadm disable $i || \
349 error "$e_dissvc" $i
350 done
351
352 #
353 # Since zones can't be NFS servers, disable all of the instances of
354 # the shares svc.
355 #
356 for i in $(/usr/bin/egrep network/shares/group $insttmpfile)
357 do
358 vlog "$v_dissvc" "$i"
359 /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svcadm disable $i || \
360 error "$e_dissvc" $i
361 done
362
363 /usr/bin/rm -f $insttmpfile
364 }
365
366 #
367 # Remove well-known pkgs that do not work inside a zone.
368 #
369 rm_pkgs()
370 {
371 /usr/bin/cat <<-EOF > $ZONEROOT/tmp/admin || fatal "$e_adminf"
372 mail=
373 instance=overwrite
374 partial=nocheck
375 runlevel=nocheck
376 idepend=nocheck
377 rdepend=nocheck
378 space=nocheck
379 setuid=nocheck
380 conflict=nocheck
381 action=nocheck
382 basedir=default
383 EOF
384
385 for i in $(/usr/bin/egrep -hv "^#" /usr/lib/brand/native/pkgrm.lst \
386 /etc/brand/native/pkgrm.conf)
387 do
388 [[ ! -d $ZONEROOT/var/sadm/pkg/$i ]] && continue
389
390 vlog "$v_rmpkg" "$i"
391 /usr/sbin/zlogin -S $ZONENAME \
392 /usr/sbin/pkgrm -na /tmp/admin $i >&2 || error "$e_rmpkg" $i
393 done
394 }
395
396 #
397 # Zoneadmd writes a one-line index file into the zone when the zone boots,
398 # so any information about installed zones from the original system will
399 # be lost at that time. Here we'll warn the sysadmin about any pre-existing
400 # zones that they might want to clean up by hand, but we'll leave the zonepaths
401 # in place in case they're on shared storage and will be migrated to
402 # a new host.
403 #
404 warn_zones()
405 {
406 zoneconfig=$ZONEROOT/etc/zones
407
408 if [[ -h $zoneconfig/index || ! -f $zoneconfig/index ]]; then
409 error "$e_badfile" "/etc/zones/index"
410 return
411 fi
412
413 NGZ=$(/usr/bin/nawk -F: '{
414 if (substr($1, 0, 1) == "#" || $1 == "global")
415 continue
416
417 if ($2 == "installed")
418 printf("%s ", $1)
419 }' $zoneconfig/index)
420
421 # Return if there are no installed zones to warn about.
422 [[ -z "$NGZ" ]] && return
423
424 log "$v_rmzones" "$NGZ"
425
426 NGZP=$(/usr/bin/nawk -F: '{
427 if (substr($1, 0, 1) == "#" || $1 == "global")
428 continue
429
430 if ($2 == "installed")
431 printf("%s ", $3)
432 }' $zoneconfig/index)
433
434 log "$v_rmzonepaths"
435
436 for i in $NGZP
437 do
438 log " %s" "$i"
439 done
440 }
441
442 unset LD_LIBRARY_PATH
443 PATH=/usr/sbin:/usr/bin
444 export PATH
445
446 #
447 # ^C Should cleanup; if the zone is running, it should try to halt it.
448 #
449 zone_is_running=0
450 trap trap_cleanup INT
451
452 #
453 # Parse the command line options.
454 #
455 unset backout
456 OPT_U=
457 OPT_V=
458 OPT_M=
459 OPT_L=
460 while getopts "b:uvm:l:" opt
461 do
462 case "$opt" in
463 b) if [[ -n "$backout" ]]; then
464 backout="$backout -b $OPTARG"
465 else
466 backout="-b $OPTARG"
467 fi
468 ;;
469 u) OPT_U="-u";;
470 v) OPT_V="-v";;
471 m) MSG_PREFIX="$OPTARG"; OPT_M="-m \"$OPTARG\"";;
472 l) LOGFILE="$OPTARG"; OPT_L="-l \"$OPTARG\"";;
473 *) usage;;
474 esac
475 done
476 shift OPTIND-1
477
478 (( $# < 1 )) && usage
479
480 (( $# > 2 )) && usage
481
482 [[ -n $LOGFILE ]] && exec 2>>$LOGFILE
483
484 ZONENAME=$1
485 ZONEPATH=$2
486 ZONEROOT=$ZONEPATH/root
487
488 e_badinfo=$(gettext "Failed to get '%s' zone resource")
489 e_badfile=$(gettext "Invalid '%s' file within the zone")
490 e_tmpfile=$(gettext "Unable to create temporary file")
491 v_mkdirs=$(gettext "Creating mount points")
492 v_nonetfix=$(gettext "Cannot update /etc/hostname.{net} file")
493 v_update=$(gettext "Updating the zone software to match the global zone...")
494 v_updatedone=$(gettext "Zone software update complete")
495 e_badupdate=$(gettext "Updating the Zone software failed")
496 v_adjust=$(gettext "Updating the image to run within a zone")
497 v_stacktype=$(gettext "Stack type '%s'")
498 v_booting=$(gettext "Booting zone to single user mode")
499 e_badboot=$(gettext "Zone boot failed")
500 e_nosmf=$(gettext "ERROR: SMF repository unavailable.")
501 e_nosingleuser=$(gettext "ERROR: zone did not finish booting to single-user.")
502 v_svcsinzone=$(gettext "The following SMF services are installed:")
503 v_rmhollowsvcs=$(gettext "Deleting SMF services from hollow packages")
504 v_fixnetsvcs=$(gettext "Adjusting network SMF services")
505 v_rminvalidsvcs=$(gettext "Disabling invalid SMF services")
506 v_delsvc=$(gettext "Delete SMF svc '%s'")
507 e_delsvc=$(gettext "deleting SMF svc '%s'")
508 v_enblsvc=$(gettext "Enable SMF svc '%s'")
509 e_enblsvc=$(gettext "enabling SMF svc '%s'")
510 v_dissvc=$(gettext "Disable SMF svc '%s'")
511 e_dissvc=$(gettext "disabling SMF svc '%s'")
512 e_adminf=$(gettext "Unable to create admin file")
513 v_rmpkg=$(gettext "Remove package '%s'")
514 e_rmpkg=$(gettext "removing package '%s'")
515 v_rmzones=$(gettext "The following zones in this image will be unusable: %s")
516 v_rmzonepaths=$(gettext "These zonepaths could be removed from this image:")
517 v_unconfig=$(gettext "Performing zone sys-unconfig")
518 e_unconfig=$(gettext "sys-unconfig failed")
519 v_halting=$(gettext "Halting zone")
520 e_shutdown=$(gettext "Shutting down zone %s...")
521 e_badhalt=$(gettext "Zone halt failed")
522 v_exitgood=$(gettext "Postprocessing successful.")
523 e_exitfail=$(gettext "Postprocessing failed.")
524
525 #
526 # Do some validation on the paths we'll be accessing
527 #
528 safe_dir etc
529 safe_dir etc/dfs
530 safe_dir etc/zones
531 safe_dir var
532
533 # Now do the work to update the zone.
534
535 # Before booting the zone we may need to create a few mnt points, just in
536 # case they don't exist for some reason.
537 #
538 # Whenever we reach into the zone while running in the global zone we
539 # need to validate that none of the interim directories are symlinks
540 # that could cause us to inadvertently modify the global zone.
541 vlog "$v_mkdirs"
542 if [[ ! -f $ZONEROOT/tmp && ! -d $ZONEROOT/tmp ]]; then
543 mkdir -m 1777 -p $ZONEROOT/tmp || exit $EXIT_CODE
544 fi
545 if [[ ! -f $ZONEROOT/var/run && ! -d $ZONEROOT/var/run ]]; then
546 mkdir -m 1755 -p $ZONEROOT/var/run || exit $EXIT_CODE
547 fi
548 if [[ ! -h $ZONEROOT/etc && ! -f $ZONEROOT/etc/mnttab ]]; then
549 /usr/bin/touch $ZONEROOT/etc/mnttab || exit $EXIT_CODE
550 /usr/bin/chmod 444 $ZONEROOT/etc/mnttab || exit $EXIT_CODE
551 fi
552 if [[ ! -f $ZONEROOT/proc && ! -d $ZONEROOT/proc ]]; then
553 mkdir -m 755 -p $ZONEROOT/proc || exit $EXIT_CODE
554 fi
555 if [[ ! -f $ZONEROOT/dev && ! -d $ZONEROOT/dev ]]; then
556 mkdir -m 755 -p $ZONEROOT/dev || exit $EXIT_CODE
557 fi
558 if [[ ! -h $ZONEROOT/etc && ! -h $ZONEROOT/etc/svc && ! -d $ZONEROOT/etc/svc ]]
559 then
560 mkdir -m 755 -p $ZONEROOT/etc/svc/volatile || exit $EXIT_CODE
561 fi
562
563 # Check for zones inside of image.
564 warn_zones
565
566 #
567 # Run update on attach. State is currently 'incomplete' so use the private
568 # force-update option.
569 #
570 log "$v_update"
571 /usr/sbin/zoneadm -z $ZONENAME attach -U $backout >&2
572 res=$?
573 if (( $? != 0 )); then
574 fatal "$e_badupdate"
575 else
576 log "$v_updatedone"
577 fi
578
579 log "$v_adjust"
580
581 #
582 # Any errors in these functions are not considered fatal. The zone can be
583 # be fixed up manually afterwards and it may need some additional manual
584 # cleanup in any case.
585 #
586
587 STACK_TYPE=$(/usr/sbin/zoneadm -z $ZONENAME list -p | \
588 /usr/bin/nawk -F: '{print $7}')
589 if (( $? != 0 )); then
590 error "$e_badinfo" "stacktype"
591 fi
592 vlog "$v_stacktype" "$STACK_TYPE"
593
594 fix_net
595 fix_nfs
596 fix_vfstab
597
598 vlog "$v_booting"
599
600 #
601 # Boot the zone so that we can do all of the SMF updates needed on the zone's
602 # repository.
603 #
604
605 zone_is_running=1
606
607 # The 'update on attach' left the zone installed.
608 /usr/sbin/zoneadm -z $ZONENAME boot -f -- -m milestone=none
609 if (( $? != 0 )); then
610 error "$e_badboot"
611 fatal "$e_exitfail"
612 fi
613
614 # cleanup SMF services
615 fix_smf
616
617 # remove invalid pkgs
618 rm_pkgs
619
620 vlog "$v_halting"
621 /usr/sbin/zoneadm -z $ZONENAME halt
622 if (( $? != 0 )); then
623 error "$e_badhalt"
624 failed=1
625 fi
626 zone_is_running=0
627
628 if [[ -z $failed && -n $OPT_U ]]; then
629 #
630 # We're sys-unconfiging the zone. This will halt the zone, however
631 # there are problems with sys-unconfig and it usually hangs when the
632 # zone is booted to milestone=none. This is why we previously halted
633 # the zone. We now boot to milestone=single-user. Again, the
634 # sys-unconfig can hang if the zone is still in the process of
635 # booting when we try to run sys-unconfig. Wait until the boot is
636 # done, which we do by checking for sulogin, or waiting 30 seconds,
637 # whichever comes first.
638 #
639
640 vlog "$v_unconfig"
641
642 zone_is_running=1
643 /usr/sbin/zoneadm -z $ZONENAME boot -- -m milestone=single-user
644 if (( $? != 0 )); then
645 error "$e_badboot"
646 fatal "$e_exitfail"
647 fi
648
649 for i in 0 1 2 3 4 5 6 7 8 9
650 do
651 sleep 10
652 /usr/sbin/zlogin $ZONENAME \
653 /usr/bin/svcs -H svc:/milestone/single-user:default 2>&1 |
654 /usr/bin/nawk '{
655 if ($1 == "online")
656 exit 0
657 else
658 exit 1
659 }' && break
660 done
661
662 if (( $i == 9 )); then
663 vlog "$e_nosingleuser"
664 fi
665
666 echo "yes" | /usr/sbin/zlogin -S $ZONENAME \
667 /usr/sbin/sys-unconfig >/dev/null 2>&1
668 if (( $? != 0 )); then
669 error "$e_unconfig"
670 failed=1
671 fi
672 fi
673
674
675 if [[ -n $failed ]]; then
676 fatal "$e_exitfail"
677 fi
678
679 vlog "$v_exitgood"
680 exit 0