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 . /usr/lib/brand/shared/common.ksh
27
28 # Restrict executables to /bin, /usr/bin and /usr/sfw/bin
29 PATH=/bin:/usr/bin:/usr/sbin:/usr/sfw/bin
30 export PATH
31
32 cmd_not_found=$(gettext "Required command '%s' cannot be found!")
33 cmd_not_exec=$(gettext "Required command '%s' not executable!")
34 zone_initfail=$(gettext "Attempt to initialize zone '%s' FAILED.")
35 path_abs=$(gettext "Pathname specified to -a '%s' must be absolute.")
36
37 e_tmpfile=$(gettext "Unable to create temporary file")
38
39 both_modes=$(gettext "%s: cannot select both silent and verbose modes")
40
41 both_choices=$(gettext "%s: cannot select both preserve and unconfigure options")
42
43 both_kinds=$(gettext "%s: cannot specify both archive and directory")
44
45 not_found=$(gettext "%s: error: file or directory not found.")
46
47 wrong_dir_type=$(gettext "error: must be a directory")
48
49 not_readable=$(gettext "Cannot read file '%s'")
50
51 no_install=$(gettext "Could not create install directory '%s'")
52 no_log=$(gettext "Could not create log directory '%s'")
53
54 media_taste=$(gettext " Media Type: %s")
55 bad_archive=$(gettext "ERROR: must be a flash archive, a cpio archive (can also
56 be gzipped or bzipped), a pax XUSTAR archive, or a level 0 ufsdump archive.")
57
58 product_vers=$(gettext " Product: %s")
59 install_vers=$(gettext " Installer: %s")
60 install_zone=$(gettext " Zone: %s")
61 install_path=$(gettext " Path: %s")
62 install_from=$(gettext " Source: %s")
63 installing=$(gettext " Installing: This may take several minutes...")
64 no_installing=$(gettext " Installing: Using pre-existing data in zonepath")
65 install_prog=$(gettext " Installing: %s")
66
67 install_fail=$(gettext " Result: *** Installation FAILED ***")
68 install_log=$(gettext " Log File: %s")
69
70 install_abort=$(gettext " Result: Installation aborted.")
71 install_good=$(gettext " Result: Installation completed successfully.")
72
73 not_native_image=$(gettext " Sanity Check: %s doesn't look like a native image.")
74 sanity_ok=$(gettext " Sanity Check: Passed. Looks like a native system.")
75 sanity_fail_detail=$(gettext " Sanity Check: Missing %s at %s")
76 sanity_fail_vers=$(gettext " Sanity Check: image release version %s does not match system release version %s, the zone is not usable on this system.")
77 sanity_fail=$(gettext " Sanity Check: FAILED (see log for details).")
78
79
80 p2ving=$(gettext "Postprocessing: This may take a while...")
81 p2v_prog=$(gettext " Postprocess: ")
82 p2v_done=$(gettext " Result: Postprocessing complete.")
83 p2v_fail=$(gettext " Result: Postprocessing failed.")
84
85 root_full=$(gettext "Zonepath root %s exists and contains data; remove or move aside prior to install.")
86
87 media_missing=\
88 $(gettext "%s: you must specify an installation source using '-a' or '-d'.")
89
90 cfgchoice_missing=\
91 $(gettext "%s: you must specify -u (sys-unconfig) or -p (preserve identity).")
92
93 mount_failed=$(gettext "ERROR: zonecfg(1M) 'fs' mount failed")
94
95 not_flar=$(gettext "Input is not a flash archive")
96 bad_flar=$(gettext "Flash archive is a corrupt")
97 unknown_archiver=$(gettext "Archiver %s is not supported")
98
99 e_baddir=$(gettext "Invalid '%s' directory within the zone")
100
101 # Clean up on interrupt
102 trap_cleanup()
103 {
104 msg=$(gettext "Installation cancelled due to interrupt.")
105 log "$msg"
106
107 # umount IPDs
108 umnt_fs
109
110 exit $EXIT_CODE
111 }
112
113 sanity_check()
114 {
115 typeset dir="$1"
116 shift
117 ret=0
118
119 # These checks must work with a sparse zone.
120 checks="etc etc/svc usr sbin lib var var/svc"
121 for x in $checks; do
122 if [[ ! -e $dir/$x ]]; then
123 vlog "$sanity_fail_detail" "$x" "$dir"
124 ret=1
125 fi
126 done
127
128 #
129 # Check image release against system release. We only work on the
130 # same minor release as the system is running.
131 #
132 sys_vers=0
133 image_vers=-1
134 if [[ -f /var/sadm/system/admin/INST_RELEASE ]]; then
135 sys_vers=$(nawk -F= '{if ($1 == "VERSION") print $2}' \
136 /var/sadm/system/admin/INST_RELEASE)
137 fi
138
139 if [[ -f $dir/var/sadm/system/admin/INST_RELEASE ]]; then
140 image_vers=$(nawk -F= '{if ($1 == "VERSION") print $2}' \
141 $dir/var/sadm/system/admin/INST_RELEASE)
142 fi
143
144 if (( $sys_vers != $image_vers )); then
145 vlog "$sanity_fail_vers" "$image_vers" "$sys_vers"
146 ret=1
147 fi
148
149 return $ret
150 }
151
152 #
153 # The main body of the script starts here.
154 #
155 # This script should never be called directly by a user but rather should
156 # only be called by zoneadm to install a native system image into a zone.
157 #
158
159 #
160 # Exit code to return if install is interrupted or exit code is otherwise
161 # unspecified.
162 #
163 EXIT_CODE=$ZONE_SUBPROC_USAGE
164
165 trap trap_cleanup INT
166
167 # If we weren't passed at least two arguments, exit now.
168 (( $# < 2 )) && exit $ZONE_SUBPROC_USAGE
169
170 zonename="$1"
171 zonepath="$2"
172
173 ZONEROOT="$zonepath/root"
174 logdir="$ZONEROOT/var/log"
175
176 shift; shift # remove zonename and zonepath from arguments array
177
178 unset backout
179 unset install_archive
180 unset source_dir
181 unset msg
182 unset silent_mode
183 unset OPT_V
184
185 #
186 # It is worth noting here that we require the end user to pick one of
187 # -u (sys-unconfig) or -p (preserve config). This is because we can't
188 # really know in advance which option makes a better default. Forcing
189 # the user to pick one or the other means that they will consider their
190 # choice and hopefully not be surprised or disappointed with the result.
191 #
192 unset unconfig_zone
193 unset preserve_zone
194
195 while getopts "a:b:d:psuv" opt
196 do
197 case "$opt" in
198 a) install_archive="$OPTARG" ; install_media="$OPTARG";;
199 b) if [[ -n "$backout" ]]; then
200 backout="$backout -b $OPTARG"
201 else
202 backout="-b $OPTARG"
203 fi
204 ;;
205 d) source_dir="$OPTARG" ; install_media="$OPTARG";;
206 p) preserve_zone="-p";;
207 s) silent_mode=1;;
208 u) unconfig_zone="-u";;
209 v) OPT_V="-v";;
210 *) exit $ZONE_SUBPROC_USAGE;;
211 esac
212 done
213 shift OPTIND-1
214
215 # The install can't be both verbose AND silent...
216 if [[ -n $silent_mode && -n $OPT_V ]]; then
217 fatal "$both_modes" "zoneadm install"
218 fi
219
220 if [[ -z $install_media ]]; then
221 fatal "$media_missing" "zoneadm install"
222 fi
223
224 if [[ -n $install_archive && -n $source_dir ]]; then
225 fatal "$both_kinds" "zoneadm install"
226 fi
227
228 # The install can't both preserve and unconfigure
229 if [[ -n $unconfig_zone && -n $preserve_zone ]]; then
230 fatal "$both_choices" "zoneadm install"
231 fi
232
233 # Must pick one or the other.
234 if [[ -z $unconfig_zone && -z $preserve_zone ]]; then
235 fatal "$cfgchoice_missing" "zoneadm install"
236 fi
237
238 #
239 # Handle "-d -" option to use whatever is already installed into the zonepath.
240 #
241 if [ "$source_dir" != "-" ]; then
242 #
243 # Validate $install_media (things common to archive/dir)
244 #
245 if [[ "$(echo $install_media | cut -c 1)" != "/" ]]; then
246 fatal "$path_abs" "$install_media"
247 fi
248
249 if [[ ! -e "$install_media" ]]; then
250 log "$not_found" "$install_media"
251 fatal "$install_abort" "$zonename"
252 fi
253
254 if [[ ! -r "$install_media" ]]; then
255 log "$not_readable" "$install_media"
256 fatal "$install_abort" "$zonename"
257 fi
258
259 if [[ -n $install_archive ]]; then
260 if [[ ! -f "$install_archive" ]]; then
261 log "$media_taste" "$bad_archive"
262 fatal "$install_abort" "$zonename"
263 fi
264 fi
265
266 if [[ -n $source_dir ]]; then
267 if [[ ! -d "$source_dir" ]]; then
268 log "$media_taste" "$wrong_dir_type"
269 fatal "$install_abort" "$zonename"
270 fi
271 fi
272 fi
273
274 LOGFILE=$(/usr/bin/mktemp -t -p /var/tmp $zonename.install_log.XXXXXX)
275 if [[ -z "$LOGFILE" ]]; then
276 fatal "$e_tmpfile"
277 fi
278 zone_logfile="${logdir}/$zonename.install$$.log"
279 exec 2>>"$LOGFILE"
280 log "$install_log" "$LOGFILE"
281
282 vlog "Starting pre-installation tasks."
283
284 if [[ -z $install_archive && -n $source_dir ]]; then
285 #
286 # Minimal check to make sure that the user is passing
287 # us something that at least seems to be a native image.
288 #
289 if [[ "$source_dir" == "-" ]]; then
290 filetype="existing"
291 filetypename="existing"
292 else
293 sanity_check $source_dir
294 if (( $? != 0 )); then
295 fatal "$not_native_image" "$source_dir"
296 fi
297
298 filetype="directory"
299 filetypename="directory"
300 fi
301 else
302 ftype="$(LC_ALL=C file $install_archive | cut -d: -f 2)"
303 case "$ftype" in
304 *cpio*) filetype="cpio"
305 filetypename="cpio archive"
306 ;;
307 *bzip2*) filetype="bzip2"
308 filetypename="bzipped cpio archive"
309 ;;
310 *gzip*) filetype="gzip"
311 filetypename="gzipped cpio archive"
312 ;;
313 *ufsdump*) filetype="ufsdump"
314 filetypename="ufsdump archive"
315 ;;
316 *Flash\ Archive*) filetype="flar"
317 filetypename="flash archive"
318 ;;
319 *USTAR\ tar\ archive\ extended\ format*) filetype="xustar"
320 filetypename="pax (xustar) archive"
321 ;;
322 *) log "$media_taste" "$bad_archive"
323 fatal "$install_abort" "$zonename"
324 ;;
325 esac
326 fi
327
328 #
329 # From here on out, an unspecified exit or interrupt should exit with
330 # ZONE_SUBPROC_NOTCOMPLETE, meaning a user will need to do an uninstall before
331 # attempting another install, as we've modified the directories we were going
332 # to install to in some way.
333 #
334 EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE
335
336 if [[ ! -d "$ZONEROOT" ]]
337 then
338 if ! mkdir -p "$ZONEROOT" 2>/dev/null; then
339 fatal "$no_install" "$ZONEROOT"
340 fi
341 fi
342
343 #
344 # Check for a non-empty root if no '-d -' option.
345 #
346 if [[ "$filetype" != "existing" ]]; then
347 cnt=$(ls $ZONEROOT | wc -l)
348 if (( $cnt != 0 )); then
349 fatal "$root_full" "$ZONEROOT"
350 fi
351 fi
352
353 vlog "Installation started for zone \"$zonename\""
354
355 log "$install_from" "$install_media"
356 vlog "$media_taste" "$filetypename"
357
358 fstmpfile=$(/usr/bin/mktemp -t -p /var/tmp)
359 if [[ -z "$fstmpfile" ]]; then
360 fatal "$e_tmpfile"
361 fi
362
363 # Make sure we always have the files holding the directories to filter
364 # out when extracting from a CPIO or PAX archive. We'll add the IPDs to these
365 # files in get_fs_info().
366 ipdcpiofile=$(/usr/bin/mktemp -t -p /var/tmp ipd.cpio.XXXXXX)
367 if [[ -z "$ipdcpiofile" ]]; then
368 rm -f $fstmpfile
369 fatal "$e_tmpfile"
370 fi
371
372 # In addition to the IPDs, also filter out these directories.
373 echo 'dev/*' >>$ipdcpiofile
374 echo 'devices/*' >>$ipdcpiofile
375 echo 'devices' >>$ipdcpiofile
376 echo 'proc/*' >>$ipdcpiofile
377 echo 'tmp/*' >>$ipdcpiofile
378 echo 'var/run/*' >>$ipdcpiofile
379 echo 'system/contract/*' >>$ipdcpiofile
380 echo 'system/object/*' >>$ipdcpiofile
381
382 ipdpaxfile=$(/usr/bin/mktemp -t -p /var/tmp ipd.pax.XXXXXX)
383 if [[ -z "$ipdpaxfile" ]]; then
384 rm -f $fstmpfile $ipdcpiofile
385 fatal "$e_tmpfile"
386 fi
387
388 printf "%s " "dev devices proc tmp var/run system/contract system/object" \
389 >>$ipdpaxfile
390
391 # Set up any fs mounts so the archive will install into the correct locations.
392 get_fs_info
393 mnt_fs
394 if (( $? != 0 )); then
395 umnt_fs >/dev/null 2>&1
396 rm -f $fstmpfile $ipdcpiofile $ipdpaxfile
397 fatal "$mount_failed"
398 fi
399
400 if [[ "$filetype" == "existing" ]]; then
401 log "$no_installing"
402 else
403 log "$installing"
404 fi
405
406 unpack_result=0
407 stage1="cat"
408 if [[ "$filetype" == "gzip" ]]; then
409 stage1="gzcat"
410 filetype="cpio"
411 fi
412
413 if [[ "$filetype" == "bzip2" ]]; then
414 stage1="bzcat"
415 filetype="cpio"
416 fi
417
418 if [[ "$filetype" == "cpio" ]]; then
419 install_cpio "$stage1" "$install_archive"
420 unpack_result=$?
421
422 elif [[ "$filetype" == "flar" ]]; then
423 ( cd "$ZONEROOT" && install_flar < "$install_archive" )
424 unpack_result=$?
425
426 elif [[ "$filetype" == "xustar" ]]; then
427 install_pax "$install_archive"
428 unpack_result=$?
429
430 elif [[ "$filetype" == "ufsdump" ]]; then
431 install_ufsdump "$install_archive"
432 unpack_result=$?
433
434 elif [[ "$filetype" == "directory" ]]; then
435 install_dir "$source_dir"
436 unpack_result=$?
437 fi
438
439 # Clean up any fs mounts used during unpacking.
440 umnt_fs
441 rm -f $fstmpfile $ipdcpiofile $ipdpaxfile
442
443 #
444 # Do a sanity check to see if various things we think should be present
445 # are present. If not, the user might have supplied a cpio archive which was
446 # not created properly.
447 #
448 if (( $unpack_result == 0 )); then
449 sanity_check $ZONEROOT
450 if (( $? != 0 )); then
451 log "$sanity_fail"
452 log ""
453 log "$install_log" "$LOGFILE"
454 fatal "$install_fail" "$zonename"
455 else
456 vlog "$sanity_ok"
457 fi
458 fi
459
460 chmod 700 $zonepath
461
462 log "$p2ving"
463 vlog "running: p2v $OPT_V $unconfig_zone $backout $zonename $zonepath"
464
465 #
466 # Run p2v.
467 #
468 # Getting the output to the right places is a little tricky because what
469 # we want is for p2v to output in the same way the installer does: verbose
470 # messages to the log file always, and verbose messages printed to the
471 # user if the user passes -v. This rules out simple redirection. And
472 # we can't use tee or other tricks because they cause us to lose the
473 # return value from the p2v script due to the way shell pipelines work.
474 #
475 # The simplest way to do this seems to be to hand off the management of
476 # the log file to the p2v script. So we run p2v with -l to tell it where
477 # to find the log file and then reopen the log (O_APPEND) when p2v is done.
478 #
479 /usr/lib/brand/native/p2v -l "$LOGFILE" -m "$p2v_prog" \
480 $OPT_V $unconfig_zone $backout $zonename $zonepath
481 p2v_result=$?
482 exec 2>>$LOGFILE
483
484 if (( $p2v_result == 0 )); then
485 vlog "$p2v_done"
486 else
487 log "$p2v_fail"
488 log ""
489 log "$install_fail"
490 log "$install_log" "$LOGFILE"
491 exit $ZONE_SUBPROC_FATAL
492 fi
493
494 EXIT_CODE=$ZONE_SUBPROC_OK
495
496 log ""
497 log "$install_good" "$zonename"
498
499 if [[ -h $ZONEROOT/var || ! -d $ZONEROOT/var || -h $ZONEROOT/var/log ]]; then
500 log "$e_baddir" "/var/log"
501 exit $ZONE_SUBPROC_FATAL
502 fi
503
504 # Just in case the log directory isn't present...
505 if [[ ! -d "$logdir" ]]; then
506 if ! mkdir -p "$logdir" 2>/dev/null; then
507 log "$no_log" "$logdir"
508 fi
509 fi
510
511 if [[ ! -h $zone_logfile && ! -d $zone_logfile ]]; then
512 cp $LOGFILE $zone_logfile
513 fi
514 log "$install_log" "$zone_logfile"
515 rm -f $LOGFILE
516
517 exit 0