#!/bin/bash
#
# transactional-update - apply updates to the system in an atomic way
#
# Author: Thorsten Kukuk <kukuk@suse.com>
# Copyright (C) 2016, 2017, 2018, 2019, 2020 SUSE Linux GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

export LANG=C
export DISABLE_SNAPPER_ZYPP_PLUGIN=1

DIR_TO_MOUNT="opt var/log"
EXITCODE=0
VERBOSITY=2
ZYPPER_ARG=""
ZYPPER_NONINTERACTIVE="-y --auto-agree-with-product-licenses"
ZYPPER_ARG_PKGS=()
REWRITE_BOOTLOADER=0
REWRITE_GRUB_CFG=0
REWRITE_INITRD=0
REBUILD_KDUMP_INITRD=0
DO_CLEANUP_OVERLAYS=0
DO_CLEANUP_SNAPSHOTS=0
DO_MIGRATION=0
DO_DUP=0
DO_ROLLBACK=0
DO_SELF_UPDATE=1
DO_REGISTRATION=0
DO_RUN=0
REGISTRATION_ARGS=""
ROLLBACK_SNAPSHOT=0
REBOOT_AFTERWARDS=0
REBOOT_METHOD="auto"
RUN_CMD=""
RUN_SHELL=0
SETUP_SELINUX=0
USE_TELEMETRICS=0
TELEM_PAYLOAD="PACKAGE_NAME=transactional-update\nPACKAGE_VERSION=2.28.3"
TELEM_CLASS=""
CONFFILE="/etc/transactional-update.conf"
SYSTEMCONFFILE="/usr/etc/transactional-update.conf"
LOGFILE="/var/log/transactional-update.log"
STATE_FILE="/var/lib/misc/transactional-update.state"
NEW_SNAPSHOT_FLAG="/var/lib/overlay/transactional-update.newsnapshot"
NEEDS_RESTARTING_FILE="/var/run/reboot-needed"
LOCKFILE="/var/run/transactional-update.pid"
ZYPPER_AUTO_IMPORT_KEYS=0
ETC_OVERLAY_PATTERN='^[^[:space:]]\+[[:space:]]\+\/etc[[:space:]]\+overlay[[:space:]]\+\([^[:space:]]*,\|\)workdir=\/sysroot\/var\/lib\/overlay\/work-etc[,[:space:]]'
NON_ROOTFS_WHITELIST=("/var/lib/rpm" "/var/lib/systemd/migrated" "/var/run/zypp.pid")
DROP_IF_NO_CHANGE=0
INOTIFY_EXCLUDES=""

TMPDIR=${TMPDIR:-/tmp}

# Load config
if [ -r ${SYSTEMCONFFILE} ]; then
    . ${SYSTEMCONFFILE}
fi
if [ -r ${CONFFILE} ]; then
    . ${CONFFILE}
fi

# Initialize internal variables
ETC_IS_OVERLAY=0
HAS_SEPARATE_VAR=0
VAR_CACHE_CLEANUP=0
FORCE_NONINTERACTIVE=""
SNAPSHOT_ID=""
SNAPSHOT_DIR=""
BASE_SNAPSHOT_ID=""
MOUNT_DIR=""
SNAPPER_NO_DBUS=""
TMPFILE=""
ETC_OVERLAY_DIR=""
ETC_OVERLAY_DIR_PREV=""
ETC_OVERLAY_WORK_DIR=""
INOTIFY_PID=""

# Create stderr alias for things that shouldn't be logged into logfile
if [ ! -e /proc/$$/fd/4 ]; then
	exec 4>&2
fi
# Log stderr to log file
exec 2> >(exec tee -a "${LOGFILE}")

self_update() {
    if [ ${DO_SELF_UPDATE} == 0 ]; then
	return
    fi

    log_info "Checking for newer version."
    if zypper --non-interactive info transactional-update | grep -q '^Status *: out-of-date'; then
	log_info "New version found - updating..."
	TA_UPDATE_TMPFILE="`mktemp -d ${TMPDIR}/transactional-update.XXXXXXXXXX`"
	if [ $? -ne 0 ]; then
	    log_error "ERROR: Couldn't create temporary directory for self-update."
	    quit 1
	fi
	export TA_UPDATE_TMPFILE
	pushd "${TA_UPDATE_TMPFILE}" >/dev/null
	zypper --non-interactive --pkg-cache-dir "${TA_UPDATE_TMPFILE}" download transactional-update
	find . -name transactional-update*.rpm -exec rpm2cpio {} \; | cpio -idmv 2>/dev/null
	if [ $? -ne 0 ]; then
	    log_error "ERROR: Couldn't extract the update."
	    quit 1
	fi
	# Reset CWD before restart
	popd >/dev/null
	exec "${TA_UPDATE_TMPFILE}/usr/sbin/transactional-update" "$@"
    fi
}

usage() {
    echo "Syntax: transactional-update [option...] [general-command...] [package-command]"
    echo "        transactional-update [option...] standalone-command"
    echo ""
    echo "Applies package updates to a new snapshot without touching the running"
    echo "system."
    echo ""
    echo "General Commands:"
    echo "cleanup                    Run both cleanup-snapshots and cleanup-overlays"
    echo "cleanup-snapshots          Mark unused snapshots for snapper removal"
    echo "cleanup-overlays           Remove unused overlay layers"
    echo "grub.cfg                   Regenerate grub.cfg"
    echo "bootloader                 Reinstall the bootloader"
    echo "initrd                     Regenerate initrd"
    echo "kdump                      Regenerate kdump initrd"
    echo "shell                      Open rw shell in new snapshot before exiting"
    echo "reboot                     Reboot after update"
    echo "run <cmd>                  Run a command in a new snapshot"
    echo "setup-selinux              Install targeted SELinux policy and enable it"
    echo ""
    echo "Package Commands:"
    echo "Defaults: (i) interactive command; (n) non-interactive command"
    echo "dup                        Call 'zypper dup' (n)"
    echo "up                         Call 'zypper up' (n)"
    echo "patch                      Call 'zypper patch' (n)"
    echo "migration                  Updates systems registered via SCC / SMT (i)"
    echo "pkg install ...            Install individual packages (i)"
    echo "pkg remove ...             Remove individual packages (i)"
    echo "pkg update ...             Updates individual packages (i)"
    echo ""
    echo "Standalone Commands:"
    echo "rollback [<number>]        Set the current or given snapshot as default snapshot"
    echo "rollback last              Set the last working snapshot as default snapshot"
    echo ""
    echo "Options:"
    echo "--interactive, -i          Use interactive mode for package command"
    echo "--non-interactive, -n      Use non-interactive mode for package command"
    echo "--continue [<number>], -c  Use latest or given snapshot as base"
    echo "--no-selfupdate            Skip checking for newer version"
    echo "--drop-if-no-change, -d    Drop the snapshot if there is no change"
    echo "--quiet                    Don't print warnings and infos to stdout"
    echo "--help, -h                 Display this help and exit"
    echo "--version                  Display version and exit"
    exit $1
}

print_version() {
    echo "transactional-update 2.28.3"
    exit 0
}

log_info() {
    echo `date "+%Y-%m-%d %H:%M"` "$@" >> ${LOGFILE}
    if [ ${VERBOSITY} -ge 2 ]; then
	echo -e "$@"
    fi
}

log_error() {
    TELEM_PAYLOAD="${TELEM_PAYLOAD}\nmessage=$@"
    echo `date "+%Y-%m-%d %H:%M"` "$@" >> ${LOGFILE}
    echo -e "$@" 1>&4
}

bashlock() {
    echo "$$" >"$LOCKFILE.$$"
    if ! ln "$LOCKFILE.$$" "$LOCKFILE" 2>/dev/null; then
	PID=`head -1 "$LOCKFILE"`
	if [ -z "$PID" ]; then
	    rm -f "$LOCKFILE"
	else
	   kill -0 "$PID" 2>/dev/null || rm -f "$LOCKFILE"
	fi

	if ! ln "$LOCKFILE.$$" "$LOCKFILE" 2>/dev/null; then
	    rm -f "$LOCKFILE.$$"
	    return 1
	fi
    fi

    rm -f "$LOCKFILE.$$"
    trap 'rm -f "$LOCKFILE"' EXIT

    return 0
}

