1#!/bin/sh
2#-
3# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4#
5# Copyright (c) 2010 iXsystems, Inc.  All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28# $FreeBSD$
29
30# functions.sh
31# Library of functions which pc-sysinstall may call upon
32
33# Function which displays the help-index file
34display_help()
35{
36  if [ -e "${PROGDIR}/doc/help-index" ]
37  then
38    cat ${PROGDIR}/doc/help-index
39  else
40    echo "Error: ${PROGDIR}/doc/help-index not found"
41    exit 1
42  fi
43};
44
45# Function which displays the help for a specified command
46display_command_help()
47{
48  if [ -z "$1" ]
49  then
50    echo "Error: No command specified to display help for"
51    exit 1
52  fi
53
54  if [ -e "${PROGDIR}/doc/help-${1}" ]
55  then
56    cat ${PROGDIR}/doc/help-${1}
57  else
58    echo "Error: ${PROGDIR}/doc/help-${1} not found"
59    exit 1
60  fi
61};
62
63# Function to convert bytes to megabytes
64convert_byte_to_megabyte()
65{
66  if [ -z "${1}" ]
67  then
68    echo "Error: No bytes specified!"
69    exit 1
70  fi
71
72  expr -e ${1} / 1048576
73};
74
75# Function to convert blocks to megabytes
76convert_blocks_to_megabyte()
77{
78  if [ -z "${1}" ] ; then
79    echo "Error: No blocks specified!"
80    exit 1
81  fi
82
83  expr -e ${1} / 2048
84};
85
86# Takes $1 and strips the whitespace out of it, returns VAL
87strip_white_space()
88{
89  if [ -z "${1}" ]
90  then
91    echo "Error: No value setup to strip whitespace from!"
92
93    exit 1
94  fi
95
96  export VAL=`echo "$1" | tr -d ' '`
97};
98
99# Displays an error message and exits with error 1
100exit_err()
101{
102  # Echo the message for the users benefit
103  echo "EXITERROR: $1"
104
105  # Save this error to the log file
106  echo "EXITERROR: ${1}" >>$LOGOUT
107
108  # Check if we need to unmount any file-systems after this failure
109  unmount_all_filesystems_failure
110
111  echo "For more details see log file: $LOGOUT"
112
113  exit 1
114};
115
116# Run-command, don't halt if command exits with non-0
117rc_nohalt()
118{
119  CMD="$1"
120
121  if [ -z "${CMD}" ]
122  then
123    exit_err "Error: missing argument in rc_nohalt()"
124  fi
125
126  echo "Running: ${CMD}" >>${LOGOUT}
127  ${CMD} >>${LOGOUT} 2>>${LOGOUT}
128
129};
130
131# Run-command, halt if command exits with non-0
132rc_halt()
133{
134  CMD="$1"
135
136  if [ -z "${CMD}" ]
137  then
138    exit_err "Error: missing argument in rc_halt()"
139  fi
140
141  echo "Running: ${CMD}" >>${LOGOUT}
142  eval ${CMD} >>${LOGOUT} 2>>${LOGOUT}
143  STATUS="$?"
144  if [ "${STATUS}" != "0" ]
145  then
146    exit_err "Error ${STATUS}: ${CMD}"
147  fi
148};
149
150# Run-command w/echo to screen, halt if command exits with non-0
151rc_halt_echo()
152{
153  CMD="$1"
154
155  if [ -z "${CMD}" ]
156  then
157    exit_err "Error: missing argument in rc_halt_echo()"
158  fi
159
160  echo "Running: ${CMD}" >>${LOGOUT}
161  ${CMD} 2>&1 | tee -a ${LOGOUT}
162  STATUS="$?"
163  if [ "$STATUS" != "0" ]
164  then
165    exit_err "Error ${STATUS}: $CMD"
166  fi
167
168};
169
170# Run-command w/echo, don't halt if command exits with non-0
171rc_nohalt_echo()
172{
173  CMD="$1"
174
175  if [ -z "${CMD}" ]
176  then
177    exit_err "Error: missing argument in rc_nohalt_echo()"
178  fi
179
180  echo "Running: ${CMD}" >>${LOGOUT}
181  ${CMD} 2>&1 | tee -a ${LOGOUT}
182
183};
184
185# Echo to the screen and to the log
186echo_log()
187{
188  STR="$1"
189
190  if [ -z "${STR}" ]
191  then
192    exit_err "Error: missing argument in echo_log()"
193  fi
194
195  echo "${STR}" | tee -a ${LOGOUT}
196};
197
198# Make sure we have a numeric
199is_num()
200{
201  expr $1 + 1 2>/dev/null
202  return $?
203}
204
205# Function which uses "fetch" to download a file, and display a progress report
206fetch_file()
207{
208
209  FETCHFILE="$1"
210  FETCHOUTFILE="$2"
211  EXITFAILED="$3"
212
213  EXITFILE="${TMPDIR}/.fetchExit"
214
215  rm ${FETCHOUTFILE} 2>/dev/null >/dev/null
216
217  SIZE=$(( `fetch -s "${FETCHFILE}"` / 1024 ))
218  echo "FETCH: ${FETCHFILE}"
219  echo "FETCH: ${FETCHOUTFILE}" >>${LOGOUT}
220
221  ( fetch -o ${FETCHOUTFILE} "${FETCHFILE}" >/dev/null 2>/dev/null ; echo "$?" > ${EXITFILE} ) &
222  PID="$!"
223  while
224  z=1
225  do
226
227    if [ -e "${FETCHOUTFILE}" ]
228    then
229      DSIZE=`du -k ${FETCHOUTFILE} | tr -d '\t' | cut -d '/' -f 1`
230      if [ $(is_num "$DSIZE") ] ; then
231      if [ $SIZE -lt $DSIZE ] ; then DSIZE="$SIZE"; fi
232    	echo "SIZE: ${SIZE} DOWNLOADED: ${DSIZE}"
233    	echo "SIZE: ${SIZE} DOWNLOADED: ${DSIZE}" >>${LOGOUT}
234      fi
235    fi
236
237    # Check if the download is finished
238    ps -p ${PID} >/dev/null 2>/dev/null
239    if [ $? -ne 0 ]
240    then
241      break;
242    fi
243
244    sleep 2
245  done
246
247  echo "FETCHDONE"
248
249  EXIT="`cat ${EXITFILE}`"
250  if [ "${EXIT}" != "0" -a "$EXITFAILED" = "1" ]
251  then
252    exit_err "Error: Failed to download ${FETCHFILE}"
253  fi
254
255  return $EXIT
256
257};
258
259# Function to return a the zpool name for this device
260get_zpool_name()
261{
262  DEVICE="$1"
263
264  # Set the base name we use for zpools
265  BASENAME="tank"
266
267  if [ ! -d "${TMPDIR}/.zpools" ] ; then
268    mkdir -p ${TMPDIR}/.zpools
269  fi
270
271  if [ -e "${TMPDIR}/.zpools/${DEVICE}" ] ; then
272    cat ${TMPDIR}/.zpools/${DEVICE}
273    return 0
274  else
275    # Need to generate a zpool name for this device
276    NUM=`ls ${TMPDIR}/.zpools/ | wc -l | sed 's| ||g'`
277
278    # Is it used in another zpool?
279    while :
280    do
281      NEWNAME="${BASENAME}${NUM}"
282      zpool list | grep -qw "${NEWNAME}"
283      local chk1=$?
284      zpool import | grep -qw "${NEWNAME}"
285      local chk2=$?
286      if [ $chk1 -eq 1 -a $chk2 -eq 1 ] ; then break ; fi
287      NUM=$((NUM+1))
288    done
289
290    # Now save the new tank name
291    mkdir -p ${TMPDIR}/.zpools/`dirname $DEVICE`
292    echo "$NEWNAME" >${TMPDIR}/.zpools/${DEVICE}
293    echo "${NEWNAME}"
294    return 0
295  fi
296};
297
298iscompressed()
299{
300  local FILE
301  local RES
302
303  FILE="$1"
304  RES=1
305
306  if echo "${FILE}" | \
307    grep -qiE '\.(Z|lzo|lzw|lzma|gz|bz2|xz|zip)$' 2>&1
308  then
309    RES=0
310  fi
311
312  return ${RES}
313}
314
315get_compression_type()
316{
317  local FILE
318  local SUFFIX
319
320  FILE="$1"
321  SUFFIX=`echo "${FILE}" | sed -E 's|^(.+)\.(.+)$|\2|'`
322
323  VAL=""
324  SUFFIX=`echo "${SUFFIX}" | tr A-Z a-z`
325  case "${SUFFIX}" in
326    z) VAL="lzw" ;;
327    lzo) VAL="lzo" ;;
328    lzw) VAL="lzw" ;;
329    lzma) VAL="lzma" ;;
330    gz) VAL="gzip" ;;
331    bz2) VAL="bzip2" ;;
332    xz) VAL="xz" ;;
333    zip) VAL="zip" ;;
334  esac
335
336  export VAL
337}
338
339write_image()
340{
341  local DEVICE_FILE
342
343  IMAGE_FILE="$1"
344  DEVICE_FILE="$2"
345
346  if [ -z "${IMAGE_FILE}" ]
347  then
348    exit_err "ERROR: Image file not specified!"
349  fi
350
351  if [ -z "${DEVICE_FILE}" ]
352  then
353    exit_err "ERROR: Device file not specified!"
354  fi
355
356  if [ ! -f "${IMAGE_FILE}" ]
357  then
358    exit_err "ERROR: '${IMAGE_FILE}' does not exist!"
359  fi
360
361  DEVICE_FILE="${DEVICE_FILE#/dev/}"
362  DEVICE_FILE="/dev/${DEVICE_FILE}"
363
364  if [ ! -c "${DEVICE_FILE}" ]
365  then
366    exit_err "ERROR: '${DEVICE_FILE}' is not a character device!"
367  fi
368
369  if iscompressed "${IMAGE_FILE}"
370  then
371	local COMPRESSION
372
373    get_compression_type "${IMAGE_FILE}"
374	COMPRESSION="${VAL}"
375
376    case "${COMPRESSION}" in
377      lzw)
378        rc_halt "uncompress ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
379        IMAGE_FILE="${IMAGE_FILE%.Z}"
380        ;;
381
382      lzo)
383        rc_halt "lzop -d $IMAGE_{FILE} -c | dd of=${DEVICE_FILE}"
384        IMAGE_FILE="${IMAGE_FILE%.lzo}"
385        ;;
386
387      lzma)
388        rc_halt "lzma -d ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
389        IMAGE_FILE="${IMAGE_FILE%.lzma}"
390        ;;
391
392      gzip)
393        rc_halt "gunzip ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
394        IMAGE_FILE="${IMAGE_FILE%.gz}"
395        ;;
396
397      bzip2)
398        rc_halt "bunzip2 ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
399        IMAGE_FILE="${IMAGE_FILE%.bz2}"
400        ;;
401
402      xz)
403        rc_halt "xz -d ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
404        IMAGE_FILE="${IMAGE_FILE%.xz}"
405        ;;
406
407      zip)
408        rc_halt "unzip ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
409        IMAGE_FILE="${IMAGE_FILE%.zip}"
410        ;;
411
412      *)
413        exit_err "ERROR: ${COMPRESSION} compression is not supported"
414        ;;
415    esac
416
417  else
418    rc_halt "dd if=${IMAGE_FILE} of=${DEVICE_FILE}"
419
420  fi
421};
422
423# Setup and install on a new disk / partition
424install_fresh()
425{
426  # Lets start setting up the disk slices now
427  setup_disk_slice
428
429  if [ -z "${ROOTIMAGE}" ]
430  then
431
432    # Disk setup complete, now lets parse WORKINGSLICES and setup the bsdlabels
433    setup_disk_label
434
435    # Now we've setup the bsdlabels, lets go ahead and run newfs / zfs
436    # to setup the filesystems
437    setup_filesystems
438
439    # Lets mount the partitions now
440    mount_all_filesystems
441
442    # We are ready to begin extraction, lets start now
443    init_extraction
444
445    # Check if we have any optional modules to load
446    install_components
447
448    # Check if we have any packages to install
449    install_packages
450
451    # Do any localization in configuration
452    run_localize
453
454    # Save any networking config on the installed system
455    save_networking_install
456
457    # Now add any users
458    setup_users
459
460    # Do any last cleanup / setup before unmounting
461    run_final_cleanup
462
463    # Now run any commands specified
464    run_commands
465
466    # Unmount and finish up
467    unmount_all_filesystems
468  fi
469
470  echo_log "Installation finished!"
471};
472
473# Extract the system to a pre-mounted directory
474install_extractonly()
475{
476  # We are ready to begin extraction, lets start now
477  init_extraction
478
479  # Check if we have any optional modules to load
480  install_components
481
482  # Check if we have any packages to install
483  install_packages
484
485  # Do any localization in configuration
486  run_localize
487
488  # Save any networking config on the installed system
489  save_networking_install
490
491  # Now add any users
492  setup_users
493
494  # Now run any commands specified
495  run_commands
496
497  # Set a hostname on the install system
498  setup_hostname
499
500  # Set the root_pw if it is specified
501  set_root_pw
502
503  echo_log "Installation finished!"
504};
505
506install_image()
507{
508  # We are ready to begin extraction, lets start now
509  init_extraction
510
511  echo_log "Installation finished!"
512};
513
514install_upgrade()
515{
516  # We're going to do an upgrade, skip all the disk setup
517  # and start by mounting the target drive/slices
518  mount_upgrade
519
520  # Start the extraction process
521  init_extraction
522
523  # Do any localization in configuration
524  run_localize
525
526  # Now run any commands specified
527  run_commands
528
529  # Merge any old configuration files
530  merge_old_configs
531
532  # Check if we have any optional modules to load
533  install_components
534
535  # Check if we have any packages to install
536  install_packages
537
538  # All finished, unmount the file-systems
539  unmount_upgrade
540
541  echo_log "Upgrade finished!"
542};
543