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
23 #
24 # Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
25 # Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
26 # Copyright 2019 Nexenta Systems, Inc. All rights reserved.
27 #
28
29 . /usr/lib/brand/ipkg/common.ksh
30
31 m_attach_log=$(gettext "Log File: %s")
32 m_zfs=$(gettext "A ZFS file system was created for the zone.")
33 m_usage=$(gettext "attach [-a archive] [-d dataset] [-n] [-p] [-r zfs-recv] [-u]\n\tThe -a archive option specifies a tar file or cpio archive.\n\tThe -d dataset option specifies an existing dataset.\n\tThe -p option disables the copying publishers from the global zone.\n\tThe -r zfs-recv option receives the output of a 'zfs send' command\n\tof an existing zone root dataset.\n\tThe -u option indicates that the software should be updated to match\n\tthe current host.")
34 m_attach_root=$(gettext " Attach Path: %s")
35 m_attach_ds=$(gettext " Attach ZFS Dataset: %s")
36 m_gzinc=$(gettext " Global zone version: %s")
37 m_zinc=$(gettext " Non-Global zone version: %s")
38 m_need_update=$(gettext " Evaluation: Packages in zone %s are out of sync with the global zone. To proceed, retry with the -u flag.")
39 m_cache=$(gettext " Cache: Using %s.")
40 m_updating=$(gettext " Updating non-global zone: Output follows")
41 m_sync_done=$(gettext " Updating non-global zone: Zone updated.")
42 m_complete=$(gettext " Result: Attach Succeeded.")
43 m_failed=$(gettext " Result: Attach Failed.")
44
45 #
46 # These two messages are used by the install_image function in
47 # /usr/lib/brand/shared/common.ksh. Yes, this is terrible.
48 #
49 installing=$(gettext " Installing: This may take several minutes...")
50 no_installing=$(gettext " Installing: Using pre-existing data in zonepath")
51 copy_publishers=$(gettext " Copying publishers from the global zone")
52
53 f_sanity_variant=$(gettext " Sanity Check: FAILED, couldn't determine %s from image.")
54 f_sanity_global=$(gettext " Sanity Check: FAILED, appears to be a global zone (%s=%s).")
55 f_update=$(gettext "Could not update attaching zone")
56 f_no_pref_publisher=$(gettext "Unable to get preferred publisher information for zone '%s'.")
57 f_nosuch_key=$(gettext "Failed to find key %s for global zone publisher")
58 f_nosuch_cert=$(gettext "Failed to find cert %s for global zone publisher")
59 f_ds_config=$(gettext "Failed to configure dataset %s: could not set %s.")
60 f_no_active_ds_mounted=$(gettext "Failed to locate any dataset mounted at %s. Attach requires a mounted dataset.")
61
62 # Clean up on interrupt
63 trap_cleanup() {
64 typeset msg=$(gettext "Installation cancelled due to interrupt.")
65
66 log "$msg"
67
68 # umount any mounted file systems
69 umnt_fs
70
71 trap_exit
72 }
73
74 # If the attach failed then clean up the ZFS datasets we created.
75 trap_exit() {
76 if [[ $EXIT_CODE == $ZONE_SUBPROC_OK ]]; then
77 # unmount the zoneroot if labeled brand
78 is_brand_labeled
79 (( $? == 1 )) && ( umount $ZONEROOT || \
80 log "$f_zfs_unmount" "$ZONEPATH/root" )
81 else
82 if [[ "$install_media" != "-" ]]; then
83 /usr/lib/brand/ipkg/uninstall $ZONENAME $ZONEPATH -F
84 else
85 # Restore the zone properties for the pre-existing
86 # dataset.
87 if [[ -n "$ACTIVE_DS" ]]; then
88 zfs set zoned=off $ACTIVE_DS
89 (( $? != 0 )) && error "$f_ds_config" \
90 "$ACTIVE_DS" "zoned=off"
91 zfs set canmount=on $ACTIVE_DS
92 (( $? != 0 )) && error "$f_ds_config" \
93 "$ACTIVE_DS" "canmount=on"
94 zfs set mountpoint=$ZONEROOT $ACTIVE_DS
95 (( $? != 0 )) && error "$f_ds_config" \
96 "$ACTIVE_DS" "mountpoint=$ZONEROOT"
97 fi
98 fi
99 log "$m_failed"
100 fi
101
102 exit $EXIT_CODE
103 }
104
105 EXIT_CODE=$ZONE_SUBPROC_USAGE
106 install_media="-"
107
108 trap trap_cleanup INT
109 trap trap_exit EXIT
110
111 #set -o xtrace
112
113 PKG="/usr/bin/pkg"
114 KEYDIR=/var/pkg/ssl
115
116 # If we weren't passed at least two arguments, exit now.
117 (( $# < 2 )) && exit $ZONE_SUBPROC_USAGE
118
119 ZONENAME="$1"
120 ZONEPATH="$2"
121 # XXX shared/common script currently uses lower case zonename & zonepath
122 zonename="$ZONENAME"
123 zonepath="$ZONEPATH"
124
125 shift; shift # remove ZONENAME and ZONEPATH from arguments array
126
127 ZONEROOT="$ZONEPATH/root"
128 logdir="$ZONEROOT/var/log"
129
130 #
131 # Resetting GZ_IMAGE to something besides slash allows for simplified
132 # debugging of various global zone image configurations-- simply make
133 # an image somewhere with the appropriate interesting parameters.
134 #
135 GZ_IMAGE=${GZ_IMAGE:-/}
136 PKG_IMAGE=$GZ_IMAGE
137 export PKG_IMAGE
138
139 allow_update=0
140 noexecute=0
141 preserve_publishers=0
142
143 unset inst_type
144
145 typeset gz_incorporations=""
146
147 contains() {
148 string="$1"
149 substring="$2"
150 if test "${string#*$substring}" != "$string"; then
151 return 1 # $substring is in $string
152 else
153 return 0 # $substring is not in $string
154 fi
155 }
156
157 #
158 # $1 is an empty string to be populated with a list of incorporation
159 # fmris.
160 #
161 gather_incorporations() {
162 typeset -n incorporations=$1
163 typeset p=
164
165 for p in \
166 $(LC_ALL=C $PKG search -Hl -o pkg.name \
167 ':pkg.depend.install-hold:core-os*');do
168 if contains $(get_pkg_fmri $p) "entire"; then
169 incorporations="$incorporations $(get_pkg_fmri $p)"
170 fi
171 done
172 }
173
174 # Other brand attach options are invalid for this brand.
175 while getopts "a:d:npr:u" opt; do
176 case $opt in
177 a)
178 if [[ -n "$inst_type" ]]; then
179 fatal "$incompat_options" "$m_usage"
180 fi
181 inst_type="archive"
182 install_media="$OPTARG"
183 ;;
184 d)
185 if [[ -n "$inst_type" ]]; then
186 fatal "$incompat_options" "$m_usage"
187 fi
188 inst_type="directory"
189 install_media="$OPTARG"
190 ;;
191 n) noexecute=1 ;;
192 p) preserve_publishers=1 ;;
193 r)
194 if [[ -n "$inst_type" ]]; then
195 fatal "$incompat_options" "$m_usage"
196 fi
197 inst_type="stdin"
198 install_media="$OPTARG"
199 ;;
200 u) allow_update=1 ;;
201 ?) fail_usage "" ;;
202 *) fail_usage "";;
203 esac
204 done
205 shift $((OPTIND-1))
206
207 if [[ $noexecute == 1 && -n "$inst_type" ]]; then
208 fatal "$m_usage"
209 fi
210
211 [[ -z "$inst_type" ]] && inst_type="directory"
212
213 if [ $noexecute -eq 1 ]; then
214 #
215 # The zone doesn't have to exist when the -n option is used, so do
216 # this work early.
217 #
218
219 # XXX There is no sw validation for IPS right now, so just pretend
220 # everything will be ok.
221 EXIT_CODE=$ZONE_SUBPROC_OK
222 exit $ZONE_SUBPROC_OK
223 fi
224
225 LOGFILE=$(/usr/bin/mktemp -t -p /var/tmp $ZONENAME.attach_log.XXXXXX)
226 if [[ -z "$LOGFILE" ]]; then
227 fatal "$e_tmpfile"
228 fi
229 exec 2>>"$LOGFILE"
230
231 log "$m_attach_log" "$LOGFILE"
232
233 #
234 # TODO - once sxce is gone, move the following block into
235 # usr/lib/brand/shared/common.ksh code to share with other brands using
236 # the same zfs dataset logic for attach. This currently uses get_current_gzbe
237 # so we can't move it yet since beadm isn't in sxce.
238 #
239
240 # Validate that the zonepath is not in the root dataset.
241 pdir=`dirname $ZONEPATH`
242 get_zonepath_ds $pdir
243 fail_zonepath_in_rootds $ZONEPATH_DS
244
245 EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE
246
247 if [[ "$install_media" == "-" ]]; then
248 #
249 # Since we're using a pre-existing dataset, the dataset currently
250 # mounted on the {zonepath}/root becomes the active dataset. We
251 # can't depend on the usual dataset attributes to detect this since
252 # the dataset could be a detached zone or one that the user set up by
253 # hand and lacking the proper attributes. However, since the zone is
254 # not attached yet, the 'install_media == -' means the dataset must be
255 # mounted at this point.
256 #
257 ACTIVE_DS=`mount -p | nawk -v zroot=$ZONEROOT '{
258 if ($3 == zroot && $4 == "zfs")
259 print $1
260 }'`
261
262 [[ -z "$ACTIVE_DS" ]] && fatal "$f_no_active_ds_mounted" $ZONEROOT
263
264 # Set up proper attributes on the ROOT dataset.
265 get_zonepath_ds $ZONEPATH
266 zfs list -H -t filesystem -o name $ZONEPATH_DS/ROOT >/dev/null 2>&1
267 (( $? != 0 )) && fatal "$f_no_active_ds"
268
269 # need to ensure zoned is off to set mountpoint=legacy.
270 zfs set zoned=off $ZONEPATH_DS/ROOT
271 (( $? != 0 )) && fatal "$f_ds_config" $ZONEPATH_DS/ROOT "zoned=off"
272
273 zfs set mountpoint=legacy $ZONEPATH_DS/ROOT
274 (( $? != 0 )) && fatal "$f_ds_config" $ZONEPATH_DS/ROOT \
275 "mountpoint=legacy"
276 zfs set zoned=on $ZONEPATH_DS/ROOT
277 (( $? != 0 )) && fatal "$f_ds_config" $ZONEPATH_DS/ROOT "zoned=on"
278
279 #
280 # We're typically using a pre-existing mounted dataset so setting the
281 # following propery changes will cause the {zonepath}/root dataset to
282 # be unmounted. However, a p2v with an update-on-attach will have
283 # created the dataset with the correct properties, so setting these
284 # attributes won't unmount the dataset. Thus, we check the mount
285 # and attempt the remount if necessary.
286 #
287 get_current_gzbe
288 zfs set $PROP_PARENT=$CURRENT_GZBE $ACTIVE_DS
289 (( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS \
290 "$PROP_PARENT=$CURRENT_GZBE"
291 zfs set $PROP_ACTIVE=on $ACTIVE_DS
292 (( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "$PROP_ACTIVE=on"
293 zfs set canmount=noauto $ACTIVE_DS
294 (( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "canmount=noauto"
295 zfs set zoned=off $ACTIVE_DS
296 (( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "zoned=off"
297 zfs inherit mountpoint $ACTIVE_DS
298 (( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "'inherit mountpoint'"
299 zfs inherit zoned $ACTIVE_DS
300 (( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "'inherit zoned'"
301
302 mounted_ds=`mount -p | nawk -v zroot=$ZONEROOT '{
303 if ($3 == zroot && $4 == "zfs")
304 print $1
305 }'`
306
307 if [[ -z $mounted_ds ]]; then
308 mount -F zfs $ACTIVE_DS $ZONEROOT || fatal "$f_zfs_mount"
309 fi
310 else
311 #
312 # Since we're not using a pre-existing ZFS dataset layout, create
313 # the zone datasets and mount them. Start by creating the zonepath
314 # dataset, similar to what zoneadm would do for an initial install.
315 #
316 zds=$(zfs list -H -t filesystem -o name $pdir 2>/dev/null)
317 if (( $? == 0 )); then
318 pnm=$(/usr/bin/basename $ZONEPATH)
319 # The zonepath dataset might already exist.
320 zfs list -H -t filesystem -o name $zds/$pnm >/dev/null 2>&1
321 if (( $? != 0 )); then
322 zfs create "$zds/$pnm"
323 (( $? != 0 )) && fatal "$f_zfs_create"
324 vlog "$m_zfs"
325 fi
326 fi
327
328 create_active_ds
329 fi
330
331 #
332 # The zone's datasets are now in place.
333 #
334
335 log "$m_attach_root" "$ZONEROOT"
336 # note \n to add whitespace
337 log "$m_attach_ds\n" "$ACTIVE_DS"
338
339 install_image "$inst_type" "$install_media"
340
341 #
342 # End of TODO block to move to common code.
343 #
344
345 #
346 # Perform a sanity check to confirm that the image is not a global zone.
347 #
348 VARIANT=variant.opensolaris.zone
349 variant=$(LC_ALL=C $PKG -R $ZONEROOT variant -H $VARIANT)
350 [[ $? -ne 0 ]] && fatal "$f_sanity_variant" $VARIANT
351
352 echo $variant | IFS=" " read variantname variantval
353 [[ $? -ne 0 ]] && fatal "$f_sanity_variant"
354
355 # Check that we got the output we expect...
356 # XXX new pkg5 output is slightly different, ignore this check for now
357 #[[ $variantname = "$VARIANT" ]] || fatal "$f_sanity_variant" $VARIANT
358
359 # Check that the variant is non-global, else fail
360 [[ $variantval = "nonglobal" ]] || fatal "$f_sanity_global" $VARIANT $variantval
361
362 #
363 # Try to find the "entire" incorporation's FMRI in the gz.
364 #
365 gz_entire_fmri=$(get_entire_incorp)
366
367 #
368 # If entire isn't installed, create an array of global zone core-os
369 # incorporations.
370 #
371 if [[ -z $gz_entire_fmri ]]; then
372 gather_incorporations gz_incorporations
373 fi
374
375 #
376 # We're done with the global zone: switch images to the non-global
377 # zone.
378 #
379 PKG_IMAGE="$ZONEROOT"
380
381 #
382 # Try to find the "entire" incorporation's FMRI in the ngz.
383 #
384 ngz_entire_fmri=$(get_entire_incorp)
385
386 [[ -n $gz_entire_fmri ]] && log "$m_gzinc" "$gz_entire_fmri"
387 [[ -n $ngz_entire_fmri ]] && log "$m_zinc" "$ngz_entire_fmri"
388
389 #
390 # Create the list of incorporations we wish to install/update in the
391 # ngz.
392 #
393 typeset -n incorp_list
394 if [[ -n $gz_entire_fmri ]]; then
395 incorp_list=gz_entire_fmri
396 else
397 incorp_list=gz_incorporations
398 fi
399
400 #
401 # If there is a cache, use it.
402 #
403 if [[ -f /var/pkg/pkg5.image && -d /var/pkg/publisher ]]; then
404 PKG_CACHEROOT=/var/pkg/publisher
405 export PKG_CACHEROOT
406 log "$m_cache" "$PKG_CACHEROOT"
407 fi
408
409 if [[ $preserve_publishers == 0 ]]; then
410 log "$copy_publishers"
411 LC_ALL=C $PKG copy-publishers-from $GZ_IMAGE
412 fi
413
414 #
415 # Bring the ngz entire incorporation into sync with the gz as follows:
416 # - First compare the existence of entire in both global and non-global
417 # zone and update the non-global zone accordingly.
418 # - Then, if updates aren't allowed check if we can attach because no
419 # updates are required. If we can, then we are finished.
420 # - Finally, we know we can do updates and they are required, so update
421 # all the non-global zone incorporations using the list we gathered
422 # from the global zone earlier.
423 #
424 if [[ -z $gz_entire_fmri && -n $ngz_entire_fmri ]]; then
425 if [[ $allow_update == 1 ]]; then
426 log "$m_updating"
427 LC_ALL=C $PKG uninstall entire || pkg_err_check "$f_update"
428 else
429 log "\n$m_need_update" "$ZONENAME"
430 EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE
431 exit $EXIT_CODE
432 fi
433 fi
434
435 if [[ $allow_update == 0 ]]; then
436 LC_ALL=C $PKG info -q $incorp_list
437 ret=$?
438 if [[ $ret == 0 ]]; then
439 log "\n$m_complete"
440 EXIT_CODE=$ZONE_SUBPROC_OK
441 exit $EXIT_CODE
442 else
443 log "\n$m_need_update" "$ZONENAME"
444 EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE
445 exit $EXIT_CODE
446 fi
447 fi
448
449 #
450 # If the NGZ doesn't have entire, but the GZ does, then we have to install
451 # entire twice. First time we don't specify a version and let constraining
452 # incorporations determine the version. Second time, we try to install the
453 # same version as we have in the GZ.
454 #
455 if [[ -n $gz_entire_fmri && -z $ngz_entire_fmri ]]; then
456 LC_ALL=C $PKG install --accept --no-refresh entire || \
457 pkg_err_check "$f_update"
458 fi
459
460 LC_ALL=C $PKG install --accept --no-refresh $incorp_list || \
461 pkg_err_check "$f_update"
462
463 log "\n$m_sync_done"
464 log "$m_complete"
465
466 EXIT_CODE=$ZONE_SUBPROC_OK
467 exit $ZONE_SUBPROC_OK