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 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 # Use is subject to license terms.
25 #
26
27 # Restrict executables to /bin, /usr/bin, /usr/sbin and /usr/sfw/bin
28 PATH=/bin:/usr/bin:/usr/sbin:/usr/sfw/bin
29
30 export PATH
31
32 # Setup i18n output
33 TEXTDOMAIN="SUNW_OST_OSCMD"
34 export TEXTDOMAIN
35
36 # Log passed arguments to file descriptor 2
37 log()
38 {
39 [[ -n $logfile ]] && echo "$@" >&2
40 }
41
42 #
43 # Send the provided printf()-style arguments to the screen and to the
44 # logfile.
45 #
46 screenlog()
47 {
48 typeset fmt="$1"
49 shift
50
51 printf "$fmt\n" "$@"
52 [[ -n $logfile ]] && printf "$fmt\n" "$@" >&2
53 }
54
55 # Print and log provided text if the shell variable "verbose_mode" is set
56 verbose()
57 {
58 [[ -n $verbose_mode ]] && echo "$@"
59 [[ -n $logfile ]] && [[ -n $verbose_mode ]] && echo "$@" >&2
60 }
61
62 unsupported_cpu=\
63 $(gettext "ERROR: Cannot install branded zone: processor must be %s-compatible")
64
65 cmd_not_found=$(gettext "Required command '%s' cannot be found!")
66 cmd_not_exec=$(gettext "Required command '%s' not executable!")
67 zone_initfail=$(gettext "Attempt to initialize zone '%s' FAILED.")
68 path_abs=$(gettext "Pathname specified to -d '%s' must be absolute.")
69
70 cmd_h=$(gettext "%s -z <zone name> %s -h")
71 cmd_full=\
72 $(gettext "%s -z <zone name> %s [-v | -s] [-d <dir>|<device>] [<cluster> ... ]")
73
74 both_modes=$(gettext "%s: error: cannot select both silent and verbose modes")
75
76 not_found=$(gettext "%s: error: file or directory not found.")
77
78 wrong_type=\
79 $(gettext "%s: error: must be a gzip, bzip2, .Z or uncompressed tar archive.")
80
81 not_readable=$(gettext "Cannot read file '%s'")
82
83 no_install=$(gettext "Could not create install directory '%s'")
84 no_log=$(gettext "Could not create log directory '%s'")
85 no_logfile=$(gettext "Could not create log file '%s'")
86
87 root_full=$(gettext "Zonepath root %s exists and contains data; remove or move aside prior to install.")
88
89 install_zone=$(gettext "Installing zone '%s' at root directory '%s'")
90 install_from=$(gettext "from archive '%s'")
91
92 install_fail=$(gettext "Installation of zone '%s' FAILED.")
93 see_log=$(gettext "See the log file:\n '%s'\nfor details.")
94
95 install_abort=$(gettext "Installation of zone '%s' aborted.")
96 install_good=$(gettext "Installation of zone '%s' completed successfully.")
97
98 # Check if commands passed in exist and are executable.
99 check_cmd()
100 {
101 for cmd in "$@"; do
102 if [[ ! -f $cmd ]]; then
103 screenlog "$cmd_not_found" "$cmd"
104 screenlog "$install_abort" "$zonename"
105 exit $ZONE_SUBPROC_NOTCOMPLETE
106 fi
107
108 if [[ ! -x $cmd ]]; then
109 screenlog "$cmd_not_exec" "$cmd"
110 screenlog "$install_abort" "$zonename"
111 exit $ZONE_SUBPROC_NOTCOMPLETE
112 fi
113 done
114 }
115
116 # Post process as tarball-installed zone for use by BrandZ.
117 init_tarzone()
118 {
119 typeset rootdir="$1"
120
121 if ! $branddir/lx_init_zone "$rootdir"; then
122 screenlog "$zone_initfail" "$zonename"
123 return 1
124 fi
125 }
126
127 # Clean up on interrupt
128 trap_cleanup()
129 {
130 msg=$(gettext "Installation cancelled due to interrupt.")
131
132 screenlog "$msg"
133 exit $int_code
134 }
135
136 #
137 # Output the usage message.
138 #
139 # This is done this way due to limitations in the way gettext strings are
140 # extracted from shell scripts and processed. Use of this somewhat awkward
141 # syntax allows us to produce longer lines of text than otherwise would be
142 # possible without wrapping lines across more than one line of code.
143 #
144 usage()
145 {
146 int_code=$ZONE_SUBPROC_USAGE
147
148 echo $(gettext "Usage:")
149 printf " $cmd_h\n" "zoneadm" "install"
150 printf " $cmd_full\n" "zoneadm" "install"
151
152 echo
153
154 echo $(gettext "The installer will attempt to use the default system") \
155 $(gettext "removable disc device if <archive dir> is not") \
156 $(gettext "specified.") | fmt -80
157
158 echo
159
160 echo $(gettext "<cluster> specifies which package cluster you wish") \
161 $(gettext "to install.") | fmt -80
162
163 echo
164 echo $(gettext "The 'desktop' cluster will be installed by default.")
165 echo
166 echo $(gettext "The available clusters are:")
167 echo " + core"
168 echo " + server"
169 echo " + desktop"
170 echo " + development"
171 echo " + all"
172 echo
173
174 echo $(gettext "Each cluster includes all of the clusters preceding") \
175 $(gettext "it, so the 'server' cluster includes the 'core'") \
176 $(gettext "cluster, the 'desktop' cluster includes the 'core'") \
177 $(gettext "and 'server' clusters, and so on.") | fmt -80
178
179 echo
180 echo $(gettext "Examples")
181 echo "========"
182
183 echo $(gettext "Example 1: Install a base Linux system from CDs or a") \
184 $(gettext "DVD using the system default removable disc device:") |
185 fmt -80
186
187 echo
188 echo " # zoneadm -z myzone install"
189 echo
190
191 echo $(gettext "Example 2: Install the 'server' cluster from CDs or") \
192 $(gettext "a DVD via an alternative removable disc device:") |
193 fmt -80
194
195 echo
196 echo " # zoneadm -z myzone install -d /cdrom/cdrom1 server"
197 echo
198
199 echo $(gettext "Example 3: Install the desktop Linux environment") \
200 $(gettext "from an ISO image made available as '/dev/lofi/1' by") \
201 $(gettext "use of lofiadm(1M):") | fmt -80
202
203 echo
204 echo " # zoneadm -z myzone install -d /dev/lofi/1 desktop"
205 echo
206
207 echo $(gettext "Example 4: Install the entire Linux environment from") \
208 $(gettext "ISO images located in the directory") \
209 "'/export/centos_3.8/isos':" | fmt -80
210
211 echo
212 echo " # zoneadm -z myzone install -d /export/centos_3.8/isos all"
213 echo
214
215 echo $(gettext "Example 5: Install from a compressed tar archive of") \
216 $(gettext "an existing Linux installation (a tar ball) with") \
217 $(gettext "verbose output regarding the progress of the") \
218 $(gettext "installation:") | fmt -80
219
220 echo
221 echo " # zoneadm -z myzone install -v -d /tmp/linux_full.tar.gz"
222 echo
223
224 echo $(gettext "Example 6: Install from a compressed tar archive of") \
225 $(gettext "an existing Linux installation (a tar ball) with NO") \
226 $(gettext "output regarding the progress of the installation") \
227 $(gettext "(silent mode.)") | fmt -80
228
229 echo
230
231 echo $(gettext "NOTE: Silent mode is only recommended for use by") \
232 $(gettext "shell scripts and other non-interactive programs:") |
233 fmt -80
234
235 echo
236 echo " # zoneadm -z myzone install -d /tmp/linux_full.tar.gz -s"
237 echo
238
239 exit $int_code
240 }
241
242 #
243 # The main body of the script starts here.
244 #
245 # This script should never be called directly by a user but rather should
246 # only be called by zoneadm to install a BrandZ Linux zone.
247 #
248
249 #
250 # Exit values used by the script, as #defined in <sys/zone.h>
251 #
252 # ZONE_SUBPROC_OK
253 # ===============
254 # Installation was successful
255 #
256 # ZONE_SUBPROC_USAGE
257 # ==================
258 # Improper arguments were passed, so print a usage message before exiting
259 #
260 # ZONE_SUBPROC_NOTCOMPLETE
261 # ========================
262 # Installation did not complete, but another installation attempt can be
263 # made without an uninstall
264 #
265 # ZONE_SUBPROC_FATAL
266 # ==================
267 # Installation failed and an uninstall will be required before another
268 # install can be attempted
269 #
270 ZONE_SUBPROC_OK=0
271 ZONE_SUBPROC_USAGE=253
272 ZONE_SUBPROC_NOTCOMPLETE=254
273 ZONE_SUBPROC_FATAL=255
274
275 #
276 # An unspecified exit or interrupt should exit with ZONE_SUBPROC_NOTCOMPLETE,
277 # meaning a user will not need to do an uninstall before attempting another
278 # install.
279 #
280 int_code=$ZONE_SUBPROC_NOTCOMPLETE
281
282 trap trap_cleanup INT
283
284 # If we weren't passed at least two arguments, exit now.
285 [[ $# -lt 2 ]] && usage
286
287 #
288 # This script is always started with a full path so we can extract the
289 # brand directory name here.
290 #
291 branddir=$(dirname "$0")
292 zonename="$1"
293 zoneroot="$2"
294
295 install_root="$zoneroot/root"
296 logdir="$install_root/var/log"
297
298 shift; shift # remove zonename and zoneroot from arguments array
299
300 unset gtaropts
301 unset install_opts
302 unset install_src
303 unset msg
304 unset silent_mode
305 unset verbose_mode
306
307 while getopts "d:hsvX" opt
308 do
309 case "$opt" in
310 h) usage;;
311 s) silent_mode=1;;
312 v) verbose_mode=1;;
313 d) install_src="$OPTARG" ;;
314 X) install_opts="$install_opts -x" ;;
315 *) usage;;
316 esac
317 done
318 shift OPTIND-1
319
320 # Providing more than one passed argument generates a usage message
321 if [[ $# -gt 1 ]]; then
322 msg=$(gettext "ERROR: Too many arguments provided:")
323
324 screenlog "$msg"
325 screenlog " \"%s\"" "$@"
326 screenlog ""
327 usage
328 fi
329
330 # Validate any free-form arguments
331 if [[ $# -eq 1 && "$1" != "core" && "$1" != "server" && "$1" != "desktop" &&
332 "$1" != "development" && "$1" != "all" ]]; then
333 msg=$(gettext "ERROR: Unknown cluster name specified: %s")
334
335 screenlog "$msg" "\"$1\""
336 screenlog ""
337 usage
338 fi
339
340 # The install can't be both verbose AND silent...
341 if [[ -n $silent_mode && -n $verbose_mode ]]; then
342 screenlog "$both_modes" "zoneadm install"
343 screenlog ""
344 usage
345 fi
346
347 #
348 # Validate that we're running on a i686-compatible CPU; abort the zone
349 # installation now if we're not.
350 #
351 procinfo=$(LC_ALL=C psrinfo -vp | grep family)
352
353 #
354 # All x86 processors in CPUID families 6, 15, 16 or 17 should be
355 # i686-compatible, assuming third party processor vendors follow AMD and
356 # Intel's lead.
357 #
358 if [[ "$procinfo" != *" x86 "* ]] ||
359 [[ "$procinfo" != *" family 6 "* && "$procinfo" != *" family 15 "* &&
360 "$procinfo" != *" family 16 "* && "$procinfo" != *" family 17 "* ]] ; then
361 screenlog "$unsupported_cpu" "i686"
362 exit $int_code
363 fi
364
365 if [[ -n $install_src ]]; then
366 #
367 # Validate $install_src.
368 #
369 # If install_src is a directory, assume it contains ISO images to
370 # install from, otherwise treat the argument as if it points to a tar
371 # ball file.
372 #
373 if [[ "`echo $install_src | cut -c 1`" != "/" ]]; then
374 screenlog "$path_abs" "$install_src"
375 exit $int_code
376 fi
377
378 if [[ ! -a "$install_src" ]]; then
379 screenlog "$not_found" "$install_src"
380 screenlog "$install_abort" "$zonename"
381 exit $int_code
382 fi
383
384 if [[ ! -r "$install_src" ]]; then
385 screenlog "$not_readable" "$install_src"
386 screenlog "$install_abort" "$zonename"
387 exit $int_code
388 fi
389
390 #
391 # If install_src is a block device, a directory, a possible device
392 # created via lofiadm(1M), or the directory used by a standard volume
393 # management daemon, pass it on to the secondary install script.
394 #
395 # Otherwise, validate the passed filename to prepare for a tar ball
396 # install.
397 #
398 if [[ ! -b "$install_src" && ! -d "$install_src" &&
399 "$install_src" != /dev/lofi/* && "$install_src" != /cdrom/* &&
400 "$install_src" != /media/* ]]; then
401 if [[ ! -f "$install_src" ]]; then
402 screenlog "$wrong_type" "$install_src"
403 screenlog "$install_abort" "$zonename"
404 exit $int_code
405 fi
406
407 filetype=`{ LC_ALL=C file $install_src |
408 awk '{print $2}' ; } 2>/dev/null`
409
410 if [[ "$filetype" = "gzip" ]]; then
411 verbose "\"$install_src\": \"gzip\" archive"
412 gtaropts="-xz"
413 elif [[ "$filetype" = "bzip2" ]]; then
414 verbose "\"$install_src\": \"bzip2\" archive"
415 gtaropts="-xj"
416 elif [[ "$filetype" = "compressed" ]]; then
417 verbose "\"$install_src\": Lempel-Ziv" \
418 "compressed (\".Z\") archive."
419 gtaropts="-xZ"
420 elif [[ "$filetype" = "USTAR" ]]; then
421 verbose "\"$install_src\":" \
422 "uncompressed (\"tar\") archive."
423 gtaropts="-x"
424 else
425 screenlog "$wrong_type" "$install_src"
426 screenlog "$install_abort" "$zonename"
427 exit $int_code
428 fi
429 fi
430 fi
431
432 #
433 # Start silent operation and pass the flag to prepare pass the flag to
434 # the ISO installer, if needed.
435 #
436 if [[ -n $silent_mode ]]
437 then
438 exec 1>/dev/null
439 install_opts="$install_opts -s"
440 fi
441
442 #
443 # If verbose mode was specified, pass the verbose flag to lx_distro_install
444 # for ISO or disc installations and to gtar for tarball-based installs.
445 #
446 if [[ -n $verbose_mode ]]
447 then
448 echo $(gettext "Verbose output mode enabled.")
449 install_opts="$install_opts -v"
450 [[ -n $gtaropts ]] && gtaropts="${gtaropts}v"
451 fi
452
453 [[ -n $gtaropts ]] && gtaropts="${gtaropts}f"
454
455 if [[ ! -d "$install_root" ]]
456 then
457 if ! mkdir -p "$install_root" 2>/dev/null; then
458 screenlog "$no_install" "$install_root"
459 exit $int_code
460 fi
461 fi
462
463 #
464 # Check for a non-empty root.
465 #
466 cnt=`ls $install_root | wc -l`
467 if [ $cnt -ne 0 ]; then
468 screenlog "$root_full" "$install_root"
469 exit $int_code
470 fi
471
472 if [[ ! -d "$logdir" ]]
473 then
474 if ! mkdir -p "$logdir" 2>/dev/null; then
475 screenlog "$no_log" "$logdir"
476 exit $int_code
477 fi
478 fi
479
480 logfile="${logdir}/$zonename.install.$$.log"
481
482 if ! > $logfile; then
483 screenlog "$no_logfile" "$logfile"
484 exit $int_code
485 fi
486
487 # Redirect stderr to the log file to automatically log any error messages
488 exec 2>>"$logfile"
489
490 #
491 # From here on out, an unspecified exit or interrupt should exit with
492 # ZONE_SUBPROC_FATAL, meaning a user will need to do an uninstall before
493 # attempting another install, as we've modified the directories we were going
494 # to install to in some way.
495 #
496 int_code=$ZONE_SUBPROC_FATAL
497
498 log "Installation started for zone \"$zonename\" `/usr/bin/date`"
499
500 if [[ -n $gtaropts ]]; then
501 check_cmd /usr/sfw/bin/gtar $branddir/lx_init_zone
502
503 screenlog "$install_zone" "$zonename" "$zoneroot"
504 screenlog "$install_from" "$install_src"
505 echo
506 echo $(gettext "This process may take several minutes.")
507 echo
508
509 if ! ( cd "$install_root" && gtar "$gtaropts" "$install_src" ) ; then
510 log "Error: extraction from tar archive failed."
511 else
512 if ! [[ -d "${install_root}/bin" &&
513 -d "${install_root}/sbin" ]]; then
514 log "Error: improper or incomplete tar archive."
515 else
516 $branddir/lx_init_zone "$install_root" &&
517 init_tarzone "$install_root"
518
519 #
520 # Emit the same code from here whether we're
521 # interrupted or exiting normally.
522 #
523 int_code=$?
524 fi
525 fi
526
527 if [[ $int_code -eq ZONE_SUBPROC_OK ]]; then
528 log "Tar install completed for zone '$zonename' `date`."
529 else
530 log "Tar install failed for zone \"$zonename\" `date`."
531
532 fi
533 else
534 check_cmd $branddir/lx_distro_install
535
536 $branddir/lx_distro_install -z "$zonename" -r "$zoneroot" \
537 -d "$install_src" -l "$logfile" $install_opts "$@"
538
539 #
540 # Emit the same code from here whether we're interrupted or exiting
541 # normally.
542 #
543 int_code=$?
544
545 [[ $int_code -eq $ZONE_SUBPROC_USAGE ]] && usage
546 fi
547
548 if [[ $int_code -ne $ZONE_SUBPROC_OK ]]; then
549 screenlog ""
550 screenlog "$install_fail" "$zonename"
551 screenlog ""
552
553 #
554 # Only make a reference to the log file if one will exist after
555 # zoneadm exits.
556 #
557 [[ $int_code -ne $ZONE_SUBPROC_NOTCOMPLETE ]] &&
558 screenlog "$see_log" "$logfile"
559
560 exit $int_code
561 fi
562
563 #
564 # After the install completes, we've likely moved a new copy of the logfile into
565 # place atop the logfile we WERE writing to, so if we don't reopen the logfile
566 # here the shell will continue writing to the old logfile's inode, meaning we
567 # would lose all log information from this point on.
568 #
569 exec 2>>"$logfile"
570
571 screenlog ""
572 screenlog "$install_good" "$zonename"
573 screenlog ""
574
575 echo $(gettext "Details saved to log file:")
576 echo " \"$logfile\""
577 echo
578
579 exit $ZONE_SUBPROC_OK