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