save_state_file() {
    echo "LAST_WORKING_SNAPSHOTS=\"${LAST_WORKING_SNAPSHOTS}\"" > ${STATE_FILE}
    echo "UNUSED_SNAPSHOTS=\"${UNUSED_SNAPSHOTS}\"" >>  ${STATE_FILE}

    if [ $1 -ne 0 -a ${HAS_SEPARATE_VAR} -eq 0 ]; then
	# If /var/lib/misc is not a seperate partition / subvolume, copy the
	# state file into the new snapshot as it will contain an outdated
	# version from before taking the snapshot otherwise.
	grep -q var.lib.misc /proc/mounts
	if [ $? -ne 0 ]; then
	    cp -a ${STATE_FILE} "/.snapshots/$1/snapshot${STATE_FILE}"
	fi
    fi
}

rebuild_kdump_initrd() {
    local MOUNT_DIR=$1

    test -f /usr/lib/systemd/system/kdump.service || return
    systemctl is-enabled --quiet kdump.service
    if [ $? = 0 -a -x ${MOUNT_DIR}/usr/sbin/tu-rebuild-kdump-initrd ]; then
	chroot ${MOUNT_DIR} /usr/sbin/tu-rebuild-kdump-initrd |& tee -a ${LOGFILE}
    fi
}

# start telemetry system if installed
telem_start() {
    if [ -x /usr/bin/telem-record-gen ]; then
	USE_TELEMETRICS=1
	TELEM_START_TIME=`echo $(($(date +%s%N)/1000000))`
	source <(grep VERSION_ID /etc/os-release)
	TELEM_PAYLOAD="${TELEM_PAYLOAD}\noptions=${ORIG_ARGS[@]}\ncurrent_version=${VERSION_ID}"
    fi
}

telem_finish () {
    if [ ${USE_TELEMETRICS} -eq 1 ]; then
	TELEM_END_TIME=`echo $(($(date +%s%N)/1000000))`
	ELAPSED_TIME=`echo "scale=3; ($TELEM_END_TIME - $TELEM_START_TIME)/1000" | bc -l`
	TELEM_PAYLOAD="${TELEM_PAYLOAD}\ntime=${ELAPSED_TIME} Seconds"
	if [ $1 -gt 0 ]; then
	  TELEM_SEVERITY=3
	else
	  TELEM_SEVERITY=1
	fi
	echo -e "${TELEM_PAYLOAD}" | telem-record-gen -s ${TELEM_SEVERITY} -c "org.opensuse/txupdate/$TELEM_CLASS"
    fi
}

# Only called in error case; reverts everything to previous state.
quit() {
    teardown

    # Wait for things to settle down
    for delay in {1..10}; do
	if [ -z "${SNAPSHOT_DIR}" -o -z "`lsof "${SNAPSHOT_DIR}" 2>/dev/null`" ]; then
	    break
	fi
	sleep $delay
    done

    if [ -n "${SNAPSHOT_ID}" ] ; then
	log_info "Removing snapshot #${SNAPSHOT_ID}..."
	snapper ${SNAPPER_NO_DBUS} delete ${SNAPSHOT_ID} |& tee -a ${LOGFILE}
    fi
    if [ -n "${ETC_OVERLAY_DIR}" ] ; then
	log_info "Removing overlay directory ${ETC_OVERLAY_DIR%/*}..."
	if findmnt "${ETC_OVERLAY_DIR}" >/dev/null; then
	    umount "${ETC_OVERLAY_DIR}"
	fi
	rm -rf "${ETC_OVERLAY_DIR%/*}" |& tee -a ${LOGFILE}
    fi
    telem_finish $1
    log_info "transactional-update finished"
    exit $1
}

