#!/bin/sh ####################################################################### # Copyright (C) 2008-2017 by NetApp. All Rights Reserved. # ####################################################################### # # Command usage function # usage() { if [ ${PRINT_USAGE} -eq 1 ]; then msg "Usage: INSTALL [-check] [-1] update|copy|install image1|image2 dir|tarball [dev]" msg " -check - will perform everything up to the point of writing to the device" msg " -1 - will cause install to install only on the specified image" msg " update|copy|install - operation to perform" msg " image1|image2 - the currently running image" msg " dir|tarball - the location of the files to install or a gzipped tarball" msg " dev - optional device containing the boot device used for the" msg " install operation" fi } # For managing firmware environment variables! . /etc/netapp_common.subr # # Register a handler for sigint to cleanup temp files SIG="INT" trap "sig_handler ${SIG}" SIG${SIG} SIG="TERM" trap "sig_handler ${SIG}" SIG${SIG} # This is a list of files that will be installed to the boot device. The # list may be modified at run time. The list will also be used in free space # calculations. INSTALL_FILES="BUILD CHECKSUM COMPAT.TXT INSTALL VERSION cap.xml fw.tgz kernel metadata.xml platform.ko rootfs.img key_fingerprint.sig kernel.sig platform.ko.sig rootfs.img.sig" # This is a list of files to be extracted to facilitate checks (metadata, free # space, etc) before the actual installation PRE_EXTRACT_FILES="BUILD CHECKSUM COMPAT.TXT VERSION metadata.xml netapp_external_keymanager_check netapp_nodar_build_check" # This is the diagnostic executable file name; that will be invoked by loader. DIAG_EXEC_RUNTIME_NAME="bzImage" # image.tgz root path that contains all files IMAGE_ROOT="netboot" FAST_CHKSUM_ALGO_WORK_DIR="/mroot/etc/software/csdir" # Create a temp working directory TMP=$(mktemp -d /tmp/install.tmp.XXXXX) # While this is set, any encounterred errors will be treated as usage errors PRINT_USAGE=1 ramdisk_created=0 FDISK=fdisk DISKINFO=diskinfo # Define a mount point for the boot device if [ -n "${CFCARD_MNTPT}" ]; then CF_MNT=${CFCARD_MNTPT} else # CFCARD_MNTPT might not be defined if coming from older (eg: tricky) # systems, so provide a default of /cfcard CF_MNT="/cfcard" SetCFcardMntPt "${CF_MNT}" fi # # Convert to uppercase # toupper() { echo $1 | sed y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIGKLMNOPQRSTUVWXYZ/ } # # Trim leading and trailing white space # trim() { echo $1 | sed 's/^[ \t]*//' | sed 's/[ \t]*$//' } # # Check for mounted file system # Input: $1 --- name of file system, such as "/cfcard" # is_mounted() { local file_system=$1 if [ ! -d "$file_system" ]; then # error due to missing input for file system exit 1 fi df -k $file_system | grep -q $file_system } # # Check whether it is performing boot recovery. # is_boot_recovery() { local boot_src=$(get_kenv EXTERNAL_BOOT_DEV) boot_src=$(toupper ${boot_src}) local booted_from=$(get_kenv BOOTED_FROM) booted_from=$(toupper ${booted_from}) if [ "${booted_from}" = "NETWORK" -a "${boot_src}" = "SEC" ]; then # It is performing boot recovery using image on the recovery # disk or partition. msg "Boot recovery operation." return 0 fi return 1; } # # Get available disk space of a file system # The available disk space is the fourth output column of 'df' command. # Example: # or-094% df -k /cfcard # Filesystem 1024-blocks Used Avail Capacity Mounted on # /dev/da0s1 7319208 1110840 6208368 15% /cfcard # # or-094% df -k /cfcard | grep /cfcard | awk '{print $4}' # 6208368 # # Input: $1 --- name of file system # fs_available_space() { local file_system=$1 if [ ! -d "$file_system" ]; then # error due to missing input for file system exit 1 fi df -k $file_system | grep $file_system | awk '{print $4}' } # # Get file system size # The number of blocks of a file system is the second output column of 'df' command. # Example: # or-094% df -k /cfcard # Filesystem 1024-blocks Used Avail Capacity Mounted on # /dev/da0s1 7319208 1110840 6208368 15% /cfcard # # or-094% df -k /cfcard | grep /cfcard | awk '{print $2}' # 7319208 # # Input: $1 --- name of file system # fs_size() { local file_system=$1 if [ ! -d "$file_system" ]; then # error due to missing input for file system exit 1 fi df -k $file_system | grep $file_system | awk '{print $2}' } # # Make a directory # NOTE: this now assumes it is passed an absolute path (which has # always been true). See burt 295028 for why this was changed. # create_dir() { if [ ! -d $1 ]; then # Don't use mkdir -p on our /cfcard partitions due to burt 295028 cd / for dir in `echo $1 | tr "/" " "` do mkdir $dir > /dev/null 2>&1 cd $dir > /dev/null 2>&1 done cd / if [ ! -d $1 ]; then msg "Unable to create the directory: $1" return 1 fi msg "Directory $1 created" fi return 0 } # # Copy files( src, dest, file_list ) # # If installing from a tarball, the souce directory is ignored. copy_files() { local srcdir=$1 shift local dstdir=$1 shift local file_list="$@" # Give the new partition table a chance to get to the device # and let any pending I/O settle down. msg "Syncing device..." sync ; sleep 3 if [ "${tarball}" ]; then msg "Extracting to $dstdir..." tar -qxvzf "${tarball}" -C $dstdir $file_list if [ $? -ne 0 ]; then msg "Unable to extract '$(basename "${tarball}")' to $dstdir - this is VERY bad" return 1 fi # move all installed files to /cfcard image top directory # then remove the tarball image top directory which should be empty now mv -f $dstdir/$IMAGE_ROOT/* $dstdir rm -rf $dstdir/$IMAGE_ROOT else for file in $file_list; do msg "Copying $file from $srcdir to $dstdir..." cp $srcdir/$file $dstdir if [ $? -ne 0 ]; then msg "Unable to copy the file $srcdir/$file to $dstdir - this is VERY bad" return 1 fi done fi sync ; sleep 3 touch $dstdir/_installtime return 0 } write_fdisk_config() { echo "${FDISK_SCRIPT}" > ${TMP_MNT}/dskfmt } write_fdisk_secondary_config() { echo "${FDISK_SECONDARY_SCRIPT}" > ${TMP_MNT}/dskfmt } # # Get a kernel environment variable # get_kenv() { kenv $1 2> /dev/null return $? } # # Clean up some state, then exit. # This allows install to be called again (specifically during # a netboot), if it happens to fail the first time. # cleanup() { if [ -d "${TMP}" ]; then # Cleanup temp files rm -rf "${TMP}" fi if [ -d "${CF_MNT}/boot" ]; then rm -rf "${CF_MNT}/boot" fi if [ -d "${FAST_CHKSUM_ALGO_WORK_DIR}" ]; then rm -rf "${FAST_CHKSUM_ALGO_WORK_DIR}" fi if [ ${ramdisk_created} -eq 1 ]; then # toss out the ramdisk umount $TMP_MNT mdconfig -d -u$mddev elif [ -n "$TMP_MNT" ]; then # if $TMP_MNT has been used, try to unmmount it # depending on when this is called, it could be a # ramdisk, or a boot device, or not even mounted at all. umount $TMP_MNT 2> /dev/null fi if [ "${op}" = "install" ]; then umount $CF_MNT 2> /dev/null fi sync } cleanup_and_exit() { cleanup exit $1 } # # Error and exit function # err_exit() { echo "ERROR: $1." >&2 usage cleanup_and_exit 1 } sig_handler() { # Which signal did we catch? local sig=$1 # unregister trap handler, revert to default handler. trap SIG${sig} msg "Exiting due to signal ${sig}." # delete temp files, etc cleanup # exit by invoking the default handler, which will tell the parent we # were killed by a signal. kill -s SIG${sig} $$ } # # Output a message # msg() { echo "$1" } # # Compute a numeric representation of the major and minor # portions of the version string. # compute_version() { local version=0 local tmp=`echo $1 | sed 's/[a-zA-Z].*__//'` tmp=`echo $tmp | sed 's/[a-zA-Z].*//'` local major=`echo $tmp | awk -F. '{printf("%d", $1)}'` local minor=`echo $tmp | awk -F. '{printf("%d", $2)}'` if [ -z $major ]; then major=0 fi # Data ONTAP 8(BR) is greater than 10(Tricky), so 8 becomes 80. # If there is a version 11 or greater, it must also be greater than # version 8, so 11 becomes 110. if [ $major -gt 7 -a \ $major -lt 10 -o \ $major -ge 11 ]; then major=`expr $major \* 10` fi version=`expr $version \* 100 + $major` if [ -z $minor ]; then minor=0 fi version=`expr $version \* 100 + $minor` return $version } # Notify the dblade if a new version of Data ONTAP has been installed # This information is used for NDU data mining. notify_dblade() { # Compare the new BUILD and VERSION files with what ever is in the # root directory of the currently running build. Any differences # should trigger the "ndu_takeover" flag to be set within the dblade. diff -q /BUILD $otherdir/BUILD > /dev/null 2>&1 rc_diff_build=$? diff -q /VERSION $otherdir/VERSION > /dev/null 2>&1 rc_diff_vers=$? if [ $rc_diff_build -ne 0 -o \ $rc_diff_vers -ne 0 ]; then # OK to fail. zsmcli might not exist (Tricky), or # dblade might not be loaded (install from boot menu). # ignore any errors. zsmcli "system-set-new-ontap-version-downloaded" > /dev/null 2>&1 || true fi return 0 } # # Brand Check is used to verify the software brand matches the # branding of the underlying hardware. # # Input: BUILD file will be used to check for the software branding name # # The logic works as folows: # 1) The BUILD file will be used to check for the software branding # 2) The hardware branding is obtainied from the SYS_MODEL environment variable # 3) If a mismatch is detected, abort the installation. # brand_check() { buildfile=$1 # The SwBrand, software branding, is obtained from the TYPE field in the BUILD file. # The TYPE field can be one of the following: # TYPE: # TYPE: IBM # TYPE: SIM # TYPE: REGULAR # TYPE: DEBUG # TYPE: DEBUG IBM # TYPE: DEBUG SIM # TYPE: DEBUG REGULAR # We only care about "IBM" brand. Every thing else is treated as null string. if grep 'TYPE:.\+IBM' ${buildfile} >/dev/null ; then SwBrand="IBM" SwType="IBM ONTAP" else SwType="NetApp ONTAP" fi # The HwBrand, hardware branding, is obtained from the SYS_MODEL environment variable. # The SYS_MODEL can be one of the following: # IBM: # IBM-XXXX # NetApp: # FASXXXX # VSIM: # SIMBOX # VSA: # DOvXXX # FCvXXX # We only care about "IBM" brand. Every thing else is treated as null string. if get_kenv SYS_MODEL | grep "IBM" >/dev/null ; then HwBrand="IBM" HwType="product of IBM" else HwType="product of NetApp" fi # branding check if [ "${SwBrand}" != "${HwBrand}" ]; then err_exit "Cannot install ${SwType} on ${HwType}" fi } # Prefix the string found in $1 to each string found in $2 ... $n prefix_pattern() { PREFIX=$1 shift for fitem in $@; do echo -n "${PREFIX}${fitem} " done } # # If it's a tarball then untar a few things to temp. # pre_extract() { if [ "${tarball}" ]; then local extract_file_list="$(prefix_pattern "${IMAGE_ROOT}/" ${PRE_EXTRACT_FILES})" tar -qxzf "$tarball" -C ${TMP} ${extract_file_list} || err_exit "Unable to extract '$(basename "${tarball}")' to ${TMP}" newdir="${TMP}/${IMAGE_ROOT}" filetype=`tar -qxf "$tarball" -O ${IMAGE_ROOT}/kernel | file -` else filetype=$(file $newdir/kernel) fi } # # Ensure that kernel matches the machine type # kernel_arch_check() { case $machine in x86_64) echo $filetype | grep -q 'x86-64' if [ $? -eq 1 ]; then err_exit "Attempt to install an invalid kernel binary on target hardware: $filetype" fi ;; x86) echo $filetype | grep -q 'Intel 80386' if [ $? -eq 1 ]; then err_exit "Attempt to install an invalid kernel binary on target hardware: $filetype" fi ;; *) err_exit "Invalid machine type: $machine" ;; esac msg "Kernel binary matches install machine type" } # # Hostname Check is used to verify that the current node hostname # does not contain any potentially problematic characters that are # not supported by the new Data ONTAP version, preventing it from # booting. # # Specifically, this check limits the hostname to A-Z, a-z, 0-9, # "-" and "_". Hostname must begin with either a letter or number # per RFC 1123 requirements. During early boot, hostname can also # be null, which we allow as it will be set later. # hostname_check() { hostname=$(/bin/hostname) if [ "$hostname" = "" ]; then msg "hostname not currently set, skipping check" else echo "$hostname" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}$' if [ $? -ne 0 ]; then err_exit "Invalid local hostname '$hostname'. The hostname can only contain the following characters: A-Z, a-z, 0-9, \"-\" and \"_\", must begin with a letter or a digit, and be at most 63 characters. Use the \"system node rename\" command to rename the node before installing this image" fi fi } # # Ensure that the file checksums match # # parameters are: # -package # verify checksums in the package # -dir path # verify the checksums in the specified directory path # -fw # verify checksums from the fw.tgz package # verify_checksums() { local source if [ "$1" = "-package" ]; then check_package="1" source="Package" checksum_dir="$newdir" checksum_file="${checksum_dir}/CHECKSUM" # Grab just the file name from the list of MD5SUMs: # "MD5 (rootfs.img) = e5d7e73a4321cb1f941b14c9d0fc3644" --> "rootfs.img" files_to_checksum=$(cut -d' ' -f2 ${checksum_file}) || err_exit "${checksum_file} file not found" files_to_checksum=$(echo ${files_to_checksum} | sed 's/[()]//g') files_to_checksum="$(prefix_pattern "${IMAGE_ROOT}/" ${files_to_checksum})" elif [ "$1" = "-dir" ]; then check_package="0" source="Installed" checksum_dir="$2" checksum_file="${checksum_dir}/CHECKSUM" if [ ! -f "${checksum_file}" ]; then err_exit "${checksum_file} file not found" fi files_to_checksum="${INSTALL_FILES}" elif [ "$1" = "-fw" ]; then check_package="0" source="Firmware" checksum_dir="${CF_MNT}" checksum_file="${CF_MNT}/common/CHECKSUM" if [ ! -f "${checksum_file}" ]; then err_exit "${checksum_file} file not found" fi # Grab just the file name from the list of MD5SUMs: # "MD5 (rootfs.img) = e5d7e73a4321cb1f941b14c9d0fc3644" --> "rootfs.img" files_to_checksum=$(sed -e 's/^MD5 (\([^)]*\)).*$/\1/' \ ${checksum_file}) || err_exit "Error reading checksums from ${checksum_file}" else err_exit "Invalid parameters to verify_checksums" fi # Ensure the CHECKSUM file itself is not in the list. files_to_checksum=$(echo ${files_to_checksum} | sed 's/CHECKSUM //') for i in ${files_to_checksum}; do if [ "$check_package" = "1" -a "${tarball}" ]; then tar -qvtf "${tarball}" -O "${i}" 2>/dev/null || err_exit "Required file '${i}' not found in '$(basename "${tarball}")'" md5sum=`tar -qxvzf "${tarball}" -O $i 2>/dev/null | md5` else [ -f "${checksum_dir}/${i}" ] || err_exit "Required file '${i}' not found in '${checksum_dir}'" md5sum=`md5 -q "${checksum_dir}/${i}"` fi # Look for the calculated MD5 in the list of stored checksums [ -n "${md5sum}" ] && grep -q "$md5sum" "${checksum_file}" 2> /dev/null if [ $? -ne 0 ]; then if [ "$check_package" != "1" ]; then perform_pkg_verification fi err_exit "MD5 checksum failure on ${source} file: '$i'" fi done msg "${source} MD5 checksums pass" } verify_pkg_checksum() { checksum_file="${newdir}/CHECKSUM" # Grab just the file name from the list of MD5SUMs: # "MD5 (rootfs.img) = e5d7e73a4321cb1f941b14c9d0fc3644" --> "rootfs.img" files_to_checksum=$(cut -d' ' -f2 ${checksum_file}) || err_exit "${checksum_file} file not found" files_to_checksum=$(echo ${files_to_checksum} | sed 's/[()]//g') files_to_checksum="$(prefix_pattern "${IMAGE_ROOT}/" ${files_to_checksum})" # Ensure the CHECKSUM file itself is not in the list. files_to_checksum=$(echo ${files_to_checksum} | sed 's/CHECKSUM //') cd "${FAST_CHKSUM_ALGO_WORK_DIR}" 2> /dev/null tar -qxzf "${tarball}" ${files_to_checksum} 2> /dev/null for i in ${files_to_checksum}; do if [ ! -e $i ]; then err_exit "Required file '${i}' not retrieved from ${tarball}" else md5sum=`md5 -q $i` fi # Look for the calculated MD5 in the list of stored checksums [ -n "${md5sum}" ] && grep -q "$md5sum" "${checksum_file}" 2> /dev/null if [ $? -ne 0 ]; then err_exit "MD5 checksum failure on Package file: '$i'" fi done msg "Tarball file MD5 checksums pass" cd "${current_dir}" 2> /dev/null } perform_pkg_verification() { # Using mkdir failure to determine whether /mroot/etc/software is available # depends on "/" as read-only. This may change in the future. # Thus need to revisit this scheme in the future. if [ ! -d "${FAST_CHKSUM_ALGO_WORK_DIR}" ] && \ ! create_dir "${FAST_CHKSUM_ALGO_WORK_DIR}" > /dev/null 2>&1 ; then verify_checksums -package else verify_pkg_checksum fi } # This Function is used to # 1. Calculate free space along with directory(second arg) with first argument "size" # 2. Delete directory(second arg) contents along with some stale images of 7G when first arg "delete" delete_old_files() { local option=$1 local tmpdir=$2 if [ "$option" = "size" ]; then local available=$(fs_available_space "/cfcard") for file in ${tmpdir}/*; do if [ -e $file ]; then temp=`du -k $file | awk '{print $1}'` available=`expr $available + $temp` fi done for file in ${olddir}/*; do if [ -e $file ]; then temp=`du -k $file | awk '{print $1}'` available=`expr $available + $temp` fi done echo $available elif [ "$option" = "delete" ]; then rm -rf ${tmpdir}/* 2> /dev/null if [ -f ${currentdir}/kernel ]; then rm -rf ${olddir}/* 2> /dev/null fi fi } # # If this is not an INSTALL operation, compute the available disk space # available = the free space on the device + the space of the files to be replaced. # # NOTES: # This function expects INSTALL_FILES to have already been set # appropriately by the parse_metadata function. # free_space_check() { if [ $op != 'INSTALL' ]; then if [ ! -d /cfcard ]; then err_exit "No /cfcard directory found" fi # # Check whether "/cfcard" is mounted. # This is to avoid wasting time to list all other unrelated # file systems. If there is stale mounted file systems, # 'df -k | grep -q "cfcard"' will timeout when listing the staled file system. # See burt 912918 # is_mounted "/cfcard" if [ $? != 0 ]; then err_exit "Boot device not mounted" fi available=$(delete_old_files size $otherdir) availableM=`expr $available / 1024` msg "Available space on boot device is $availableM MB" # # Compute the required disk space - the space of the new files. # required=0 local extract_file_list="$(prefix_pattern "${IMAGE_ROOT}/" ${INSTALL_FILES})" for file in ${extract_file_list}; do if [ "${tarball}" ]; then temp=`grep "${file}\$" ${TMP}/filelist | awk '{print $5}'` temp=`expr $temp / 1024` else temp=`du -k $newdir/$file | awk '{print $1}'` fi required=`expr $required + $temp` done # # Ensure that the device will not be more than 95% full # slack=$(fs_size "/cfcard") slack=`expr $slack / 20` required=`expr $required + $slack` # convert to MB requiredM=`expr $required / 1024` msg "Required space on boot device is $requiredM MB" if [ $required -gt $available ]; then err_exit "Insufficient space available on device" fi # # Otherwise check that the media size is at least 1GB # else msize=`$DISKINFO $cfdev` || err_exit "Unable to read diskinfo from device $cfdev" msize=`echo $msize | awk '{print $3}'` if [ -z "$MIN_BOOTDEVICE_SIZE" ]; then # Set minimum boot device size as 900M $MIN_BOOTDEVICE_SIZE=900000000 fi if [ $msize -lt "$MIN_BOOTDEVICE_SIZE" ]; then # boot device insert is smaller than minimum size supported. msizeM=$(( msize / 1024 / 1024 )) MIN_BOOTDEVICE_SIZE=$(( $MIN_BOOTDEVICE_SIZE / 1024 / 1024 )) msg "The version of Data ONTAP you are attempting to install is not" msg "supported on a boot device of $msizeM MB capacity." err_exit "Size of boot device is less than $MIN_BOOTDEVICE_SIZE MB" fi fi } # # Rename the platform generation specific files to a generic platform file name # Parameters: # $1=directory The path to the file to be renamed. # Returns: # 0 success # 1 or non-zero on error # rename_platfiles() { local directory=$1 if [ ! -d "${directory}" ]; then return 1 fi if [ ! -f "${directory}/${PLATFS_NAME}" ]; then return 1 fi if [ ! -f "${directory}/${PLATFS_NAME}.sig" ]; then return 1 fi if [ ! -f "${directory}/${PLAT_FW_LATE}" ]; then return 1 fi if [ "${PLAT_GENERATION}" = "" ]; then # no need to rename return 0 fi mv -f ${directory}/${PLATFS_NAME} ${directory}/platfs.img mv -f ${directory}/${PLATFS_NAME}.sig ${directory}/platfs.img.sig mv -f ${directory}/${PLAT_FW_LATE} ${directory}/pmroot_late.tgz } # # Rename the platform specific ODM diagnostic executable name to a name is known by loader. # Parameters: # $1=directory The path to the file to be renamed. # Returns: # 0 success # 1 or non-zero on error # rename_diag_image() { local directory=$1 # Skip renaming ODM diagnostic file if it is not supported. if [ -z "${ODM_DIAG_SRC_EXEC}" ]; then return 0 fi if [ ! -d "${directory}" ]; then return 1 fi if [ -e "${directory}/${ODM_DIAG_SRC_EXEC}" ]; then if ! mv ${directory}/${ODM_DIAG_SRC_EXEC} ${directory}/${DIAG_EXEC_RUNTIME_NAME}; then msg "Failed to rename diagnostic executable file name." return 1 fi fi } # # Generate a temp file containing an XSLT. The caller is expected to clean up # the temp file. The result of running this transform will be the "value" of # the requested platform parameter. For example, the name of the platfs.img # file to be used on this platform. # # Input parameters: # $1= boardname identifier, eg: SB_XXX # $2= tag, eg: platfs # OR # $1= boardname identifier, eg: SB_XXX # $2= modelname identifier, eg: FAS8060 # $3= tag, eg: platfs # Output: # The name of the temp file containing the XSLT. xslt_gen() { local boardname local modelname local tag local selectfield boardname=$1 if [ "$3" = "" ]; then # modelname not specified tag=$2 selectfield="/package_metadata/platform[@name='${boardname}']/${tag}" else # modelname specified modelname=$2 tag=$3 selectfield="/package_metadata/platform[@name='${boardname}']/model[@name='${modelname}']/${tag}" fi local tmp_xslt=$(mktemp ${TMP}/xslt.XXXXX) cat <<-XSLT > ${tmp_xslt} XSLT echo ${tmp_xslt} } # Prevent upgrade or install on platforms with a Flash Cache adaptor and 4GiB # of memory or less. See burts 693390 and 539729 flash_cache_check() { # 5 GiB = 4 + 1 for NVMEM local fas3210_mem=$((5 * 1024 * 1024 * 1024)) local virgo_devid="0x774c1275" local fc_override_var="ext-cache-4g-override" # If memsize <= FAS3210 memory && PCI vendor id for Virgo is found, # we have an illegal config local fc_memsize fc_memsize="$(sysctl hw.total_physmem)" || msg "Unable to read system memory information" fc_memsize="$(echo "${fc_memsize}" | awk '{print $2}')" if [ "${fc_memsize}" -gt "${fas3210_mem}" ]; then # System has enough memory, return success return 0 fi if ! (pciconf -l | grep -q "${virgo_devid}" 2> /dev/null); then # flash cache is not present, return success return 0 fi # We have a low mem system with flash cache, but # allow override by bootarg: local fc_override="$(get_kenv "${fc_override_var}")" if [ "${fc_override}" = "true" ]; then msg "'$(get_kenv SYS_MODEL)' does not support Flash Cache in this release of Data ONTAP," msg "but '${fc_override_var}' is '${fc_override}'." return 0 fi # If we haven't returned success by now, this is a failure err_exit "Model '$(get_kenv SYS_MODEL)' does not support Flash Cache in this release of Data ONTAP" } # # Special case: versions are identical -> this becomes a copy operation # Only do this if the operation is not an INSTALL. # # NOTES: # This function should be called as part of the compat_check() function, # since it could change an UPDATE op into a COPY op. COPY is used to # intentionally bypass the compat check on what would otherwise be an # UPDATE operation. # op_should_be_COPY() { if [ $op = 'INSTALL' ]; then return fi diff /VERSION ${newdir}/VERSION > /dev/null 2>&1 if [ $? -eq 0 ]; then op='COPY' fi } # 8.3 and beyond are C-mode only. Disallow upgrades from a 7-mode system to # this release. no_7_mode_check() { if get_kenv "bootarg.init.boot_clustered" | grep -q -i "true"; then # The system is in C-mode; # allow install: return fi # The system is not in C-mode, so it must be in 7-Mode; # abort install local error_string error_string="Clustered Data ONTAP cannot be installed on a 7-mode system" err_exit "${error_string}" } # Check that the current VERSION file exists # and that versions are compatible. compat_check() { op_should_be_COPY # COPY is used to intentionally bypass the compatibility check # INSTALL and UPDATE are subject to checks if [ $op = 'COPY' ]; then return fi # VERSION file must exist if [ ! -e /VERSION ]; then err_exit "/VERSION file does not exist" fi local error_string="The running version $(cat /VERSION)" error_string="${error_string} is not compatible with the package" error_string="${error_string} version $(cat ${newdir}/VERSION)" # Check basic compatibility # If a regex pattern in COMPAT.TXT matches the current /VERSION # string, then we are compatible. if ! grep -qEf ${newdir}/COMPAT.TXT /VERSION; then err_exit "${error_string}" fi if [ $op != 'INSTALL' ]; then no_7_mode_check fi msg "Versions are compatible" } # # Given the platform boardname and a tag name, parse the metadata.xml file and # return the value of the requested tag # # Input parameters: # $1= boardname identifier # $2= tag # Output: # The content of "tag" # Return: # The return code of the xsltproc(1) command platform_xpath() { local boardname=$1 local tag=$2 local xslt_file=$(xslt_gen ${boardname} ${tag}) # Output is captuire by this function's caller xsltproc ${xslt_file} ${newdir}/metadata.xml local rc=$? rm -f ${xslt_file} return ${rc} } # # Given the platform boardname, modelname and a tag name, parse the metadata.xml file and # return the value of the requested tag # # Input parameters: # $1= boardname identifier # $2= modelname # $3= tag # Output: # The content of "tag" # Return: # The return code of the xsltproc(1) command platform_model_xpath() { local boardname=$1 local modelname=$2 local tag=$3 # get model defined attribute local xslt_file=$(xslt_gen ${boardname} ${modelname} ${tag}) local output=$(xsltproc ${xslt_file} ${newdir}/metadata.xml) local rc=$? rm -f ${xslt_file} if [ "${output}" = "" ]; then # get general board defined attribute output="$(platform_xpath ${boardname} ${tag})" rc=$? fi echo "${output}" return ${rc} } # Ensure the given command line tool exists # Input paramters: # $1 The command to verify is present on the system and in the path. # Return: # The script will exit if the command is not found. tool_check() { local tool_cmd="$1" local error_string="The running version $(cat /VERSION)" error_string="${error_string} does not have the tool set necessary to" error_string="${error_string} complete the ${op} operation" if ! which ${tool_cmd} > /dev/null; then msg "Command '${tool_cmd}' was not found." err_exit "${error_string}" fi } is_file_in_package() { local file=${1} if [ -n "${tarball}" ]; then if grep -w -q "${file}" ${TMP}/filelist; then return 0 fi else if [ -e "${newdir}/${file}" ]; then return 0 fi fi return 1 } parse_metadata() { # Ensure that the versions are compatible. Do this early, otherwise # we might not have essential tools (such as xsltproc) or other # unexpected incompatibilities compat_check # verify xsltproc is available for use tool_check xsltproc # Which platform are we on? local boardname=$(get_kenv BOARDNAME) local modelname=$(get_kenv SYS_MODEL) # BOARDNAME is readonly from the LOADER. Allow a user to override # the BOARDNAME with an arbitrary value by setting BOARDNAME_OVERRIDE. local boardname_override=$(get_kenv BOARDNAME_OVERRIDE) if [ -n "${boardname_override}" ]; then msg "BOARDNAME_OVERRIDE='${boardname_override}' will replace the default value of BOARDNAME='${boardname}'." boardname="${boardname_override}" fi local modelname_override=$(get_kenv bootarg.SYS_MODEL_MOD) if [ -n "${modelname_override}" ]; then msg "bootarg.SYS_MODEL_MOD='${modelname_override}' will replace the default value of SYS_MODEL='${modelname}'." modelname="${modelname_override}" fi # Platform generation specific file names are using 'plat_generation' as part # of the module name, e.g. platfs2g.img, pmroot_late2g.tgz # Setup the global macro PLATFS_NAME and PLAT_FW_LATE based on the # 'plat_generation' attribute. # The global macros are re-use during the rename of platform specific files. PLAT_GENERATION="$(platform_model_xpath ${boardname} ${modelname} plat_generation)" PLATFS_NAME="platfs${PLAT_GENERATION}.img" PLAT_FW_LATE="pmroot_late${PLAT_GENERATION}.tgz" # add platfs.img to the list of files to be installed: INSTALL_FILES="${INSTALL_FILES} ${PLATFS_NAME} ${PLATFS_NAME}.sig ${PLAT_FW_LATE}" TRIM_CMD="$(platform_model_xpath ${boardname} ${modelname} trim_cmd)" NEW_KEY_CMD="$(platform_model_xpath ${boardname} ${modelname} new_key_cmd)" CLEAR_BD_CMD="$(platform_model_xpath ${boardname} ${modelname} clr_bootdevice_cmd)" CLEAR_BD_BLOCK_CNT="$(platform_model_xpath ${boardname} ${modelname} clr_bootdevice_blk_cnt)" FDISK_SCRIPT="$(platform_model_xpath ${boardname} ${modelname} fdisk_script)" FDISK_SECONDARY_SCRIPT="$(platform_model_xpath ${boardname} ${modelname} fdisk_secondary_script)" NEWFS_CMD="$(platform_model_xpath ${boardname} ${modelname} newfs_cmd)" MOUNT_CMD="$(platform_model_xpath ${boardname} ${modelname} mount_cmd)" MIN_BOOTDEVICE_SIZE="$(platform_model_xpath ${boardname} ${modelname} minimum_bootdevice_size)" INSTALL_LOADER="$(platform_model_xpath ${boardname} ${modelname} install_loader_efi)" if [ ! -z "${INSTALL_LOADER}" ]; then INSTALL_LOADER=$(toupper ${INSTALL_LOADER}) fi IM_BOOT_RECOVERY="$(platform_model_xpath ${boardname} ${modelname} im_boot_recovery)" if [ ! -z "${IM_BOOT_RECOVERY}" ]; then IM_BOOT_RECOVERY=$(toupper ${IM_BOOT_RECOVERY}) fi SINGLE_DISK_BRECOVERY="$(platform_model_xpath ${boardname} ${modelname} single_disk_brecovery)" if [ ! -z "${SINGLE_DISK_BRECOVERY}" ]; then SINGLE_DISK_BRECOVERY=$(toupper ${SINGLE_DISK_BRECOVERY}) fi ODM_DIAG_SRC_EXEC="$(platform_model_xpath ${boardname} ${modelname} odm_diag_image)" if [ -n "${ODM_DIAG_SRC_EXEC}" ] && \ is_file_in_package "${ODM_DIAG_SRC_EXEC}"; then INSTALL_FILES="${INSTALL_FILES} ${ODM_DIAG_SRC_EXEC}" fi # Add extra platform-specific files PLATFORM_INSTALL_FILES="$(platform_model_xpath ${boardname} ${modelname} platform_specific_install_files)" if [ "$PLATFORM_INSTALL_FILES" != "" ]; then INSTALL_FILES="${INSTALL_FILES} ${PLATFORM_INSTALL_FILES}" fi # Optionally install the pobj.db file if is_file_in_package "pobj.db"; then INSTALL_FILES="${INSTALL_FILES} pobj.db" fi # Make sure the metadata file provided all the values that we expect, # else we exit. if [ -z "${FDISK_SCRIPT}" -o \ -z "${NEWFS_CMD}" -o \ -z "${MOUNT_CMD}" ]; then err_exit "Model '$(get_kenv SYS_MODEL)' is not supported by this package" fi } # # Ensure the files for the new image exist # # NOTES: # This function expects INSTALL_FILES to have already been set # appropriately by the parse_metadata function. # file_presence_check() { local pkg_source local file if [ "${tarball}" ]; then pkg_source="${tarball}" else pkg_source="${newdir}" fi local extract_file_list="$(prefix_pattern "${IMAGE_ROOT}/" ${INSTALL_FILES})" for file in ${extract_file_list}; do if ! is_file_in_package "${file}"; then err_exit "'${file}' does not exist in ${pkg_source}" fi done } # # For the INSTALL operation check for the existance of the device and # ensure that it is not currently mounted. # device_mount_check() { if [ $op != 'INSTALL' ]; then return fi if [ ! -c $cfdev ]; then err_exit "Cannot find boot device file $cfdev" fi mount | grep -q $cfdev if [ $? = 0 ]; then err_exit "Cannot initialize a mounted filesystem" fi msg "Install device found" if [ $oneonly ]; then msg "Install command will only setup $current" fi } # # Get system BOARDNAME # get_board_name() { # Which platform are we on? local boardname=$(get_kenv BOARDNAME) # BOARDNAME is readonly from the LOADER. Allow a user to override # the BOARDNAME with an arbitrary value by setting BOARDNAME_OVERRIDE. local boardname_override=$(get_kenv BOARDNAME_OVERRIDE) if [ -n "${boardname_override}" ]; then boardname="${boardname_override}" fi echo "${boardname}" } # # Install EFI Loader Files # setup_loader_files() { # Check install_loader_efi attribute set in build metadata file. # Skip setting up loader files if the xml tag is not defined; # or not set to true. if [ "${INSTALL_LOADER}null" = "null" -o \ "${INSTALL_LOADER}" != "TRUE" ]; then return 0; fi local boardname=`get_board_name` local loader_zipsrc="${CF_MNT}/x86_64/firmware/${boardname}/firmware2.img" local loader_zipdest="${CF_MNT}/x86_64/firmware/${boardname}/loader.zip" if [ -e "${loader_zipsrc}" ]; then msg "Install loader files" # remove any file/folder named '/cfcard/boot' before unzip in case someone created it rm -rf "${CF_MNT}/boot" dd bs=256 skip=2 < ${loader_zipsrc} > ${loader_zipdest} 2> /dev/null if ! unzip -d ${CF_MNT} ${loader_zipdest} > /dev/null; then msg "Failed to unzip loader.zip" return 1 fi if [ -e ${loader_zipdest} ]; then rm -f ${loader_zipdest} fi # perform loader file installation for INSTALL mode only if [ $op = 'INSTALL' ]; then # Invoke loader script to install files onto primary boot device local install_loader_script="${CF_MNT}/boot/x86-64/install_loader_files.sh" if [ -e "${install_loader_script}" ]; then ${install_loader_script} "${CF_MNT}" "${CF_MNT}" "${boardname}" else msg "Failed to find loader script." fi fi fi return 0 } # # Setup secondary boot device # setup_recovery_media() { if is_boot_recovery; then # do not need to setup recovery file msg "Skip recovery file setup due to recovery in progress." return 0; fi if [ "${IM_BOOT_RECOVERY}null" = "null" -o \ "${IM_BOOT_RECOVERY}" != "TRUE" ]; then # does not support boot recovery using internal boot media return 0; fi local recovery_dev=$(get_kenv ntap.secondary.bootmedia) if [ $recovery_dev ]; then recovery_part=$recovery_dev else return 0 fi if [ "${SINGLE_DISK_BRECOVERY}null" = "null" -o \ "${SINGLE_DISK_BRECOVERY}" != "TRUE" ]; then # process dual disk boot recovery recovery_dev=`echo $recovery_dev | sed 's/s.*//'` if [ ! -e $recovery_dev ]; then msg "Secondary boot disk is not detected." return 0 fi if [ $op = 'INSTALL' ]; then # partition the disk write_fdisk_secondary_config $FDISK -i -f $TMP_MNT/dskfmt $recovery_dev if [ $? != 0 ]; then msg "Failed to partition secondary boot media" return 0 fi sync ; sleep 3; fi else # process single disk boot recovery if [ ! -e $recovery_part ]; then msg "Recovery partition is not detected." return 0 fi fi if [ $op = 'INSTALL' ]; then # create a new filesystem on partition 1 $(printf "${NEWFS_CMD}" "${recovery_part}") if [ $? != 0 ]; then msg "Unable to create a filesystem for recovery partition" return 0 fi msg "New filesystem created on recovery partition" sync fi # mount the new filesystem for copying over files local RECOVERY_MNT="/mnt/recoverydisk" if [ ! -d ${RECOVERY_MNT} ]; then mkdir "/mnt/recoverydisk" fi $(printf "${MOUNT_CMD}" "${recovery_part}" "${RECOVERY_MNT}") if [ $? != 0 ]; then msg "Unable to mount the secondary boot device filesystem" return 0 else sync fi cp "${tarball}" ${RECOVERY_MNT} # Invoke loader script to install to secondary disk local boardname=`get_board_name` local install_loader_script="${CF_MNT}/boot/x86-64/install_loader_files.sh" if [ -e "${install_loader_script}" ]; then ${install_loader_script} "${CF_MNT}" "${RECOVERY_MNT}" "${boardname}" fi msg "Boot recovery files installed on recovery partition" umount ${RECOVERY_MNT} } # # Validate the machine type # machine=`uname -m` case $machine in amd64) machine=x86_64 def_cfdev=/dev/ad0 ;; i386) machine=x86 def_cfdev=/dev/ad4 ;; *) err_exit "Unrecognized machine type: $machine" ;; esac # If this is not a LOADER based system, then this will be empty LOADER_VERSION=`get_kenv LOADER_VERSION` #Input: $1 --- path of target build file nodar_build_check() { if [ $op = 'UPDATE' ]; then # BUILD file must exist if [ ! -e "$1" ]; then err_exit "$1 does not exist" fi /usr/bin/sudo /sbin/vcontext -v 4294967295 $newdir/netapp_nodar_build_check netapp_nodar_build_check $1 if [ $? != 0 ]; then msg "Error: Update is prohibited. Reason: The target image does not support Netapp Volume Encryption (NVE), but encrypted volumes are found in the cluster. Use the \"volume show -is-encrypted true\" command to list encrypted volumes. Unencrypt the volumes or install an image that supports NVE." err_exit "Netapp Volume Encryption image check failed" fi fi } external_keymanager_check() { if [ $op = 'UPDATE' ]; then $newdir/netapp_external_keymanager_check netapp_external_keymanager_check if [ $? != 0 ]; then err_exit "external keymanager check failed" else msg "external keymanager check script has run successfully." fi fi } # # Validate the number of paramters # if [ `toupper $1`null = "-CHECK"null ]; then checkonly=1 shift msg "INSTALL running in check-only mode: the image will be validated only" fi if [ $1null = "-1"null ]; then oneonly=1 shift fi if [ $# -lt 3 -o $# -gt 4 ]; then err_exit "Invalid number of parameters specified" fi op=`toupper $1` current=$2 location="$3" cfdev=$4 # # Only accept copy, update and install as operations # if [ $op != 'COPY' -a $op != 'UPDATE' -a $op != 'INSTALL' ]; then err_exit "Invalid operation specified" fi msg "Mode of operation is $op" # # Only accept image1 and image2 as the location of the currently running image # if [ `toupper $current` = 'IMAGE1' ]; then current=image1 other=image2 elif [ `toupper $current` = 'IMAGE2' ]; then current=image2 other=image1 else err_exit "Invalid current image specified" fi # # Ensure the that currently running image is really the currently running image. # imagestr=`get_kenv bootarg.init.rootimage` if [ $? -ne 0 ]; then imagestr=`get_kenv kernelname` fi if [ $imagestr ]; then echo $imagestr | grep -q $other 2> /dev/null if [ $? -eq 0 ]; then err_exit "Cannot write to a currently running image" fi fi currentdir=/cfcard/$machine/freebsd/$current otherdir=/cfcard/$machine/freebsd/$other olddir=/cfcard/backup/$machine/kernel msg "Current image is $current" msg "Alternate image is $other" # # Is it a directory or a tarball? # if [ ! -d "${location}" ]; then tar -tvzf "${location}" > ${TMP}/filelist if [ $? -ne 0 ]; then err_exit "Invalid location specified - must be a directory or a gzipped tarball" fi tarball="$location" else newdir="$location" fi # # If this in an INSTALL operation then check for a valid optional device # if [ $op = "INSTALL" ]; then if [ $cfdev ]; then if [ ! -c $cfdev ]; then err_exit "Device $cfdev does not exist" fi cfpart=$cfdev"s1" else cfdev=`get_kenv ntap.init.cfdevice` if [ $cfdev ]; then cfpart=$cfdev cfdev=`echo $cfdev | sed 's/s.*//'` else cfdev=$def_cfdev cfpart=$cfdev"s1" fi fi msg "Using device $cfdev for install" fi # After this point, we assume we've been invoked correctly, so don't print the # script usage. PRINT_USAGE=0 pre_extract if [ $checkonly ]; then perform_pkg_verification fi parse_metadata file_presence_check free_space_check kernel_arch_check hostname_check flash_cache_check brand_check "${newdir}/BUILD" device_mount_check nodar_build_check "$newdir/BUILD" external_keymanager_check # # If running as check only then stop here. # if [ $checkonly ]; then cleanup_and_exit 0 fi msg "Getting ready to install image" # # If this is an INSTALL operation then format the device # if [ $op = 'INSTALL' ]; then # whipup a ramdisk to place a file specifing partitioning info for fdisk mddev=`mdconfig -a -tmalloc -s1024` if [ $? != 0 ]; then err_exit "Unable to allocate a small ramdisk" fi newfs /dev/$mddev if [ $? != 0 ]; then mdconfig -d -u$mddev err_exit "Unable to newfs ramdisk" fi sync TMP_MNT=`mktemp -d /tmp/mnt.XXXXX` mount /dev/$mddev $TMP_MNT if [ $? != 0 ]; then mdconfig -d -u$mddev err_exit "Unable to mount ramdisk" fi ramdisk_created=1 # Trim the boot device before partition and installation # If the trim process failed,Installation continues if [ -n "${TRIM_CMD}" ]; then $(printf "${TRIM_CMD}" "${cfdev}") fi write_fdisk_config # zeroing out a chunk of boot device if ! is_boot_recovery && [ -n "${CLEAR_BD_CMD}" ]; then # Only zero out first GB if it is not performing boot recovery msg "Clearing boot device..." $CLEAR_BD_CMD if=/dev/zero of=$cfdev bs=1m count=$CLEAR_BD_BLOCK_CNT > /dev/null 2>&1 fi # partition the disk $FDISK -i -f $TMP_MNT/dskfmt $cfdev if [ $? != 0 ]; then err_exit "Unable to initialize the boot device partition table" fi sync ; sleep 3; msg "Partitioning of boot device complete" # On platform like Razor where mSata device is partitioned into the # boot device and destage device.Trim the whole mSata device in the # above step actually erased the encryption key in the destage partition. # # In case customer chooses to continue boot(less likley but possible) # DeStage in AC power loss case can't proceed without encryption key, # therefore, re-generate the encryption key here. if [ -n "${NEW_KEY_CMD}" ]; then ${NEW_KEY_CMD} fi # create a new filesystem on partition 1 $(printf "${NEWFS_CMD}" "${cfpart}") if [ $? != 0 ]; then err_exit "Unable to create a filesystem on the boot device" fi msg "New boot device filesystem created" sync # mount the new filesystem for copying over files $(printf "${MOUNT_CMD}" "${cfpart}" "${CF_MNT}") if [ $? != 0 ]; then err_exit "Unable to mount boot device filesystem" fi sync msg "New filesystem mounted" if [ "$LOADER_VERSION" != "" ]; then # Restore loader's environment to the boot device since we blew # it away when the boot device was formatted SaveLoaderEnv || msg "Warning: Could not preserve Loader environment." sync fi currentdir=${CF_MNT}/$machine/freebsd/$current otherdir=${CF_MNT}/$machine/freebsd/$other olddir=${CF_MNT}/backup/$machine/kernel fi # # If destination directory does not exist then create it. # create_dir $otherdir if [ $? != 0 ]; then cleanup_and_exit 1 fi if [ $op = 'INSTALL' ]; then create_dir $currentdir if [ $? != 0 ]; then cleanup_and_exit 1 fi fi # # Copy the files # extract_file_list="$(prefix_pattern "${IMAGE_ROOT}/" ${INSTALL_FILES})" if [ $op = 'INSTALL' -a $oneonly ]; then delete_old_files delete $currentdir copy_files "${newdir}" "${currentdir}" "${extract_file_list}" if [ $? != 0 ]; then cleanup_and_exit 1 fi verify_checksums -dir $currentdir rename_platfiles "${currentdir}" rename_diag_image "${currentdir}" # Swap for fw install otherdir=$currentdir else if [ $op = 'INSTALL' ]; then delete_old_files delete $currentdir copy_files "${newdir}" "${currentdir}" "${extract_file_list}" if [ $? != 0 ]; then cleanup_and_exit 1 fi verify_checksums -dir $currentdir rename_platfiles "${currentdir}" rename_diag_image "${currentdir}" # we don't need two copies of fw.tgz rm -f ${currentdir}/fw.tgz fi delete_old_files delete $otherdir copy_files "${newdir}" "${otherdir}" "${extract_file_list}" if [ $? != 0 ]; then cleanup_and_exit 1 fi verify_checksums -dir $otherdir rename_platfiles "${otherdir}" rename_diag_image "${otherdir}" fi # # Check for fw.tgz in this package and install it # if [ -f $otherdir/fw.tgz ]; then # See if we have enough space to install this. # Assume we need twice the size of the tarball fw_needed=`ls -l $otherdir/fw.tgz | awk '{print $5}'` fw_needed=`expr $fw_needed \* 2 / 1024` avail=`df -k $otherdir | grep -v Avail | awk '{print $4}'` avail=`expr $avail \* 90 / 100` # 10% head room if [ $fw_needed -gt $avail ]; then msg "Not enough space to install diagnostics and firmware files" else msg "Installing diagnostic and firmware files" # use the directory hierarchy built into the tar file tar -xzf $otherdir/fw.tgz -C ${CF_MNT} if [ $? != 0 ]; then err_exit "Failed to install diagnostic and firmware files" fi verify_checksums -fw # install loader files if ! setup_loader_files; then err_exit "Failed to install loader files" fi # Successfully extracted, no need to keep the package rm -f ${otherdir}/fw.tgz fi fi # Setup Boot Recovery Disk setup_recovery_media rm -rf "${CF_MNT}/boot" # After the install, and before we unmount and exit, # tell the dblade to set a flag. notify_dblade # # If this was an install operation then unmount the boot device. # if [ $op = "INSTALL" ]; then umount ${CF_MNT} if [ $? != 0 ]; then err_exit "Unable to unmount the boot device" fi sync fi cleanup_and_exit 0