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