# Called on exit (both on success and failure); cleans up temporary files,
# mount points and variables
teardown() {
    # Unset variable
    unset TRANSACTIONAL_UPDATE

    # Roll back snapshot if no changes were performed
    if [ -n "${INOTIFY_PID}" ] && sync "${SNAPSHOT_DIR}" && ps -q ${INOTIFY_PID} 2>&1 >/dev/null; then
	kill ${INOTIFY_PID}
	log_info "The snapshot does not contain any changed files."
	INOTIFY_PID=""

	# Even if the snapshot itself did not contain any changes,
	# /etc may do so. Changes in /etc may be applied immediately,
	# so merge them back into the running system.
	log_info "Merging overlay directory ${SNAPSHOT_DIR}/etc into /etc..."
	rsync --archive --inplace --xattrs --acls --exclude 'fstab' --delete --quiet "${SNAPSHOT_DIR}/etc/" /etc

	quit 0
    fi

    # Cleanup temporary files
    rm -f ${TMPFILE}

    # The following commands only make sense if snapshot dir is set already
    if [ "${SNAPSHOT_DIR}" = "" ]; then
	return
    fi

    # Delete temporary data before unmounting everything:
    if [ ${HAS_SEPARATE_VAR} -eq 1 ]; then
	rm -rf ${SNAPSHOT_DIR}/var/tmp
	rm -rf ${SNAPSHOT_DIR}/var/lib/zypp
	rm -rf ${SNAPSHOT_DIR}/var/run
    fi

    # Unmount everything we don't need anymore:
    for directory in etc $DIR_TO_MOUNT .snapshots dev sys proc; do
	# Only try unmount if directory is mounted
	if ! findmnt ${SNAPSHOT_DIR}/$directory >/dev/null; then
	    continue;
	fi
	umount -R ${SNAPSHOT_DIR}/$directory
	if [ $? -ne 0 ]; then
	    log_error "Warning: First attempt to umount $directory failed!"
	    # Try again after some time
	    sleep 30
	    umount -R ${SNAPSHOT_DIR}/$directory
	    if [ $? -ne 0 ]; then
		fuser -v ${SNAPSHOT_DIR}/$directory >> ${LOGFILE}
		lsof ${SNAPSHOT_DIR}/$directory >> ${LOGFILE}
		log_error "ERROR: second attempt to umount $directory also failed!"
		EXITCODE=1;
	    else
		log_info "Second attempt to umount $directory succeeded."
	    fi
	fi
    done

    # Cleanup of temporary mount point
    if [ -e "${MOUNT_DIR}" ]; then
	if findmnt ${MOUNT_DIR} >/dev/null; then
	    umount -R ${MOUNT_DIR}
	fi
	rmdir ${MOUNT_DIR}
    fi
    if [ -n "${ETC_OVERLAY_WORK_DIR}" ]; then
	rm -rf "${ETC_OVERLAY_WORK_DIR}"
    fi

    # Cleanup other stuff
    # Cleanup cache directory
    if [ $VAR_CACHE_CLEANUP -eq 1 ]; then
	rm -rf ${SNAPSHOT_DIR}/var/cache/*
    fi
    # Remove temporary mount point
    if [ -e ${SNAPSHOT_DIR}/var/lib/ca-certificates ]; then
	rmdir --ignore-fail-on-non-empty ${SNAPSHOT_DIR}/var/lib/ca-certificates
    fi
    # systemd-tmpfiles creates directories/files even if /run is no tmpfs:
    rm -rf ${SNAPSHOT_DIR}/run/*
    # WARNING: /var/spool/ can contain changes through RPM!
    rm -rf ${SNAPSHOT_DIR}/var/spool/*
}

reboot_via_rebootmgr() {
    /usr/sbin/rebootmgrctl is-active --quiet
    if [ $? -eq 0 ]; then
	# rebootmgrctl is running
	TELEM_PAYLOAD="${TELEM_PAYLOAD}\nreboot=rebootmgr"
	telem_finish 0
	/usr/sbin/rebootmgrctl reboot
	log_info "transactional-update finished - informed rebootmgr"
	exit 0
    fi
}

reboot_via_systemd() {
    TELEM_PAYLOAD="${TELEM_PAYLOAD}\nreboot=systemctl reboot"
    telem_finish 0
    log_info "transactional-update finished - rebooting machine"
    sync
    systemctl reboot |& tee -a ${LOGFILE}
    exit 0
}

reboot_via_kured() {
    log_info "transactional-update finished - informed kured"
    TELEM_PAYLOAD="${TELEM_PAYLOAD}\nreboot=kured"
    telem_finish 0
    touch /var/run/reboot-required
    exit 0
}

reboot_via_kexec() {
    log_info "transactional-update finished - re-initializing system with kexec"
    TELEM_PAYLOAD="${TELEM_PAYLOAD}\nreboot=kexec"
    telem_finish 0
    kexec -l /boot/vmlinuz --initrd=/boot/initrd --reuse-cmdline
    sync
    systemctl kexec |& tee -a ${LOGFILE}
    exit 0
}

reboot_autodetect() {
    if [ -x /usr/sbin/rebootmgrctl ]; then
	reboot_via_rebootmgr
    fi
    # If rebootmgr is inactive try systemd
    reboot_via_systemd
}

add_unique_id() {
    local NEW_ID="$1"

    for snap in ${LAST_WORKING_SNAPSHOTS}; do
	if [ ${snap} -eq ${NEW_ID} ]; then
	    return
	fi
    done
    LAST_WORKING_SNAPSHOTS="${NEW_ID} ${LAST_WORKING_SNAPSHOTS}"
}

check_registration_on_next_reboot() {
    local VARDIR="/var/lib/rollback"
    # If VARDIR is part of the root file system (usually on rw systems), then
    # create the file in the new snapshot
    if [ "$(findmnt --noheadings --output TARGET --target "${VARDIR}")" = "/" ]; then
	VARDIR="${MOUNT_DIR}/${VARDIR}"
    fi
    test -d "${VARDIR}" || mkdir -p "${VARDIR}"
    touch "${VARDIR}/check-registration"
}

get_etc_overlay_from() {
    local overlay_options
    local option lowerdir overlay_id

    fstab=""
    fstab_lowerdirs=()
    fstab_upper=""
    fstab_previous_snapshot=""
    fstab_otheroptions=""

    # Check for /etc overlay
    for fstab in "$1"/etc/fstab{,.sys}; do
	if [ -e "$fstab" ] ; then
	    overlay_options="`findmnt --noheadings --types "overlay" --options "workdir=/sysroot/var/lib/overlay/work-etc" --output OPTIONS --tab-file "$fstab" /etc`"
	    [ -n "${overlay_options}" ] && break
	fi
    done
    [ -n "${overlay_options}" ] || return

    for option in `echo ${overlay_options} | tr ',' ' '`; do
	case "${option%=*}" in
	    upperdir)
		fstab_upper="${option#*=}"
		;;
	    lowerdir)
		for lowerdir in `echo ${option#*=} | tr ':' ' '`; do
		    overlay_id=`echo "${lowerdir#/sysroot}" | sed -n 's#^/var/lib/overlay/\([[:digit:]]\+\)/etc$#\1#p'`

		    if [ -z "${fstab_previous_snapshot}" -a -n "${overlay_id}" ]; then
			fstab_previous_snapshot="/.snapshots/${overlay_id}/snapshot"
		    fi
		    fstab_lowerdirs+=("${lowerdir}")
		done
		;;
	    workdir)
		;;
	    x-systemd.requires-mounts-for|x-initrd.mount)
		;;
	    *)
		fstab_otheroptions="${fstab_otheroptions}${option},"
		;;
	esac
    done
}

# Replace all /sysroot prefixes from /etc fstab entry; for the overlays in /var
# the prefix can simply be removed, while the lowest layer - the /etc directory
# on the root file system - has to be replaced with the actual snapshot path,
# as /etc overmounted with the current overlay already.
parse_lowerdirs_for_mount() {
    fstab_lowerdirs=("${fstab_lowerdirs[@]///sysroot\/var//var}")
    echo ${fstab_lowerdirs[@]///sysroot/$1} | tr ' ' ':'
}

# Sync /etc mount contents to target directory
sync_etc() {
    local mount_opts overlay_id rsync_extra_args

    get_etc_overlay_from "$1"

    log_info "Syncing /etc of previous snapshot $1 as base into new snapshot $2"

    mount_opts="defaults,"
    mount_opts+="lowerdir="
    mount_opts+="${fstab_upper#/sysroot}:"
    mount_opts+="`parse_lowerdirs_for_mount $1`"
    mount -t overlay overlay "${ETC_OVERLAY_DIR}" -o ${mount_opts}
    if [ $? -ne 0 ]; then
	log_error "ERROR: mount of old overlay stack from $1 failed!"
	quit 1
    fi

    if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled ; then
	# Ignore the SELinux attributes when synchronizing pre-SELinux files,
	# rsync will fail otherwise
	if [ "$(ls --directory --context "${ETC_OVERLAY_DIR}" | cut -d ' ' -f 1 | cut -d ':' -f 3)" == "unlabeled_t" ]; then
	    rsync_extra_args=("--filter=-x security.selinux")
	fi
    fi
    rsync --archive --inplace --xattrs "${rsync_extra_args[@]}" --acls --delete --quiet "${ETC_OVERLAY_DIR}" "$2"
    if [ $? -ne 0 ]; then
	log_error "ERROR: syncing $1 into snapshot $2 failed!"
	quit 1
    fi
    umount "${ETC_OVERLAY_DIR}"
}

# Return the optimized list of mount points
root_mount_points() {
    local prev=0
    for mount in $(findmnt --noheadings --submounts --target / --output TARGET --raw | sort | tail -n +2); do
	if [ "${prev}/" != "${mount:0:$((${#prev}+1))}" ]; then
	    echo "${mount}"
	    prev="${mount}"
	fi
    done
}

check_resolved_resolvconf() {
    [ -L /etc/resolv.conf ] || return 0
    local resolveddir="/run/systemd/resolve"
    local r="`readlink /etc/resolv.conf`"
    if [ "${r%/*}" = "$resolveddir" ]; then
        mkdir -p "${SNAPSHOT_DIR}$resolveddir"
        cp "$resolveddir"/*.conf "${SNAPSHOT_DIR}$resolveddir"
    fi
}

ORIG_ARGS=("$@")

while [ 1 ]; do
    if [ $# -eq 0 ]; then
	break
    fi

    case "$1" in
	cleanup)
	    DO_CLEANUP_OVERLAYS=1
	    DO_CLEANUP_SNAPSHOTS=1
	    shift
	    ;;
	cleanup-snapshots)
	    DO_CLEANUP_SNAPSHOTS=1
	    shift
	    ;;
	cleanup-overlays)
	    DO_CLEANUP_OVERLAYS=1
	    shift
	    ;;
	dup)
	    DO_DUP=1
	    ZYPPER_ARG="--no-cd dup"
	    shift
	    TELEM_CLASS="upgrade"
	    ;;
        up|patch)
	    ZYPPER_ARG=$1
	    shift
	    TELEM_CLASS="update"
	    ;;
	ptf|pkg|package)
	    TELEM_CLASS="package"
	    shift
	    if [ $# -eq 0 ]; then
		usage 1
	    fi
            # Interactively run installing PTFs
            ZYPPER_NONINTERACTIVE="${FORCE_NONINTERACTIVE:-}"
	    case "$1" in
		install|in)
		    ZYPPER_ARG="install"
		    shift
		    ;;
		remove|rm)
		    ZYPPER_ARG="remove"
		    if [ -n "${ZYPPER_NONINTERACTIVE}" ]; then
			ZYPPER_NONINTERACTIVE="-y"
		    fi
		    shift
		    ;;
		update|up)
		    ZYPPER_ARG="up"
		    shift
		    ;;
		*)
		    usage 1;
		    ;;
	    esac

	    if [ $# -eq 0 ]; then
		usage 1
	    fi

	    while [ 1 ]; do
		if [ $# -eq 0 ]; then
		    break;
		else
		    ZYPPER_ARG_PKGS+=("$1");
		    shift
		fi
	    done
	    ;;
	migration)
	    TELEM_CLASS="migration"
	    __NO_RESET=0
	    DO_MIGRATION=1
	    ZYPPER_ARG="migration --no-snapshots --no-selfupdate"
	    if [ -n "${FORCE_NONINTERACTIVE}" ]; then
		# non interative option is named different for
		# different commands ...
		ZYPPER_NONINTERACTIVE="--non-interactive --auto-agree-with-licenses"
	    else
		ZYPPER_NONINTERACTIVE=""
	    fi
	    shift
	    ;;
	bootloader)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="bootloader"
	    REWRITE_BOOTLOADER=1
	    REWRITE_GRUB_CFG=1
	    shift
	    ;;
	grub.cfg)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="bootloader"
	    REWRITE_GRUB_CFG=1
	    shift
	    ;;
	shell)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="shell"
	    RUN_SHELL=1
	    shift
	    ;;
	initrd)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="initrd"
	    REWRITE_INITRD=1
	    REBUILD_KDUMP_INITRD=1
	    shift
	    ;;
	kdump)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="kdump"
	    REBUILD_KDUMP_INITRD=1
	    shift
	    ;;
	reboot)
	    REBOOT_AFTERWARDS=1
	    shift
	    ;;
	rollback)
	    TELEM_CLASS="rollback"
	    DO_ROLLBACK=1
	    DO_SELF_UPDATE=0
	    shift
	    if [ $# -eq 1 ]; then
		ROLLBACK_SNAPSHOT=$1
		shift
	    fi
	    ;;
	run)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="shell"
	    DO_RUN=1
	    shift

	    # Collect arguments for run
	    if [ $# -eq 0 ]; then
		usage 1
	    fi

	    RUN_CMD=("$@")
	    break
	    ;;
	setup-selinux)
	    test -z "$TELEM_CLASS" && TELEM_CLASS="selinux"
	    SETUP_SELINUX=1
	    shift
	    ;;
	-i|--interactive)
	    ZYPPER_NONINTERACTIVE=""
	    shift
	    ;;
	-n|--non-interactive)
	    FORCE_NONINTERACTIVE="${ZYPPER_NONINTERACTIVE}"
	    shift
	    ;;
	-c|--continue)
	    # Check whether we got an optional snapshot number argument
	    if [[ $2 =~ ^[0-9]+$ ]]; then
		BASE_SNAPSHOT_ID="$2"
		shift
	    else
		BASE_SNAPSHOT_ID="default"
	    fi
	    shift
	    ;;
	--no-selfupdate)
	    DO_SELF_UPDATE=0
	    shift
	    ;;
	-d|--drop-if-no-change)
	    DROP_IF_NO_CHANGE=1
	    shift
	    ;;
	--quiet)
	    VERBOSITY=1
	    shift
	    ;;
	register)
	    DO_REGISTRATION=1
	    shift

	    # Collect arguments for Registration
	    if [ $# -eq 0 ]; then
		usage 1
	    fi

	    while [ 1 ]; do
		if [ $# -eq 0 ]; then
		    break;
		else
		    REGISTRATION_ARGS="${REGISTRATION_ARGS} $1";
		    shift
		fi
	    done
	    ;;
	-h|--help)
	    usage 0
	    ;;
	--version)
	    print_version
	    ;;
	*)
	    if [ $# -ge 1 ]; then
		usage 1;
	    fi
	    ;;
    esac
done

# Setup SELinux
if [ "${SETUP_SELINUX}" -eq 1 ]; then
    # Setting up SELinux requires several steps:
    # 1. Make sure the policies are installed
    # 2. Adjust /etc/default/grub
    # 3. Adjust /etc/selinux/config
    # 4. Rebuild grub.cfg and initrd

    if [ -n "${ZYPPER_ARG}" -a "${ZYPPER_ARG}" != "install" ]; then
	log_error "ERROR: Cannot combine 'setup-selinux' with zypper command '${ZYPPER_ARG}'"
	exit 1
    fi
    # Check if we need to install packages
    for pkg in selinux-policy-targeted container-selinux; do
	rpm -q --quiet ${pkg} || ZYPPER_ARG_PKGS+=("${pkg}")
    done
    if [ ${#ZYPPER_ARG_PKGS[@]} -ne 0 ]; then
	ZYPPER_ARG="install"
    fi
    REWRITE_INITRD=1
    REBUILD_KDUMP_INITRD=1

    # Make sure /var/lib/selinux exists, else installing the
    # Policy will fail
    test -d /var/lib/selinux || mkdir -p /var/lib/selinux
fi

# If no commands were given, assume "up"
if [ -z "${ZYPPER_ARG}" -a -z "${TELEM_CLASS}" -a "${REBOOT_AFTERWARDS}" -eq 0 \
	-a "${DO_REGISTRATION}" -eq 0 -a "${DO_CLEANUP_OVERLAYS}" -eq 0 \
	-a "${DO_CLEANUP_SNAPSHOTS}" -eq 0 ]; then
    ZYPPER_ARG="up"
    TELEM_CLASS="update"
fi

# Prevent running transactional-update inside transactional-update
if [ -n "${TRANSACTIONAL_UPDATE}" ]; then
    log_error "Cannot call transactional-update from within transactional-update environment!"
    exit 1
fi

# Check if this is a self-updated transactional-update; if it isn't lock and
# check for update
if [ -z "${TA_UPDATE_TMPFILE}" ]; then
    bashlock
    if [ $? -ne 0 ]; then
	log_error "Couldn't get lock, is another instance already running?"
	exit 1
    fi
    self_update "${ORIG_ARGS[@]}"
else # Set exit handler to clean up artifacts of the self-update
    trap 'rm -f "$LOCKFILE" && rm -rf "${TA_UPDATE_TMPFILE}" && unset TA_UPDATE_TMPFILE' EXIT
fi

# Clean up in case the application is interrupted
trap 'log_error "Received termination signal..." && quit 1' HUP INT QUIT TERM

# Load old state file
if [ -f ${STATE_FILE} ]; then
    . ${STATE_FILE}
fi

log_info "transactional-update 2.28.3 started"
log_info "Options: ${ORIG_ARGS[@]}"

telem_start

SNAPPER_VERSION=`snapper --version | head -1 | cut -d ' ' -f 2`
if [ -n "${BASE_SNAPSHOT_ID}" -a $(zypper --terse versioncmp $SNAPPER_VERSION 0.8.4) -lt 0 ]; then
    log_error "ERROR: snapper >= 0.8.4 required for --continue option!"
    log_info "transactional-update finished"
    telem_finish 1
    exit 1
fi

if [ $DROP_IF_NO_CHANGE -eq 1 ] && ! command -v inotifywait &> /dev/null; then
    log_error "ERROR: 'inotifywait' is required for --drop-if-no-change option!"
    log_info "transactional-update finished"
    exit 1
fi

if [ "`stat -f -c %T /`" != "btrfs" ]; then
  log_error "ERROR: not using btrfs as root file system!"
  log_info "transactional-update finished"
  telem_finish 1
  exit 1
fi

if [ ! -d /.snapshots ]; then
  log_error "ERROR: no snapshots for root file system configured!"
  log_info "transactional-update finished"
  telem_finish 1
  exit 1
fi

grep -q "[[:space:]]/var[[:space:]]" /proc/mounts
if [ $? -eq 0 ]; then
    log_info "Separate /var detected."
    DIR_TO_MOUNT="${DIR_TO_MOUNT} var/cache"
    for vardir in /var/lib/alternatives /var/lib/selinux ; do
        if [ -d "${vardir}" ]; then
	    DIR_TO_MOUNT="${DIR_TO_MOUNT} ${vardir}"
         fi
    done
    HAS_SEPARATE_VAR=1
else
    grep -q var.cache /proc/mounts
    if [ $? -ne 0 ]; then
	log_error "WARNING: it looks like your installation isn't recent enough."
    fi
fi

if [ -n "${ZYPPER_ARG}" -a "${DO_MIGRATION}" -eq 0 -a ${ZYPPER_AUTO_IMPORT_KEYS} -eq 1 ]; then
    ZYPPER_ARG="--gpg-auto-import-keys ${ZYPPER_ARG}"
fi

CURRENT_SNAPSHOT_ID=`grep subvol=/@/.snapshots/ /proc/mounts | grep "/ btrfs" | sed -e 's|.*.snapshots/\(.*\)/snapshot.*|\1|g'`
DEFAULT_SNAPSHOT_ID=`btrfs subvolume get-default / | sed -e 's|.*.snapshots/\(.*\)/snapshot|\1|g'`
RO_ROOT=`btrfs property get / ro | sed -e 's|ro=||'`

if [ -z "${BASE_SNAPSHOT_ID}" ]; then
    BASE_SNAPSHOT_ID="${CURRENT_SNAPSHOT_ID}"
elif [ "${BASE_SNAPSHOT_ID}" = "default" ]; then
    BASE_SNAPSHOT_ID="${DEFAULT_SNAPSHOT_ID}"
fi

get_etc_overlay_from "/.snapshots/${BASE_SNAPSHOT_ID}/snapshot"
if [ -n "$fstab_upper" ]; then
    log_info "/etc on overlayfs detected."
    ETC_IS_OVERLAY=1
fi

if [ ${DO_ROLLBACK} -eq 1 ]; then
    NEED_REBOOT_WARNING=1

    if [ "${ROLLBACK_SNAPSHOT}" = "last" ]; then
	if [ -n "${LAST_WORKING_SNAPSHOTS}" ]; then
	    ROLLBACK_SNAPSHOT=${LAST_WORKING_SNAPSHOTS%% *}
	else
	    log_error "No last working snapshot saved; please use 'snapper list' for manual selection"
	fi
    elif [ ${ROLLBACK_SNAPSHOT} -eq 0 -o ${ROLLBACK_SNAPSHOT} -eq ${CURRENT_SNAPSHOT_ID} ]; then
	ROLLBACK_SNAPSHOT=${CURRENT_SNAPSHOT_ID}
	NEED_REBOOT_WARNING=0
    fi

    log_info "Rollback to snapshot ${ROLLBACK_SNAPSHOT}..."

    if [ ${RO_ROOT} == "true" ]; then
	BTRFS_ID=`btrfs subvolume list -o /.snapshots | grep /.snapshots/${ROLLBACK_SNAPSHOT}/snapshot | awk '{print $2}'`
	if [ -z $BTRFS_ID ]; then
	    log_error "ERROR: couldn't determine btrfs subvolume ID"
	    quit 1
	else
	    btrfs subvolume set-default $BTRFS_ID /.snapshots
	    if [ $? -ne 0 ]; then
		log_error "ERROR: btrfs set-default $BTRFS_ID failed!"
		quit 1
	    fi
	    # Create the trigger to re-register the system as new version after next
	    # reboot.
	    check_registration_on_next_reboot
	fi
	# Remove possible cleanup algo
	snapper ${SNAPPER_NO_DBUS} modify -c '' ${ROLLBACK_SNAPSHOT}

	if [ ${NEED_REBOOT_WARNING} -eq 0 ]; then
	    rm -f "${NEEDS_RESTARTING_FILE}"
	fi
    else
	snapper rollback ${ROLLBACK_SNAPSHOT}
	NEED_REBOOT_WARNING=1
    fi
    if [ ${NEED_REBOOT_WARNING} -eq 1 ]; then
	log_error "Please reboot to finish rollback!"
    fi
    telem_finish 0
    exit 0
fi

#
# Cleanup part: make sure old root file systems will be removed after they are no longer active.
#
if [ ${DO_CLEANUP_SNAPSHOTS} -eq 1 ]; then
    # If there is a list of working snapshots, go through it and mark any snapshot for deletion, if it is
    # not the currently used one or the active one.
    if [ -n "${LAST_WORKING_SNAPSHOTS}" ]; then
	for snap in ${LAST_WORKING_SNAPSHOTS}; do
	    if [ ${CURRENT_SNAPSHOT_ID} -ne ${snap} ]; then
		log_info "Adding cleanup algorithm to snapshot #${snap}"
		snapper modify -c number ${snap} |& tee -a ${LOGFILE}
		if [ ${PIPESTATUS[0]} -ne 0 ]; then
		    log_error "ERROR: cannot set cleanup algorithm for snapshot #${snap}"
		fi
		# If the old snapshot is read-write, we have already a mandatory snapshot and this one can deleted
		# earlier. If not, mark is as important, so that it will not get deleted too fast.
		if [ ${RO_ROOT} == "true" ]; then
		    log_info "Adding \"important=yes\" to snapshot #${snap}"
		    snapper modify -u "important=yes" ${snap} |& tee -a ${LOGFILE}
		    if [ ${PIPESTATUS[0]} -ne 0 ]; then
			log_error "ERROR: cannot set \"important=yes for snapshot\" #${snap}"
		    fi
		fi
	    else
		NEW_LIST="${snap} ${NEW_LIST}"
	    fi
	done
	LAST_WORKING_SNAPSHOTS="${NEW_LIST}"
	save_state_file 0
    fi

    # Check for aborted transactional-updates (due to power outtage, killed
    # process, forced shutdown or similar uncommon conditions).
    # As snapper list output differs between versions search for the correct
    # rows first
    for snap in $(snapper list | awk -F '|' '
	    NR==1 { for(i=1; i<=NF; i++) { gsub(/^ +/, "", $i); gsub(/ +$/, "", $i); fields[$i]=i }}
	    NR>2 { if($fields["Userdata"] ~ "transactional-update-in-progress=yes") { print $fields["#"] }}'); do
	UNUSED_SNAPSHOTS="${UNUSED_SNAPSHOTS} ${snap}"
    done

    # Always try to cleanup all snapshots; only the current snapshot and an
    # eventual new default one needs to be kept.
    if [ -n "${UNUSED_SNAPSHOTS}" ]; then
	_new_unused=""
	for snap in ${UNUSED_SNAPSHOTS}; do
	    # Don't mark our current in use snapshot for deletion
	    if [ ${snap} -ne ${CURRENT_SNAPSHOT_ID} ] && \
		[ ${snap} -ne ${DEFAULT_SNAPSHOT_ID} ]; then
		log_info "Mark unused snapshot #${snap} for deletion"
		snapper modify -c number ${snap} |& tee -a ${LOGFILE}
		if [ ${PIPESTATUS[0]} -ne 0 ]; then
		    log_error "ERROR: cannot set cleanup algorithm for snapshot #${snap}"
		    # Is the snapshot still available at all?
		    if [ -e /.snapshots/${snap} ]; then
			# Keep the snapshot in the list
			_new_unused="${snap} ${_new_unused}"
		    fi
		fi
	    elif [ ${snap} -ne ${CURRENT_SNAPSHOT_ID} ]; then
		# This is the snapshot which is currently in use, so keep it in
		# the list. We would probably never clean it up later otherwise.
		_new_unused="${snap} ${_new_unused}"
	    fi
	done
	UNUSED_SNAPSHOTS="${_new_unused}"
	save_state_file 0
    fi
fi

if [ ${DO_CLEANUP_OVERLAYS} -eq 1 ]; then
    # Clean up old unused overlays
    if [ ${RO_ROOT} == "true" ]; then
	shopt -s nullglob
	for overlay in /var/lib/overlay/[0-9]*/etc /var/lib/overlay/etc; do
	    if [ -e ${overlay} ] && ! grep -qs "${overlay}" /.snapshots/*/snapshot/etc/fstab{,.sys}; then
		log_info "Deleting unused overlay ${overlay}"
		rm -rf "${overlay}"
		rmdir --ignore-fail-on-non-empty "$(dirname "${overlay}")"
	    fi
	done
	shopt -u nullglob
    fi
fi

if [ -n "${ZYPPER_ARG}" -o ${REWRITE_GRUB_CFG} -eq 1 \
    -o ${REWRITE_INITRD} -eq 1 -o ${REBUILD_KDUMP_INITRD} -eq 1 \
    -o ${RUN_SHELL} -eq 1 -o ${DO_RUN} -eq 1 \
    -o ${REWRITE_BOOTLOADER} -eq 1 -o ${DO_REGISTRATION} -eq 1 ]; then

    if [ "${DEFAULT_SNAPSHOT_ID}" -ne "${BASE_SNAPSHOT_ID}" ]; then
	log_info "WARNING: You are creating a snapshot from a different base (${BASE_SNAPSHOT_ID}) than the"
	log_info "         current default snapshot (${DEFAULT_SNAPSHOT_ID})."
	if [ "${BASE_SNAPSHOT_ID}" -eq "${CURRENT_SNAPSHOT_ID}" ]; then
	    log_info "         If you want to continue a previous snapshot use the --continue"
	    log_info "         option, otherwise the previous changes will be discarded."
	fi
    fi

    # Create the working snapshot
    snapper_args=(create --print-number --userdata "transactional-update-in-progress=yes")
    if [ "${BASE_SNAPSHOT_ID}" -ne "${CURRENT_SNAPSHOT_ID}" ]; then
	snapper_args+=(--from ${BASE_SNAPSHOT_ID})
    fi
    snapper_args+=(--description "Snapshot Update of #${BASE_SNAPSHOT_ID}")
    SNAPSHOT_ID=`snapper "${snapper_args[@]}"`
    if [ $? -ne 0 ]; then
	SNAPPER_NO_DBUS="--no-dbus"
	SNAPSHOT_ID=`snapper ${SNAPPER_NO_DBUS} "${snapper_args[@]}"`
	if [ $? -ne 0 ]; then
	    log_error "ERROR: snapper create failed!"
	    quit 1
	fi
    fi

    SNAPSHOT_DIR=/.snapshots/${SNAPSHOT_ID}/snapshot

    # Make the snapshot read-write:
    btrfs property set  ${SNAPSHOT_DIR} ro false
    if [ $? -ne 0 ]; then
	log_error "ERROR: changing ${SNAPSHOT_DIR} to read-write failed!"
	quit 1
    fi

    if [ ${ETC_IS_OVERLAY} -eq 1 ]; then
	# Create new /etc overlay for snapshot
	ETC_OVERLAY_DIR="/var/lib/overlay/${SNAPSHOT_ID}/etc"
	ETC_OVERLAY_DIR_PREV="/var/lib/overlay/${BASE_SNAPSHOT_ID}/etc"
	ETC_OVERLAY_WORK_DIR="${ETC_OVERLAY_DIR}-work"

	# If the user is calling `snapper delete` on the latest snapshot, then
	# the snapshot number will be reused; make sure to delete any artifacts
	rm -rf "${ETC_OVERLAY_DIR}"

	mkdir -p "${ETC_OVERLAY_DIR}" "${ETC_OVERLAY_WORK_DIR}"

	if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled ; then
            chcon --reference /etc "${ETC_OVERLAY_DIR}"
	fi

	get_etc_overlay_from /
	current_upper="${fstab_upper}"
	get_etc_overlay_from "/.snapshots/${BASE_SNAPSHOT_ID}/snapshot"

	# Copy the previous snapshot's /etc state into new snapshot (if it
	# hasn't been synced in a previous layer already)
	if [ -n "${fstab_previous_snapshot}" ] && [[ ${fstab_lowerdirs[@]} != *"${current_upper}"* ]]; then
	    sync_etc "${fstab_previous_snapshot}" "${SNAPSHOT_DIR}"
	    get_etc_overlay_from "/.snapshots/${BASE_SNAPSHOT_ID}/snapshot"
	fi

	# Special handling /etc/fstab{.sys}: dracut needs the *current* version
	# of the files in the snapshot, as the overlay is not yet mounted
	# during early boot - in fact the files are required to be able to
	# mount the overlays
	if [ -e "${ETC_OVERLAY_DIR_PREV}/fstab" ]; then
	    cp "${ETC_OVERLAY_DIR_PREV}/fstab" "${SNAPSHOT_DIR}/etc"
	fi
	if [ -e "${ETC_OVERLAY_DIR_PREV}/fstab.sys" ]; then
	    cp -a "${ETC_OVERLAY_DIR_PREV}/fstab.sys" "${SNAPSHOT_DIR}/etc"
	fi

	# Migrate fstab.sys' /var and overlay entries to regular fstab
	if [ -e "${SNAPSHOT_DIR}/etc/fstab.sys" ] && grep -qE "^overlay /etc" "${SNAPSHOT_DIR}/etc/fstab.sys"; then
	    awk '$3 == "overlay" && $4 ~ /=\/sysroot\/var\/lib\/overlay/' "${SNAPSHOT_DIR}/etc/fstab.sys" >> "${SNAPSHOT_DIR}/etc/fstab"
	    # Remove /etc, /var and /var/lib/overlay (legacy) from fstab.sys
	    gawk -i inplace '$2 != "/etc" && $2 != "/var" && $2 != "/var/lib/overlay" &&
		($3 != "overlay" || $4 !~ /=\/sysroot\/var\/lib\/overlay\//)' "${SNAPSHOT_DIR}/etc/fstab.sys"
	    # Append x-initrd.mount to /var mount options (bsc#1121279)
	    gawk -i inplace '$2 == "/var" || $2 == "/var/lib/overlay" { $4 = $4",x-initrd.mount"; } 1' "${SNAPSHOT_DIR}/etc/fstab"
	    gawk -i inplace '$3 == "overlay" && $4 ~ "=/sysroot/var/lib/overlay/" { $4 = $4",x-systemd.requires-mounts-for=/var,x-systemd.requires-mounts-for=/var/lib/overlay,x-systemd.requires-mounts-for=/sysroot/var,x-systemd.requires-mounts-for=/sysroot/var/lib/overlay,x-initrd.mount"; } 1' "${SNAPSHOT_DIR}/etc/fstab"
	fi

	# Check whether the new snapshot is based on the currently running
	# one - if so, then keep the overlay stack to not loose changes
	# applied until reboot
	if [[ ${fstab_lowerdirs[@]} == *"${current_upper}"* ]]; then
	    lowerdirs="${fstab_upper}:$(echo ${fstab_lowerdirs[@]} | tr ' ' ':')"
	else
	    lowerdirs="${fstab_upper}:/sysroot/etc"
	fi

	# Update fstab{,.sys} entries for /etc overlays
	fstab_options="${fstab_otheroptions}"
	fstab_options+="upperdir=/sysroot${ETC_OVERLAY_DIR},"
	fstab_options+="lowerdir=${lowerdirs},"
	fstab_options+="workdir=/sysroot/var/lib/overlay/work-etc,"
	fstab_options+="x-systemd.requires-mounts-for=/var,"
	fstab_options+="x-systemd.requires-mounts-for=/var/lib/overlay,"
	fstab_options+="x-systemd.requires-mounts-for=/sysroot/var,"
	fstab_options+="x-systemd.requires-mounts-for=/sysroot/var/lib/overlay,"
	fstab_options+="x-initrd.mount"
	if [ ${#fstab_options} -ge $(getconf PAGE_SIZE) ]; then
	    log_error "ERROR: Exceeding maximum length of mount options; please boot into the new"
	    log_error "       snapshot before proceeding."
	    quit 1
	fi
	fstab_line="overlay /etc overlay ${fstab_options} 0 0"
	sed -i "/${ETC_OVERLAY_PATTERN}/c ${fstab_line}" "${SNAPSHOT_DIR}/etc/fstab"

	# Prettify fstab
	gawk -i inplace 'NR==FNR { for(i=1;i<=NF;i++) { len=length($i); collen[i]=collen[i]>len?collen[i]:(len>42?42:len); } print }
	    NR!=FNR { for(i=1;i<=NF;i++) { printf("%-"collen[i]"s  ", $i); } print ""}' "${SNAPSHOT_DIR}/etc/fstab" "${SNAPSHOT_DIR}/etc/fstab"

	# Make the diff visible to the admin by copying /etc/fstab into the
	# overlay (dracut itself wouldn't need it)
	cp "${SNAPSHOT_DIR}/etc/fstab" "${ETC_OVERLAY_DIR}"
    fi

    # Remember all snapshots we create for update. If transactional-update is
    # run several times before a reboot, we need to clean up the unused
    # snapshots, otherwise we would have a big disk space leak. But don't store
    # it on disk yet, in error case we would delete the snapshot again.
    UNUSED_SNAPSHOTS="${SNAPSHOT_ID} ${UNUSED_SNAPSHOTS}"

    # Check if installed with SLES12
    if [ ${HAS_SEPARATE_VAR} -eq 0 ]; then
	touch ${SNAPSHOT_DIR}/var/tmp/update_snapshot.test
	if [ $? -ne 0 ]; then
	    log_error "ERROR: System installation is too old!"
	    quit 1
	fi
	rm -f ${SNAPSHOT_DIR}/var/tmp/update_snapshot.test
    else
        # Check if the btrfs subvolumes were created correct
        # or with broken storage-ng [bsc#1077240]
        touch ${SNAPSHOT_DIR}/var/update_snapshot.test
        if [ $? -ne 0 ]; then
            log_error "ERROR: System installation is broken!"
            quit 1
        fi
        rm -f ${SNAPSHOT_DIR}/var/update_snapshot.test
    fi

    if [ ${RUN_SHELL} -eq 1 ]; then
	DIR_TO_MOUNT="${DIR_TO_MOUNT} root"
    fi
    if [ -d /boot/writable ]; then
	DIR_TO_MOUNT="${DIR_TO_MOUNT} /boot/writable"
    fi

    # Bind system directories into chroot environment
    for sysdir in /proc /sys /dev; do
	if ! mount --rbind "${sysdir}" "${SNAPSHOT_DIR}${sysdir}" ||
	   ! mount --make-rslave "${SNAPSHOT_DIR}${sysdir}"; then
	    log_error "ERROR: mounting ${sysdir} failed!"
	    quit 1
	fi
    done

    # Check which directories in /boot/grub2 need to be mounted,
    # otherwise grub2 will not boot after a version update.
    DIR_TO_MOUNT="${DIR_TO_MOUNT} $(awk '$2 ~ /^\/boot\/grub2\// { print $2 }' /proc/mounts)"
    # On EFI systems the EFI partition is also required
    if [ -e /boot/efi ]; then
	DIR_TO_MOUNT="${DIR_TO_MOUNT} /boot/efi"
    fi

    if [ ${ETC_IS_OVERLAY} -eq 1 ]; then
	lowerdir="${fstab_upper///sysroot\/var//var}:"
	# Is the snapshot based on the currently running system?
	if [[ ${fstab_lowerdirs[@]} == *"${current_upper}"* ]]; then
	    lowerdir+="$(parse_lowerdirs_for_mount "/.snapshots/${BASE_SNAPSHOT_ID}/snapshot")"
	    # Mounting an already used overlay directory again is not
	    # supported by overlayfs - use the old mount point instead
	    lowerdir="${lowerdir/${current_upper#/sysroot}*/\/etc}"
	else
	    # Use the (synced) /etc directory of the new snapshot as base
	    lowerdir+="/.snapshots/${SNAPSHOT_ID}/snapshot/etc"
	fi

	etc_opts="defaults,"
	etc_opts+="lowerdir=${lowerdir},"
	etc_opts+="upperdir=${ETC_OVERLAY_DIR},"
	etc_opts+="workdir=${ETC_OVERLAY_WORK_DIR}"
	mount -t overlay overlay ${SNAPSHOT_DIR}/etc -o ${etc_opts}
	if [ $? -ne 0 ]; then
	    log_error "ERROR: mount of etc failed!"
	    quit 1;
	fi
    fi

    for directory in $DIR_TO_MOUNT ; do
	# Make sure mount point exists. With /var on an own subvolume, this directory
	# is empty by default and mount points don't exist in chroot environment.
	test -d ${SNAPSHOT_DIR}/$directory || mkdir -p ${SNAPSHOT_DIR}/$directory
	mount -o bind /$directory ${SNAPSHOT_DIR}/$directory
	if [ $? -ne 0 ]; then
	    log_error "ERROR: mount of $directory failed!"
	    quit 1
	fi
    done

    # If we have a seperate /var, create some files / directories which we
    # will delete again later.
    if [ ${HAS_SEPARATE_VAR} -eq 1 ]; then
	mkdir ${SNAPSHOT_DIR}/var/tmp
	if [ -f /var/lib/zypp/RequestedLocales ]; then
	    mkdir -p ${SNAPSHOT_DIR}/var/lib/zypp
	    cp /var/lib/zypp/RequestedLocales ${SNAPSHOT_DIR}/var/lib/zypp/
	fi
    fi

    # Check if we have /var/lib/rpm, otherwise zypper will
    # create a new rpm database [bsc#1074598]
    if [ ! -e ${SNAPSHOT_DIR}/var/lib/rpm -a \
	-e ${SNAPSHOT_DIR}/usr/lib/sysimage/rpm ]; then
	mkdir -p ${SNAPSHOT_DIR}/var/lib
	ln -sf ../../usr/lib/sysimage/rpm ${SNAPSHOT_DIR}/var/lib/rpm
    fi

    # Copy network configuration into chroot
    # The links in /etc may either point to /var/run or /run, and the root file
    # system snapshot may already contain the /var/run -> /run link or not.
    if [ -e /run/netconfig ]; then
	if [ ${HAS_SEPARATE_VAR} -eq 1 ]; then
	    ln -sf /run ${SNAPSHOT_DIR}/var/run
	fi
	cp -r /run/netconfig ${SNAPSHOT_DIR}/run/
    fi

    check_resolved_resolvconf

    # Create bind mount for CA certificates to support HTTPS connections
    if [ ${HAS_SEPARATE_VAR} -eq 1 ]; then
	mkdir -p ${SNAPSHOT_DIR}/var/lib/ca-certificates
	mount -o bind /var/lib/ca-certificates ${SNAPSHOT_DIR}/var/lib/ca-certificates
    fi

    # Do we need to cleanup the /var/cache directory?
    if [ ! -d ${SNAPSHOT_DIR}/var/cache/zypp ]; then
	VAR_CACHE_CLEANUP=1
    fi

    # Create bind mounts or else grub2 will fail
    MOUNT_DIR=`mktemp -d`
    mount -o rbind ${SNAPSHOT_DIR} ${MOUNT_DIR}
    mount -o bind,ro /.snapshots ${MOUNT_DIR}/.snapshots

    if [ $DROP_IF_NO_CHANGE -eq 1 ]; then
	#TODO: Handle directories with special characters (space, newline, apostrophe etc)
	for snapmount in $(root_mount_points); do
	    INOTIFY_EXCLUDES="${INOTIFY_EXCLUDES} @${SNAPSHOT_DIR}${snapmount}"
	done
	INOTIFY_PID=$( ( inotifywait -r -e modify -e move -e create -e delete -e attrib "${SNAPSHOT_DIR}" ${INOTIFY_EXCLUDES} 2>&1 & echo "PID: $!" ) | ( grep -m1 '^PID: ' | cut -d ' ' -f 2 && grep -q "Watches established" ) )
    fi

    # Set indicator for RPM pre/post sections to detect whether we run in a
    # transactional update
    export TRANSACTIONAL_UPDATE=true

    if [ ${DO_REGISTRATION} -eq 1 ]; then
	SUSEConnect --root ${MOUNT_DIR}  ${REGISTRATION_ARGS}
    fi

    if [ -n "${ZYPPER_ARG}" ]; then

	log_info "Calling zypper ${ZYPPER_ARG}"
	if [ ${DO_MIGRATION} -eq 1 ]; then
	    # transactional-update migration
	    env DISABLE_RESTART_ON_UPDATE=yes zypper ${ZYPPER_ARG} --root ${MOUNT_DIR} ${ZYPPER_NONINTERACTIVE} "${ZYPPER_ARG_PKGS[@]}" |& tee -a ${LOGFILE}
	    RETVAL=${PIPESTATUS[0]}
	else
	    # Check if there are updates at all.
	    TMPFILE=`mktemp ${TMPDIR}/transactional-update.XXXXXXXXXX`
	    zypper -R ${MOUNT_DIR} --xmlout ${ZYPPER_ARG} -y --auto-agree-with-product-licenses --dry-run "${ZYPPER_ARG_PKGS[@]}" > ${TMPFILE}
	    PACKAGE_UPDATES=`grep "install-summary download-size" ${TMPFILE} | sed -e 's|.*install-summary download-size=\"\(.*\)\" space-usage-diff.*|\1|g'`
	    SIZE_OF_UPDATES=`grep "install-summary.*space-usage-diff" ${TMPFILE} | sed -e 's|.*install-summary.*space-usage-diff=\"\([^"]*\)\".*|\1|g'`
	    NUM_OF_UPDATES=`grep "install-summary.*packages-to-change" ${TMPFILE} | sed -e 's|.*install-summary.*packages-to-change=\"\([^"]*\)\".*|\1|g'`
	    rm -f ${TMPFILE}
	    TELEM_PAYLOAD="${TELEM_PAYLOAD}\npackages=${NUM_OF_UPDATES}\ndownload_size=${PACKAGE_UPDATES}\nspace-usage=${SIZE_OF_UPDATES}"
	    if [ "${NUM_OF_UPDATES}" = "0" ] || [ -z "${NUM_OF_UPDATES}" -a "${PACKAGE_UPDATES}" = "0" -a "${SIZE_OF_UPDATES}" = "0" ]; then
		log_info "zypper: nothing to update"
		quit 0
	    fi

	    env DISABLE_RESTART_ON_UPDATE=yes zypper -R ${MOUNT_DIR} ${ZYPPER_ARG} ${ZYPPER_NONINTERACTIVE} "${ZYPPER_ARG_PKGS[@]}" |& tee -a ${LOGFILE}
	    RETVAL=${PIPESTATUS[0]}
	fi
	# in case of migration, we need to do a little bit more:
	if [ ${DO_MIGRATION} -eq 1 ]; then
	    # Reset registration until reboot. Needed in both cases,
	    # whether an error occured or whether we had success.
	    test -x /usr/sbin/SUSEConnect && /usr/sbin/SUSEConnect --rollback
	    if [ $RETVAL -eq 0 ]; then
		# Create the trigger to re-register the system as new version after next
		# reboot.
		check_registration_on_next_reboot
	    fi
	fi

	if [ $RETVAL -eq 0 -o $RETVAL -eq 102 -o $RETVAL -eq 103 -o \( $DO_DUP -eq 0 -a $RETVAL -eq 106 \) ]; then
	    REBUILD_KDUMP_INITRD=1
	    # check if products are updated and we need to re-register
	    # at next boot.
	    diff -qr /etc/products.d ${MOUNT_DIR}/etc/products.d > /dev/null
	    if [ $? -ne 0 ]; then
		check_registration_on_next_reboot
	    fi
	    # Rebuild grub.cfg if /etc/os-release changes, could change grub
	    # menu output, too.
	    cmp -s /etc/os-release ${MOUNT_DIR}/etc/os-release
	    if [ $? -ne 0 ]; then
	        REWRITE_GRUB_CFG=1
	    fi
	    source <(grep VERSION_ID ${MOUNT_DIR}/etc/os-release)
	    TELEM_PAYLOAD="${TELEM_PAYLOAD}\nnext_version=${VERSION_ID}"
	else
	    log_error "ERROR: zypper ${ZYPPER_ARG} on ${MOUNT_DIR} failed with exit code ${RETVAL}!"
	    if [ -n "${ZYPPER_NONINTERACTIVE}" ]; then
		log_error "Use '--interactive' for manual problem resolution."
	    fi
	    EXITCODE=1
	fi
    fi

    if [ ${SETUP_SELINUX} -eq 1 ]; then
	# Adjust grub configuration

	# Check if we don't have selinux already enabled.
	grep ^GRUB_CMDLINE_LINUX_DEFAULT /etc/default/grub | grep -q security=selinux || \
	    sed -i -e 's|\(^GRUB_CMDLINE_LINUX_DEFAULT=.*\)"|\1 security=selinux selinux=1"|g' "${MOUNT_DIR}/etc/default/grub"
	REWRITE_GRUB_CFG=1

	if [ ! -e "${MOUNT_DIR}/etc/selinux/config" ]; then
	    log_error "ERROR: /etc/selinux/config does not exist!"
	    EXITCODE=1
	fi
	# Adjust selinux config
	sed -i -e 's|^SELINUX=.*|SELINUX=enforcing|g' \
	    -e 's|^SELINUXTYPE=.*|SELINUXTYPE=targeted|g' \
	    "${MOUNT_DIR}/etc/selinux/config"

	# Move an /.autorelabel file from initial installation to writeable location
	test -f ${MOUNT_DIR}/.autorelabel && mv ${MOUNT_DIR}/.autorelabel ${MOUNT_DIR}/etc/selinux/.autorelabel
    fi

    if [ ${REWRITE_INITRD} -eq 1 ]; then
	log_info "Creating new initrd"
	chroot ${MOUNT_DIR} /sbin/mkinitrd
	if [ $? -ne 0 ]; then
	    log_error "ERROR: mkinitrd failed!"
	    EXITCODE=1
	else
	    REBUILD_KDUMP_INITRD=1
	fi
    fi

    if [ ${REBUILD_KDUMP_INITRD} -eq 1 ]; then
	log_info "Trying to rebuild kdump initrd"
	rebuild_kdump_initrd ${MOUNT_DIR}
    fi

    if [ ${REWRITE_GRUB_CFG} -eq 1 ]; then
	log_info "Creating a new grub2 config"
	chroot ${MOUNT_DIR} /usr/sbin/grub2-mkconfig > ${MOUNT_DIR}/boot/grub2/grub.cfg
	if [ $? -ne 0 ]; then
	    log_error "ERROR: grub2-mkconfig failed!"
	    EXITCODE=1;
        else
            if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled ; then
                chcon --reference /boot/grub2/grub.cfg "${MOUNT_DIR}/boot/grub2/grub.cfg"
            fi
	fi
    fi

    if [ ${REWRITE_BOOTLOADER} -eq 1 ]; then
	log_info "Writing new bootloader"
	chroot ${MOUNT_DIR} /sbin/pbl --install
	if [ $? -ne 0 ]; then
	    log_error "ERROR: /sbin/pbl --install failed!"
	    EXITCODE=1;
	fi
    fi

    if [ ${DO_RUN} -eq 1 ]; then
	chroot ${MOUNT_DIR} "${RUN_CMD[@]}"
    fi

    if [ ${RUN_SHELL} -eq 1 ]; then
	log_info "Opening chroot in snapshot ${SNAPSHOT_ID}, continue with 'exit'"
	env PS1="transactional update # " chroot ${MOUNT_DIR} bash 2>&4
    fi

    if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled ; then
        chcon --reference /etc/fstab "${MOUNT_DIR}/etc/fstab"
    fi

    teardown

    # Somersault:
    if [ $EXITCODE -eq 0 ]; then
	BTRFS_ID=`btrfs subvolume list -o /.snapshots | grep ${SNAPSHOT_DIR} | awk '{print $2}'`
	if [ -z "$BTRFS_ID" ]; then
	    log_error "ERROR: couldn't determine btrfs subvolume ID"
	    EXITCODE=1
	else
	    btrfs subvolume set-default $BTRFS_ID ${SNAPSHOT_DIR}
	    if [ $? -ne 0 ]; then
		log_error "ERROR: btrfs set-default $BTRFS_ID failed!"
		EXITCODE=1;
	    else
		# Save the old snapshot or else it will get lost.
		add_unique_id ${CURRENT_SNAPSHOT_ID}
		save_state_file ${SNAPSHOT_ID}
		# Create flag file for dracut scripts
		if [ ${RO_ROOT} = "true" ]; then
		    echo "EXPECTED_SNAPSHOT_ID=${SNAPSHOT_ID}" > "${NEW_SNAPSHOT_FLAG}"
		    echo "PREV_SNAPSHOT_ID=${CURRENT_SNAPSHOT_ID}" >> "${NEW_SNAPSHOT_FLAG}"
		fi
		# Reset in-progress flag
		snapper ${SNAPPER_NO_DBUS} modify -u "transactional-update-in-progress=" ${SNAPSHOT_ID}
	    fi
	fi
    fi

    # Make the snapshot ro flag identical to current root:
    btrfs property set  ${SNAPSHOT_DIR} ro ${RO_ROOT}
    if [ $? -ne 0 ]; then
	log_error "ERROR: changing ${SNAPSHOT_DIR} to ro=${RO_ROOT} failed!"
	EXITCODE=1
    fi

    # Check for installation artefacts: Packages may have created files in
    # directories outside of the root file system; these files will not be
    # visible in the actual system as they are shadowed by the real mount
    # points, so warn the user
    searchdirs=""
    # Filter out commented lines and swap partition
    for mountdir in $(awk '$1 !~ "^#.*" && $2 ~ "^/.*" { print $2 }' ${SNAPSHOT_DIR}/etc/fstab); do
	# Filter out root fs and fstab entries which we bind mounted anyway
	if [[ ! $mountdir =~ ^(/|/etc|${DIR_TO_MOUNT// /|})$ ]]; then
	    searchdirs+="${SNAPSHOT_DIR}${mountdir} "
	fi
    done
    if [ -n "${searchdirs}" ]; then
	filelist="$(find ${searchdirs} -cnewer ${LOCKFILE} -not -type d 2>/dev/null)"
	# Filter acceptable hits
	whitelist=""
	for wlentry in "${NON_ROOTFS_WHITELIST[@]}"; do
	    whitelist+="${SNAPSHOT_DIR}${wlentry}\|"
	done
	filelist="$(echo "$filelist" | grep -v "^\(${whitelist::-2}\)")"

	if [ -n "$filelist" ]; then
	    log_info
	    log_info "Warning: The following files were changed in the snapshot, but are shadowed by"
	    log_info "other mounts and will not be visible to the system:"
	    log_info "${filelist}"
	fi
    fi

    if [ ${EXITCODE} -ne 0 ]; then
	quit ${EXITCODE}
    elif [ $REBOOT_AFTERWARDS -eq 0 ]; then
	log_error "\nPlease reboot your machine to activate the changes and avoid data loss."
	touch "${NEEDS_RESTARTING_FILE}"
    fi

    log_info "New default snapshot is #${SNAPSHOT_ID} (${SNAPSHOT_DIR})."
fi

if [ ${EXITCODE} -eq 0 ]; then
    if [ $REBOOT_AFTERWARDS -eq 1 ]; then
	trap '-' HUP INT QUIT TERM
	case "$REBOOT_METHOD" in
	    auto)
		reboot_autodetect
		;;
	    kured)
		reboot_via_kured
		;;
	    rebootmgr)
		reboot_via_rebootmgr
		;;
	    systemd)
		reboot_via_systemd
		;;
	    kexec)
		reboot_via_kexec
		;;
	    none)
		;;
	    *)
	        log_info "Unsupported reboot method, falling back to 'auto'; please"
	        log_info "check your configuration in ${CONFFILE}."
	        reboot_autodetect
	        ;;
	esac
	log_error "The system couldn't be rebooted using method '${REBOOT_METHOD}'. Please reboot the system"
	log_error "manually."
    fi
fi

log_info "transactional-update finished"

telem_finish $EXITCODE

exit $EXITCODE
