1 #
2 # CDDL HEADER START
3 #
4 # The contents of this file are subject to the terms of the
5 # Common Development and Distribution License (the "License").
6 # You may not use this file except in compliance with the License.
7 #
8 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 # or http://www.opensolaris.org/os/licensing.
10 # See the License for the specific language governing permissions
11 # and limitations under the License.
12 #
13 # When distributing Covered Code, include this CDDL HEADER in each
14 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 # If applicable, add the following below this CDDL HEADER, with the
16 # fields enclosed by brackets "[]" replaced with your own identifying
17 # information: Portions Copyright [yyyy] [name of copyright owner]
18 #
19 # CDDL HEADER END
20 #
21
22 #
23 # Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
24 # Copyright 2015 Nexenta Systems, Inc. All rights reserved.
25
26 unset LD_LIBRARY_PATH
27 PATH=/usr/bin:/usr/sbin
28 export PATH
29
30 . /usr/lib/brand/shared/common.ksh
31
32 PROP_PARENT="org.opensolaris.libbe:parentbe"
33 PROP_ACTIVE="org.opensolaris.libbe:active"
34
35 f_incompat_options=$(gettext "cannot specify both %s and %s options")
36 f_sanity_detail=$(gettext "Missing %s at %s")
37 f_sanity_sparse=$(gettext "Is this a sparse zone image? The image must be whole-root.")
38 sanity_ok=$(gettext " Sanity Check: Passed. Looks like an OpenSolaris system.")
39 sanity_fail=$(gettext " Sanity Check: FAILED (see log for details).")
40 sanity_fail_vers=$(gettext " Sanity Check: the Solaris image (release %s) is not an OpenSolaris image and cannot be installed in this type of branded zone.")
41 install_fail=$(gettext " Result: *** Installation FAILED ***")
42 f_zfs_in_root=$(gettext "Installing a zone inside of the root pool's 'ROOT' dataset is unsupported.")
43 f_zfs_create=$(gettext "Unable to create the zone's ZFS dataset.")
44 f_root_create=$(gettext "Unable to create the zone's ZFS dataset mountpoint.")
45 f_no_gzbe=$(gettext "unable to determine global zone boot environment.")
46 f_no_ds=$(gettext "the zonepath must be a ZFS dataset.\nThe parent directory of the zonepath must be a ZFS dataset so that the\nzonepath ZFS dataset can be created properly.")
47 f_multiple_ds=$(gettext "multiple active datasets.")
48 f_no_active_ds=$(gettext "no active dataset.")
49 f_zfs_unmount=$(gettext "Unable to unmount the zone's root ZFS dataset (%s).\nIs there a global zone process inside the zone root?\nThe current zone boot environment will remain mounted.\n")
50 f_zfs_mount=$(gettext "Unable to mount the zone's ZFS dataset.")
51
52 f_safedir=$(gettext "Expected %s to be a directory.")
53 f_cp=$(gettext "Failed to cp %s %s.")
54 f_cp_unsafe=$(gettext "Failed to safely copy %s to %s.")
55
56 m_brnd_usage=$(gettext "brand-specific usage: ")
57
58 v_unconfig=$(gettext "Performing zone unconfiguration")
59 e_unconfig=$(gettext "Zone unconfiguration failed")
60 v_mounting=$(gettext "Mounting the zone")
61 e_badmount=$(gettext "Zone mount failed")
62 v_unmount=$(gettext "Unmounting zone")
63 e_badunmount=$(gettext "Zone unmount failed")
64 e_exitfail=$(gettext "Postprocessing failed.")
65
66 m_complete=$(gettext " Done: Installation completed in %s seconds.")
67 m_postnote=$(gettext " Next Steps: Boot the zone, then log into the zone console (zlogin -C)")
68 m_postnote2=$(gettext " to complete the configuration process.")
69
70 fail_incomplete() {
71 printf "ERROR: " 1>&2
72 printf "$@" 1>&2
73 printf "\n" 1>&2
74 exit $ZONE_SUBPROC_NOTCOMPLETE
75 }
76
77 fail_usage() {
78 printf "$@" 1>&2
79 printf "\n" 1>&2
80 printf "$m_brnd_usage" 1>&2
81 printf "$m_usage\n" 1>&2
82 exit $ZONE_SUBPROC_USAGE
83 }
84
85 is_brand_labeled() {
86 if [ -z $ALTROOT ]; then
87 AR_OPTIONS=""
88 else
89 AR_OPTIONS="-R $ALTROOT"
90 fi
91 brand=$(/usr/sbin/zoneadm $AR_OPTIONS -z $ZONENAME \
92 list -p | awk -F: '{print $6}')
93 [[ $brand == "labeled" ]] && return 1
94 return 0
95 }
96
97 sanity_check() {
98 typeset dir="$1"
99 shift
100 res=0
101
102 #
103 # Check for some required directories and make sure this isn't a
104 # sparse zone image from SXCE.
105 #
106 checks="etc etc/svc var var/svc"
107 for x in $checks; do
108 if [[ ! -e $dir/$x ]]; then
109 log "$f_sanity_detail" "$x" "$dir"
110 res=1
111 fi
112 done
113 if (( $res != 0 )); then
114 log "$f_sanity_sparse"
115 log "$sanity_fail"
116 fatal "$install_fail" "$ZONENAME"
117 fi
118
119 # Check for existence of pkg command.
120 if [[ ! -x $dir/usr/bin/pkg ]]; then
121 log "$f_sanity_detail" "usr/bin/pkg" "$dir"
122 log "$sanity_fail"
123 fatal "$install_fail" "$ZONENAME"
124 fi
125
126 #
127 # XXX There should be a better way to do this.
128 # Check image release. We only work on the same minor release as the
129 # system is running. The INST_RELEASE file doesn't exist with IPS on
130 # OpenSolaris, so its presence means we have an earlier Solaris
131 # (i.e. non-OpenSolaris) image.
132 #
133 if [[ -f "$dir/var/sadm/system/admin/INST_RELEASE" ]]; then
134 image_vers=$(nawk -F= '{if ($1 == "VERSION") print $2}' \
135 $dir/var/sadm/system/admin/INST_RELEASE)
136 vlog "$sanity_fail_vers" "$image_vers"
137 fatal "$install_fail" "$ZONENAME"
138 fi
139
140 vlog "$sanity_ok"
141 }
142
143 get_current_gzbe() {
144 #
145 # If there is no alternate root (normal case) then set the
146 # global zone boot environment by finding the boot environment
147 # that is active now.
148 # If a zone exists in a boot environment mounted on an alternate root,
149 # then find the boot environment where the alternate root is mounted.
150 #
151 if [ -x /usr/sbin/beadm ]; then
152 CURRENT_GZBE=`/usr/sbin/beadm list -H | /usr/bin/nawk \
153 -v alt=$ALTROOT -F\; '{
154 if (length(alt) == 0) {
155 # Field 3 is the BE status. 'N' is the active BE.
156 if ($3 !~ "N")
157 next
158 } else {
159 # Field 4 is the BE mountpoint.
160 if ($4 != alt)
161 next
162 }
163 # Field 2 is the BE UUID
164 print $2
165 }'`
166 else
167 # If there is no beadm command then the system doesn't really
168 # support multiple boot environments. We still want zones to
169 # work so simulate the existence of a single boot environment.
170 CURRENT_GZBE="opensolaris"
171 fi
172
173 if [ -z "$CURRENT_GZBE" ]; then
174 fail_fatal "$f_no_gzbe"
175 fi
176 }
177
178 # Find the active dataset under the zonepath dataset to mount on zonepath/root.
179 # $1 CURRENT_GZBE
180 # $2 ZONEPATH_DS
181 get_active_ds() {
182 ACTIVE_DS=`/usr/sbin/zfs list -H -r -t filesystem \
183 -o name,$PROP_PARENT,$PROP_ACTIVE $2/ROOT | \
184 /usr/bin/nawk -v gzbe=$1 ' {
185 if ($1 ~ /ROOT\/[^\/]+$/ && $2 == gzbe && $3 == "on") {
186 print $1
187 if (found == 1)
188 exit 1
189 found = 1
190 }
191 }'`
192
193 if [ $? -ne 0 ]; then
194 fail_fatal "$f_multiple_ds"
195 fi
196
197 if [ -z "$ACTIVE_DS" ]; then
198 fail_fatal "$f_no_active_ds"
199 fi
200 }
201
202 # Check that zone is not in the ROOT dataset.
203 fail_zonepath_in_rootds() {
204 case $1 in
205 rpool/ROOT/*)
206 fail_fatal "$f_zfs_in_root"
207 break;
208 ;;
209 *)
210 break;
211 ;;
212 esac
213 }
214
215 #
216 # Make sure the active dataset is mounted for the zone. There are several
217 # cases to consider:
218 # 1) First boot of the zone, nothing is mounted
219 # 2) Zone is halting, active dataset remains the same.
220 # 3) Zone is halting, there is a new active dataset to mount.
221 #
222 mount_active_ds() {
223 mount -p | cut -d' ' -f3 | egrep -s "^$ZONEPATH/root$"
224 if (( $? == 0 )); then
225 # Umount current dataset on the root (it might be an old BE).
226 umount $ZONEPATH/root
227 if (( $? != 0 )); then
228 # The umount failed, leave the old BE mounted.
229 # Warn about gz process preventing umount.
230 printf "$f_zfs_unmount" "$ZONEPATH/root"
231 return
232 fi
233 fi
234
235 # Mount active dataset on the root.
236 get_current_gzbe
237 get_zonepath_ds $ZONEPATH
238 get_active_ds $CURRENT_GZBE $ZONEPATH_DS
239
240 mount -F zfs $ACTIVE_DS $ZONEPATH/root || fail_fatal "$f_zfs_mount"
241 }
242
243 #
244 # Set up ZFS dataset hierarchy for the zone root dataset.
245 #
246 create_active_ds() {
247 get_current_gzbe
248
249 #
250 # Find the zone's current dataset. This should have been created by
251 # zoneadm.
252 #
253 get_zonepath_ds $zonepath
254
255 # Check that zone is not in the ROOT dataset.
256 fail_zonepath_in_rootds $ZONEPATH_DS
257
258 #
259 # From here on, errors should cause the zone to be incomplete.
260 #
261 int_code=$ZONE_SUBPROC_FATAL
262
263 #
264 # We need to tolerate errors while creating the datasets and making the
265 # mountpoint, since these could already exist from some other BE.
266 #
267
268 /usr/sbin/zfs list -H -o name $ZONEPATH_DS/ROOT >/dev/null 2>&1
269 if (( $? != 0 )); then
270 /usr/sbin/zfs create -o mountpoint=legacy \
271 -o zoned=on $ZONEPATH_DS/ROOT
272 if (( $? != 0 )); then
273 fail_fatal "$f_zfs_create"
274 fi
275 fi
276
277 BENAME=zbe
278 BENUM=0
279 # Try 100 different names before giving up.
280 while [ $BENUM -lt 100 ]; do
281 /usr/sbin/zfs create -o $PROP_ACTIVE=on \
282 -o $PROP_PARENT=$CURRENT_GZBE \
283 -o canmount=noauto $ZONEPATH_DS/ROOT/$BENAME >/dev/null 2>&1
284 if (( $? == 0 )); then
285 break
286 fi
287 BENUM=`expr $BENUM + 1`
288 BENAME="zbe-$BENUM"
289 done
290
291 if [ $BENUM -ge 100 ]; then
292 fail_fatal "$f_zfs_create"
293 fi
294
295 if [ ! -d $ZONEROOT ]; then
296 /usr/bin/mkdir $ZONEROOT
297 fi
298
299 /usr/sbin/mount -F zfs $ZONEPATH_DS/ROOT/$BENAME $ZONEROOT || \
300 fail_incomplete "$f_zfs_mount"
301 }
302
303 #
304 # Remove zone's system configuration
305 #
306 unconfigure_zone() {
307 vlog "$v_unconfig"
308
309 vlog "$v_mounting"
310 ZONE_IS_MOUNTED=1
311 zoneadm -z $ZONENAME mount -f || fatal "$e_badmount"
312 ZONE_BRAND=`zoneadm list -pc | /usr/bin/gawk -v zone=$ZONENAME -F':' '$2 == zone { print $6 }'`
313 if [[ $ZONE_BRAND = "ipkg" ]]; then
314 zlogin -S $ZONENAME /usr/lib/brand/ipkg/system-unconfigure -R /a \
315 >/dev/null 2>&1
316 if (( $? != 0 )); then
317 error "$e_unconfig"
318 failed=1
319 fi
320 else
321 zlogin -S $ZONENAME /usr/sbin/sys-unconfig -R /a \
322 </dev/null >/dev/null 2>&1
323 if (( $? != 0 )); then
324 error "$e_unconfig"
325 failed=1
326 fi
327 fi
328
329 vlog "$v_unmount"
330 zoneadm -z $ZONENAME unmount || fatal "$e_badunmount"
331 ZONE_IS_MOUNTED=0
332
333 [[ -n $failed ]] && fatal "$e_exitfail"
334 }
335
336 #
337 # Emits to stdout the fmri for the supplied package,
338 # stripped of publisher name and other junk.
339 #
340 get_pkg_fmri() {
341 typeset pname=$1
342 typeset pkg_fmri=
343 typeset info_out=
344
345 info_out=$(LC_ALL=C $PKG info pkg:/$pname 2>/dev/null)
346 if [[ $? -ne 0 ]]; then
347 return 1
348 fi
349 pkg_fmri=$(echo $info_out | grep FMRI | cut -d'@' -f 2)
350 echo "$pname@$pkg_fmri"
351 return 0
352 }
353
354 #
355 # Emits to stdout the entire incorporation for this image,
356 # stripped of publisher name and other junk.
357 #
358 get_entire_incorp() {
359 get_pkg_fmri entire
360 return $?
361 }
362
363 #
364 # Emits to stdout the extended attributes for a publisher. The
365 # attributes are emitted in the order "sticky preferred enabled". It
366 # expects two parameters: publisher name and URL type which can be
367 # ("mirror" or "origin").
368 #
369 get_publisher_attrs() {
370 typeset pname=$1
371 typeset utype=$2
372
373 LC_ALL=C $PKG publisher -HF tsv| \
374 nawk '($5 == "'"$utype"'" || \
375 ("'"$utype"'" == "origin" && $5 == "")) \
376 && $1 == "'"$pname"'" \
377 {printf "%s %s %s\n", $2, $3, $4;}'
378 return 0
379 }
380
381 #
382 # Emits to stdout the extended attribute arguments for a publisher. It
383 # expects two parameters: publisher name and URL type which can be
384 # ("mirror" or "origin").
385 #
386 get_publisher_attr_args() {
387 typeset args=
388 typeset sticky=
389 typeset preferred=
390 typeset enabled=
391
392 get_publisher_attrs $1 $2 |
393 while IFS=" " read sticky preferred enabled; do
394 if [ $sticky == "true" ]; then
395 args="--sticky"
396 else
397 args="--non-sticky"
398 fi
399
400 if [ $preferred == "true" ]; then
401 args="$args -P"
402 fi
403
404 if [ $enabled == "true" ]; then
405 args="$args --enable"
406 else
407 args="$args --disable"
408 fi
409 done
410 echo $args
411
412 return 0
413 }
414
415 #
416 # Emits to stdout the publisher's prefix followed by a '=', and then
417 # the list of the requested URLs separated by spaces, followed by a
418 # newline after each unique publisher. It expects two parameters,
419 # publisher type ("all", "preferred", "non-preferred") and URL type
420 # ("mirror" or "origin".)
421 #
422 get_publisher_urls() {
423 typeset ptype=$1
424 typeset utype=$2
425 typeset __pub_prefix=
426 typeset __publisher_urls=
427 typeset ptype_filter=
428
429 if [ "$ptype" == "all" ]
430 then
431 ptype_filter=""
432 elif [ "$ptype" == "preferred" ]
433 then
434 ptype_filter="true"
435 elif [ "$ptype" == "non-preferred" ]
436 then
437 ptype_filter="false"
438 fi
439
440 LC_ALL=C $PKG publisher -HF tsv | \
441 nawk '($5 == "'"$utype"'" || \
442 ("'"$utype"'" == "origin" && $5 == "")) && \
443 ( "'"$ptype_filter"'" == "" || $3 == "'"$ptype_filter"'" ) \
444 {printf "%s %s\n", $1, $7;}' |
445 while IFS=" " read __publisher __publisher_url; do
446 if [[ "$utype" == "origin" && \
447 -z "$__publisher_url" ]]; then
448 # Publisher without origins.
449 __publisher_url="None"
450 fi
451
452 if [[ -n "$__pub_prefix" && \
453 "$__pub_prefix" != "$__publisher" ]]; then
454 # Different publisher so emit accumulation and
455 # clear existing data.
456 echo $__pub_prefix=$__publisher_urls
457 __publisher_urls=""
458 fi
459 __pub_prefix=$__publisher
460 __publisher_urls="$__publisher_urls$__publisher_url "
461 done
462
463 if [[ -n "$__pub_prefix" && -n "$__publisher_urls" ]]; then
464 echo $__pub_prefix=$__publisher_urls
465 fi
466
467 return 0
468 }
469
470 #
471 # Emit to stdout the key and cert associated with the publisher
472 # name provided. Returns 'None' if no information is present.
473 # For now we assume that the mirrors all use the same key and cert
474 # as the main publisher.
475 #
476 get_pub_secinfo() {
477 typeset key=
478 typeset cert=
479
480 key=$(LC_ALL=C $PKG publisher $1 |
481 nawk -F': ' '/SSL Key/ {print $2; exit 0}')
482 cert=$(LC_ALL=C $PKG publisher $1 |
483 nawk -F': ' '/SSL Cert/ {print $2; exit 0}')
484 print $key $cert
485 }
486
487 #
488 # Handle pkg exit code. Exit 0 means Command succeeded, exit 4 means
489 # No changes were made - nothing to do. Any other exit code is an error.
490 #
491 pkg_err_check() {
492 typeset res=$?
493 (( $res != 0 && $res != 4 )) && fail_fatal "$1"
494 }