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 }