#!/bin/bash
### This file is covered by the GNU General Public License
### version 3 or later.
###
### Copyright (C) 2019, 2025, ALT Linux Team
### Author: Leonid Krivoshein <klark@altlinux.org>

### Пример "универсального" скрипта для создания резервной копии
### установленной операционной системы ALT Linux, версия 0.1.5

set -o errexit
set -o noglob
set -o nounset

# Под каким именем будет сохранён очередной бэкап (каталог).
# По умолчанию используется текущая дата в формате YYYY-MM-DD.
#
backup_name="$(LC_TIME=C date "+%F")"

# Хранилище бэкапов: точка монтирования либо каталог
backup_base=/mnt/autorun

# ID записи UEFI-загрузки, используемый по умолчанию
default_loader_id=altlinux

# Алгоритмы вычисляемыех контрольных сумм (md5, sha1, sha256, ...)
digests=sha256

# Максимальная длина имени архива
archive_name_limit=16

# Порядковый номер найденного /etc/fstab для
# авто-определения нужного корневого раздела
#
rootfs_number=

# 1, если нужно просканировать все устройства, вывести список
# корневых разделов с названиями/версиями ОС и завершить работу
#
scanonly=

# 1, чтобы показывать выполняемые действия, но ничего не делать
dryrun=

# 1, если целевой компьютер может умереть при записи данных в NVRAM
no_nvram=

# 1, если исходные файловые системы необходимо почистить до сохранения
cleanup=

# 1, если в исходной системе нужно переименовать единственный сетевой интерфейс
rename_iface=

# 1, если требуется более подробный вывод сообщений (режим отладки)
verbose=

# 1, если необходимо подавить вывод сообщений в стандартный поток вывода
quiet=

# 1, если необходимо отмонтировать $backup_base по окончанию работы
need_unmount=

# 1, если съёмные носители должны считаться пригодными целевыми дисками
use_removable=

# Список точек монтирования, по которым определяются защищаемые диски
protected_mpoints="/ /image /mnt/backup $backup_base"

# Игнорируемые при поиске целых дисков начальные имена устройств
skip_disk_names="ram fd loop sr"

# Опции tar для создания бэкапов
tar_opts="--numeric-owner --one-file-system"
tar_opts="$tar_opts --warning=no-file-ignored"
tar_opts="$tar_opts --transform='s,^\\.\\/,,'"

# Опции монтирования разделов FAT и ESP (EFI System Partition)
vfatopts="noatime,umask=0,quiet,showexec,iocharset=utf8,codepage=866"

# Опции монтирования раздела /boot
bootopts="noatime,nodiratime,nodev,noexec"

# Опции монтирования корневого раздела
rootopts="noatime,nodiratime"

# Опции монтирования разделов данных
dataopts="noatime,nodiratime,nosuid"

# Определяемый дополнительными опциями командной строки список
# устройств или точек монтирования в целевой системе, которые
# бэкапить не следует
#
skip_volumes=

# Определяемый дополнительными опциями командной строки список
# исключений (шаблонов типа "mnt/*"), которые бэкапить не следует
#
excludes='lost+found mnt/target'

# Автоматически определяемые в процессе работы скрипта
# имена устройств разделов, которые будут сохранены
# (обязательным является только "корневой" раздел):
#
# $esp_part соответствует /boot/efi
# $bootpart соответствует /boot
# $swappart соответствует SWAP
# $rootpart соответствует /
# $var_part соответствует /var
# $homepart соответствует /home
#
esp_part=
bootpart=
swappart=
rootpart=
var_part=
homepart=

# Все остальные разделы сохраняются в виде списка элементов,
# отделяемых одним пробелом в формате <device>:<fstype>:<mpoint>
#
other_parts=

# Путь к найденному SWAP-файлу, который не следует сохранять в архиве
swap_filename=

# Размер обнаруженного SWAP-файла, который нужно создать при восстановлении
swap_filesize=

# Список потенциально возможных расположений SWAP-файлов
swap_filelist="/swap /swapfile"

# Автоматически определяемый список защищаемых от бэкапа устройств
protected_devices=

# Автоматически определяемый идентификатор записи UEFI-загрузки
boot_loader_id=

# Изначально: определяемый в командной строке целевой диск или раздел.
# В процессе работы скрипта: эта переменная получит список всех дисков
# (родительских устройств), на которых расположены сохраняемые разделы.
#
targets=

# Способ определения целевых устройств
tgtway=auto

# Список устройств (разделов или томов btrfs) с разметкой под Timeshift
timeshift=

# 1 Мегабайт
MiB=$((1024 * 1024))

# Название данного скрипта
progname="${0##*/}"


# Выводит подсказку и завершает работу
#
show_help()
{
	echo "Использование: $progname [<опции>...] [--] [<цель>]"
	echo
	echo "Цель - это блочное устройство (диск или раздел), которое будет"
	echo "просканировано, чтобы найти хотя бы один корневой раздел Linux."
	echo "По нему определяются другие разделы, подлежащие сохранению. По"
	echo "умолчанию сохраняются все найденные таким образом разделы. Если"
	echo "на диске имеется более одной ОС Linux, обязательно указывать"
	echo "порядковый номер нужной ОС, иначе ничего не будет сохранено."
	echo "Цель указывать необязательно - в этом случае сканируются все"
	echo "доступные блочные устройства, за исключением защищаемых."
	echo
	echo "Опции:"
	echo
	echo "-b, --base <DIR>    Базовый каталог для сохранения всех бэкапов."
	echo "-n, --name <NAME>   Сохранить создаваемый бэкап под этим именем."
	echo "-s, --skip <LIST>   Не сохранять перечисленные через запятую"
	echo "                    разделы, диски и точки монтирования. UUID=id"
	echo "                    и LABEL=метка допустимы, данную опцию можно"
	echo "                    указывать более одного раза."
	echo "-S, --scan-only     Только вывести список операционных систем."
	echo "-X, --exclude <PAT> Добавляет указанный шаблон к исключениям."
	echo "                    Данную опцию можно указать несколько раз."
	echo "-f, --fstab <NO>    Сохранить указанную по счёту Linux-систему."
	echo "-r, --removable     Разрешить использование съёмных носителей."
	echo "-R, --rename-iface  Переименовать единственный сетевой интерфейс."
	echo "-c, --cleanup       Очищать файловые системы перед сохранением."
	echo "-p, --no-nvram      Запретить запись каких-либо данных в NVRAM."
	echo "-t, --tar <LOPTS>   Добавить перечисленные через запятую длинные"
	echo "                    опции при запуске программы tar, данную опцию"
	echo "                    можно указывать более одного раза. Например,"
	echo "                    -t xattrs,acls добавит опции --xattrs --acls."
	echo "-U, --uuid <UUID>   Взять раздел с этим UUID в качестве целевого."
	echo "-L, --label <LABEL> Взять раздел с этой меткой в качестве целевого."
	echo "-C, --current,self  Сохранить текущую систему, с которой грузились."
	echo "-l, --loader <ID>   Использовать этот идентификатор UEFI-загрузки."
	echo "-A, --algo <LIST>   Список алгоритмов контрольных сумм через запятую."
	echo "-N, --no-act,dryrun Только показать намерения, но ничего не делать."
	echo "-v, --verbose       Выводить больше деталей в сообщениях (отладка)."
	echo "-q, --quiet         Ничего не выводить в стандартный поток вывода."
	echo "-h, --help          Показать данную подсказку и завершить работу."
	echo
	echo "Пожалуйста, сообщайте об ошибках на https://bugzilla.altlinux.org/"
	exit ${1-0}
}

# Обеспечивает фатальный выход с выводом сообщения об ошибке
#
fatal()
{
	echo "$progname СБОЙ: $*" >&2
	exit 1
}

# Выводит отладочное сообщение
#
dbg()
{
	[ -z "$verbose" ] || echo "$*" >&2
}

# Выводит сообщение в стандартный поток вывода
#
msg()
{
	[ -n "$quiet" ] || echo "$*"
}

# Выполняет или не выполняет указанные действия, в зависимости
# от режима выполнения, с выводом сообщения в отладочный журнал
#
run()
{
	dbg "Executing: $*"
	[ -n "$dryrun" ] || "$@"
}

# Конвертирует байты в мегабайты с округлением до ближайшего целого
#
size2mib()
{
	local m=$(($1 / $MiB))

	[ "$(($1 % $MiB))" = 0 ] ||
		m=$(($m + 1))
	printf '%s' "$m"
}

# Выводит сортированный список, соответствующий указанному шаблону
#
glob()
{
	(set +f; eval ls -X1 -- "$@" ||:) 2>/dev/null
}

# В реальном режиме выполнения рекурсивно сохраняет все файлы
# текущего каталога в указанный архив $1 при помощи tar и pigz,
# в режиме симуляции только показывает запускаемые команды
#
backup_linux()
{
	local archive="$1" mpoint="$2"
	local i pv options="$tar_opts"

	shift 2
	msg "Сохраняется $mpoint..."

	for i in $excludes; do
		options="$options --exclude='./$i'"
	done

	dbg "Executing: cd '$(pwd)' &&"
	dbg "Executing: tar -cpSf - $options $* |"
	dbg "Executing: pigz -9qnc > '$backupdir/$archive.tgz'"

	[ -z "$dryrun" ] ||
		return 0
	pv="$(command -v pv 2>/dev/null ||:)"

	if [ -n "$verbose" ] || [ -n "$quiet" ] || [ ! -x "$pv" ]
	then
		eval tar -cpSf - $options "$@" |
			pigz -9qnc > "$backupdir/$archive.tgz"
	else
		eval tar -cpSf - $options "$@" |"$pv" -rbt |
			pigz -9qnc > "$backupdir/$archive.tgz"
	fi
}

# Выводит каталог с точкой монтирования, если указанное
# устройство $1 уже смонтировано с файловой системой $2.
# Необязательный параметр $3 может содержать subvolume
# name: в этом случае проверка выполняется с учётом
# подключения указанного подтома btrfs.
#
device_mpoint()
{
	local devname="${1##/dev/}" fstype="$2"
	local name sysfs mp="" subvol="${3-}" blkdev=

	__device_mpoint_inner()
	{
		if [ -z "$subvol" ] ||
		   [ "$fstype" != btrfs ]
		then
			mp="$(grep -s -v ',subvol=' /proc/mounts |
				awk '{print $1 " " $2 " " $3;}' |
				grep -E "^/dev/${devname//-/\-} " |
				grep -E -- " ${fstype}$" |
				awk '{print $2;}' |
				tail -n1)"
		else
			[ "${subvol:0:1}" != / ] ||
				subvol="${subvol:1}"
			mp="$(grep -s -E ",subvol=/?$subvol[, ]" /proc/mounts |
				awk '{print $1 " " $2 " " $3;}' |
				grep -E "^/dev/${devname//-/\-} " |
				grep -E " btrfs$" |
				awk '{print $2;}' |
				tail -n1)"
		fi

		[ "${mp:0:1}" = / ] && mountpoint -q -- "$mp" || mp=
	}

	__device_mpoint_inner

	if [ -n "$mp" ]; then
		printf '%s' "$mp"
		return 0
	fi

	case "$devname" in
	dm-[0-9]*)
		sysfs="$(mountpoint -x "/dev/$devname" 2>/dev/null ||:)"
		[ -n "$sysfs" ] ||
			return 0
		sysfs="$(readlink -fv "/sys/dev/block/$sysfs" 2>/dev/null ||:)"
		[ -n "$sysfs" ] && [ -f "$sysfs/dm/name" ] ||
			return 0
		read name < "$sysfs/dm/name"
		[ -n "$name" ] ||
			return 0
		blkdev="$(readlink -fv "/dev/mapper/$name" 2>/dev/null ||:)"
		[ "$blkdev" = "/dev/$devname" ] ||
			return 0
		devname="mapper/$name"
		__device_mpoint_inner
		printf '%s' "$mp"
		;;
	esac

	return 0
}

# Размонтирует указанный каталог $1, если он смонтирован
#
cond_unmount()
{
	test -d "$1" && umount -fl -- "$1" >/dev/null 2>&1 ||:
}

# Рекурсивно размонтирует в нужном порядке всё, что смонтировано в /mnt/target
#
unmount_all_targets()
{
	local mp list

	list="$(grep -s -E -- ' /mnt/target[ /]' /proc/mounts |
			sort -k2,2 -rs |
			awk '{print $2;}')"
	if [ -n "$list" ]; then
		dbg "Unmounting target filesystems..."
		#
		for mp in $list; do
			dbg "  - unmounting $mp..."
			umount -fl -- "$mp" >/dev/null 2>&1 ||:
		done
		#
		dbg
	fi
}

# Очищающий следы работы скрипта обработчик завершения работы
#
exit_handler()
{
	local rc="$?"

	trap - EXIT; cd /

	if [ "$backup_base" = /mnt/autorun ] &&
	   grep -qs ' /mnt/autorun ext2 r' /proc/mounts
	then
		if [ -n "$need_unmount" ]; then
			umount /mnt/autorun >/dev/null 2>&1 &&
				dbg "Unmounting /mnt/autorun (alt-autorun) media..." ||
				need_unmount=
		fi

		if [ -z "$need_unmount" ]; then
			dbg "Remounting /mnt/autorun for read-only access..."
			mount -o remount,ro /mnt/autorun ||:
		fi
	fi

	[ ! -d /mnt/target ] ||
		unmount_all_targets
	[ -z "${probedir-}" ] ||
		cond_unmount "${probedir-}"

	if [ -d "$workdir" ]; then
		if mountpoint -q -- "$workdir"; then
			umount -fl -- "$workdir" ||:
			rmdir -- "$workdir" ||:
		else
			rm -rf --one-file-system -- "$workdir"
		fi
	fi

	rmdir /mnt/target ||:
	exit $rc
}

# Выводит тип файловой системы для указанного устройства $1
#
getfstype()
{
	blkid -o value -s TYPE -- "$1" 2>/dev/null ||:
}

# Выводит размер устройства в мегабайтах
#
get_devsize()
{
	local bytes

	bytes=$(blockdev --getsize64 -- "$1" 2>/dev/null ||:)
	[ -z "$bytes" -o "$bytes" = 0 ] && echo -n 0 || size2mib $bytes
}

# Сохраняет метаданные устройства $1 в файлы с именем $2.*
#
save_fsmeta()
{
	local regex tag dev="$1" f="$2"

	tag="$(getfstype "$dev")"

	case "$tag" in
	ext2|ext3|ext4|ext4dev)
		regex='/^Filesystem features:[[:space:]]*/!d'
		regex="$regex;s/^[^:]*:[[:space:]]*//"
		dumpe2fs -f -- "$dev" 2>/dev/null |
			sed -e "$regex" |
			tr ' ' '\n' > "$metadir/$f.e2fs"
		;;

	btrfs)	[ -n "$timeshift" ] && in_array "$dev" $timeshift ||
			btrfs inspect-internal dump-super -- "$dev" \
					> "$metadir/$f.btrfs" 2>&1 ||:
		;;

	jfs)	jfs_tune -l -- "$dev" >"$metadir/$f.jfs" 2>&1 ||:
		;;

	xfs)	xfs_info "$dev" >"$metadir/$f.xfs" 2>&1 ||:
		;;
	esac

	tag=$(get_devsize "$dev")
	echo -n "$tag" > "$metadir/$f.size"
	tag="$(blkid -o value -s LABEL -- "$dev" 2>/dev/null ||:)"
	[ -z "$tag" ] ||
		echo -n "$tag" > "$metadir/$f.label"
	tag="$(blkid -o value -s UUID -- "$dev" 2>/dev/null ||:)"
	[ -z "$tag" ] ||
		echo -n "$tag" > "$metadir/$f.uuid"
	return 0
}

# Возвращает 0, если блочное устройство $1 имеет файловую систему $2
#
expect_fs()
{
	[ -b "$1" ] && [ "$(getfstype "$1")" = "$2" ]
}

# Возвращает 0 в случае Linux-совместимой файловой системы $1
#
is_linux_fs()
{
	case "$1" in
	ext2|ext3|ext4|ext4dev|jfs|xfs|reiserfs|btrfs|zfs)
		return 0;;
	esac

	return 1
}

# Убеждается, что блочное устройство $1 для точки монтирования
# $2 имеет Linux-совместимую файловую систему, и в случае успеха
# возвращает 0, иначе генерирует соответствующее фатальное
# сообщение и завершает работу скрипта
#
must_be_linux()
{
	local fs

	[ -b "$1" ] ||
		fatal "Ошибочный раздел: $1 ($2)"
	fs="$(getfstype "$1")"
	is_linux_fs "$fs" ||
		fatal "Неподдерживаемая файловая система '$fs' на разделе $1 ($2)"
	return 0
}

# Ищет элемент $1 в массиве $* и возвращет 0 в случае успеха
#
in_array()
{
	local needle="$1" item=""

	shift

	for item in $*; do
		[ "$item" != "$needle" ] ||
			return 0
	done

	return 1
}

# Выводит карту подключенных носителей в формате "major:minor <TAB> devname",
# исключая те, чьи имена начинаются с перечисленных в списке $skip_disk_names
#
show_devmap()
{
	local f major minor dev b

	[ -f /proc/partitions ] ||
		fatal "Вероятно, файловая система /proc не смонтирована!"
	sed -e '3,$!d' /proc/partitions |
	while read major minor b dev
	do
		b=
		if [ -n "$skip_disk_names" ]; then
			for f in $skip_disk_names; do
				case "$dev" in
				${f}[0-9]*)
					b=1
					break
					;;
				esac
			done

			[ -z "$b" ] ||
				continue
		fi
		echo -e "$major:$minor\t$dev"
	done
}

# Определяет по ранее созданной карте носителей
# и имени узла /dev/$1 его major:minor номер
#
get_devno()
{
	local devno devname needle="$1"

	while read devno devname; do
		if [ "$needle" = "$devname" ]; then
			echo -n "$devno"
			break
		fi
	done < "$workdir"/devices.map
}

# Рекурсивно определяет и выводит список всех
# родительских устройств для указанного /dev/$1
#
get_all_parents()
{
	local devname="$1"
	local devno sysfs number

	[ -b "/dev/$devname" ] ||
		return 0

	# Если $devname является разделом диска
	if [ ! -f "/sys/block/$devname/dev" ]; then
		if [ -f "/sys/class/block/$devname/partition" ]; then
			read number < "/sys/class/block/$devname/partition"
			devname="${devname%$number}"
			[ -b "/dev/$devname" ] ||
				devname="${devname%p}"

			if [ -b "/dev/$devname" ]; then
				devname="$(readlink -fv "/dev/$devname")"

				case "$devname" in
				/dev/*)	devname="${devname##/dev/}";;
				*)	devname="";;
				esac

				if [ -b "/dev/$devname" ]; then
					get_all_parents "$devname"
					echo "$devname"
				fi
			fi
		fi
	fi

	# Если устройство находится на других устройствах
	devno="$(get_devno "$devname")"
	[ -n "$devno" ] ||
		return 0
	sysfs="$(readlink -fv "/sys/dev/block/$devno")"
	[ -d "$sysfs" ] ||
		return 0
	#
	if [ -f "$sysfs/dev" ] && [ -d "$sysfs/slaves" ]; then
		read number < "$sysfs/dev"

		if [ "$number" = "$devno" ]; then
			sysfs="$(glob "$sysfs/slaves/*")"

			if [ -n "$sysfs" ]; then
				for devname in $sysfs; do
					devname="${devname##*/}"

					if [ -b "/dev/$devname" ]; then
						get_all_parents "$devname"
						echo "$devname"
					fi
				done
			fi
		fi
	fi
}

# Возвращает 0, если указанное устройство /dev/$1 защищено от бэкапа
#
is_protected()
{
	[ -n "$protected_devices" ] && in_array "$1" $protected_devices
}

# Проверяет целевой носитель на предмет допустимости использования
# в качестве исходного диска или раздела, с которого можно снимать
# резервную копию
#
check_target_device()
{
	local dummy sysfs devno devname

	devname="$(readlink -fv -- "$targets")"

	case "$devname" in
	/dev/*)	devname="${devname##/dev/}";;
	*)	devname="";;
	esac

	[ -n "$devname" ] && [ -b "/dev/$devname" ] ||
		fatal "Целевой диск ($targets) не является блочным устройством!"
	devno="$(get_devno "$devname")"
	[ -n "$devno" ] ||
		fatal "Ошибка определения номера целевого диска!"
	sysfs="$(readlink -fv "/sys/dev/block/$devno")"
	[ -f "$sysfs/dev" ] ||
		fatal "Целевой диск не найден в /sys/dev/block!"
	read dummy < "$sysfs/dev"
	[ "$dummy" = "$devno" ] ||
		fatal "Обнаружено внутреннее несоответствие!"

	if [ -n "$cleanup" ]; then
		[ -r "$sysfs/ro" ] && read dummy < "$sysfs/ro" ||
			dummy="$(blockdev --getro "/dev/$devname")"
		[ "$dummy" = 0 ] ||
			fatal "Целевой диск только для чтения, его нельзя очистить!"
	fi

	if [ -z "$use_removable" ] && [ -r "$sysfs/removable" ]; then
		read dummy < "$sysfs/removable"
		[ "$dummy" = 0 ] ||
			fatal "Съёмные диски следует бэкапить с флагом --removable!"
	fi

	tgtway=part
	targets="$devname"
	dummy="$(getfstype "/dev/$devname")"

	if is_linux_fs "$dummy"; then
		tgtway=disk
	elif [ -f "/sys/block/$devname/dev" ]; then
		read dummy < "/sys/block/$devname/dev"

		if [ "$dummy" = "$devno" ]; then
			dummy="$(blkid -o value -s PTTYPE "/dev/$devname" 2>/dev/null ||:)"

			if [ "$dummy" != dos ] && [ "$dummy" != gpt ]; then
				dbg "WARNING: Device /dev/$devname doesn't have MBR/GPT label!"
			else
				dummy="$(glob "/dev/${devname}?*")"
				[ -n "$dummy" ] ||
					fatal "Целевые разделы не найдены!"
				tgtway=disk
				targets=

				for dummy in $(glob "/dev/${devname}?*"); do
					targets="$targets ${dummy##/dev/}"
				done

				targets="${targets## }"
			fi
		fi
	fi
}

# Пересоздаёт списки пропускаемых томов и защищаемых разделов
#
rebuild_skipped_volumes()
{
	local dev mp ptl

	if [ -n "$skip_volumes" ]; then
		ptl="$skip_volumes"
		skip_volumes=

		for mp in $ptl; do
			case "$mp" in
			/dev/?*)   dev="$(readlink -fv -- "$mp")";;
			LABEL=?*)  dev="$(blkid -L "${mp##LABEL=}" 2>/dev/null ||:)";;
			UUID=?*)   dev="$(blkid -U "${mp##UUID=}" 2>/dev/null ||:)";;
			/?*)	   dev="";;
			*)	   dbg "Invalid volume '$mp', it will be ignored!"
				   continue;;
			esac

			case "$dev" in
			/dev/?*)   dev="${dev##/dev/}";;
			esac

			if [ -z "$dev" ] || [ ! -b "/dev/$dev" ]; then
				skip_volumes="$skip_volumes $mp"
			elif ! is_protected "$dev"; then
				protected_devices="$protected_devices $dev"
			fi
		done

		skip_volumes="${skip_volumes## }"
	fi

	dbg "Skip volumes:    ${skip_volumes:-<EMPTY LIST>}"
	dbg
}

# Определяет список устройств, с которых могла быть
# загружена ОС или на которые может сохраняться бэкап
#
autodetect_protected()
{
	local dev mp plist
	local ptl="$protected_mpoints"

	in_array "$backup_base" $ptl ||
		ptl="$ptl $backup_base"

	for mp in $ptl; do
		dev="$(grep -s -- " $mp " /proc/mounts |
			tail -n1 |
			cut -f1 -d ' ')"
		[ -n "$dev" ] && [ -d "$mp" ] ||
			continue
		dev="$(readlink -fv -- "$dev")"

		case "$dev" in
		/dev/*)	dev="${dev##/dev/}";;
		*)	continue;;
		esac

		[ -b "/dev/$dev" ] ||
			continue
		is_protected "$dev" ||
			protected_devices="$protected_devices $dev"
		plist="$(get_all_parents "$dev")"
		[ -n "$plist" ] ||
			continue

		for dev in $plist; do
			is_protected "$dev" ||
				protected_devices="$protected_devices $dev"
		done
	done

	rebuild_skipped_volumes
	protected_devices="${protected_devices## }"
	dbg "Protect devices: ${protected_devices:-<EMPTY LIST>}"
	dbg
}

# Определяет все целевые носители, пригодные для бэкапа
#
autodetect_targets()
{
	local devno devname sysfs f b plist

	dbg "Auto-detecting targets..."

	while read devno devname; do
		if [ ! -b "/dev/$devname" ]; then
			dbg "  - /dev/$devname is not a block device"
			continue
		fi

		if is_protected "$devname"; then
			dbg "  - /dev/$devname protected directly"
			continue
		fi

		sysfs="$(readlink -fv "/sys/dev/block/$devno")"
		if [ ! -r "$sysfs/dev" ]; then
			dbg "  - $devname not found in the /sys/dev/block"
			continue
		fi

		read b < "$sysfs/dev"
		if [ "$b" != "$devno" ]; then
			dbg "  - /dev/$devname will be skipped ($devno<>$b)"
			continue
		fi

		[ -r "$sysfs/ro" ] && read b < "$sysfs/ro" ||
			b="$(blockdev --getro "/dev/$devname")"
		if [ "$b" != 0 ]; then
			dbg "  - /dev/$devname is read-only, it will be skipped"
			continue
		fi

		if [ -z "$use_removable" ] && [ -r "$sysfs/removable" ]; then
			read b < "$sysfs/removable"

			if [ "$b" != 0 ]; then
				dbg "  - /dev/$devname is removable, it will be skipped"
				continue
			fi
		fi

		targets="$targets $devname"
	done < "$workdir"/devices.map

	[ -n "$targets" ] ||
		fatal "Целевых устройств не обнаружено!"
	targets="${targets## }"
}

# Определяет и выводит версию операционной системы
#
get_sysver()
{
	local f="${1-}/etc/os-release"
	local version="Unknown" v=

	if [ -r "$f" ]; then
		if grep -qs -E ^PRETTY_NAME= "$f"; then
			v="$(grep -s -E ^PRETTY_NAME= "$f" |
				tail -n1 |
				cut -f2- -d= |
				tr -d \")"
			[ -z "$v" ] ||
				version="$v"
		elif grep -qs -E ^NAME= "$f" &&
		     grep -qs -E ^VERSION= "$f"
		then
			v="$(grep -s -E ^NAME= "$f" |
				tail -n1 |
				cut -f2- -d= |
				tr -d \")"

			if [ -n "$v" ]; then
				version="$v"
				v="$(grep -s -E ^VERSION= "$f" |
					tail -n1 |
					cut -f2- -d= |
					tr -d \")"
				[ -z "$v" ] ||
					version="$version $v"
			fi
		fi
	fi

	echo -n "$version"
}

# Проверяет обязательные объекты корневого каталога
#
check_rootdir()
{
	[ -r "$probedir/etc/fstab" ] &&
	[ -r "$probedir/etc/os-release" ] &&
	[ -d "$probedir/root" ] &&
	[ -d "$probedir/proc" ] &&
	[ -d "$probedir/sys" ] &&
	[ -d "$probedir/run" ] &&
	[ -d "$probedir/usr" ]
}

# Сканирует все целевые разделы (ищет корневые разделы Linux).
# В режиме scan-only все найденные корневые разделы выводятся
# сплошным списком, после чего работа скрипта прекращается.
# В остальных случаях проверяется, что найден хотя бы один
# корневой раздел, что найден лишь один корневой раздел либо
# найден раздел, соответвующий указанному порядковому номеру.
#
search_linux_rootfs()
{
	local fstype system ts p dev mp n=0
	local msg="a possible timeshift partition already mounted"

	dbg "Search Linux root partitions..."

	for p in $targets; do
		dev="/dev/$p"
		fstype="$(getfstype "$dev")"
		ts=
		mp=

		if is_linux_fs "$fstype"; then
			dbg "  - probing the device $dev ($fstype)..."
		else
			[ -n "$fstype" ] ||
				fstype="$(blkid -o value -s PTTYPE -- \
							"$dev" 2>/dev/null ||:)"
			dbg "  - skipping the device ${dev}${fstype:+ ($fstype)}..."
			continue
		fi

		[ "$fstype" != btrfs ] ||
			mp="$(device_mpoint "$p" btrfs @)"
		[ -n "$mp" ] && ts=1 ||
			mp="$(device_mpoint "$p" "$fstype")"
		cond_unmount "$probedir"

		if [ -n "$mp" ]; then
			mount --bind -- "$mp" "$probedir" \
				>/dev/null 2>&1 || fstype=
			[ -z "$fstype" ] || [ -z "$ts" ] ||
				dbg "  - $msg: $dev"
		elif [ "$fstype" = btrfs ] &&
			mount -t btrfs -o "ro,$rootopts,subvol=/@" -- \
				"$dev" "$probedir" >/dev/null 2>&1
		then
			dbg "  - a possible timeshift partition found: $dev"
			ts=1
		else
			mount -t "$fstype" -o "ro,$rootopts" -- "$dev" \
				"$probedir" >/dev/null 2>&1 || fstype=
		fi

		[ -n "$fstype" ] || {
			dbg "  - error mounting the partition $dev, skipping"
			continue
		}
		check_rootdir || {
			cond_unmount "$probedir"
			continue
		}
		n=$((1 + $n))
		system="$(get_sysver "$probedir")"
		dbg "    #$n found: $system"
		[ -z "$scanonly" ] || {
			[ -n "$ts" ] && p=timeshift || p="$fstype"
			echo -e "$n. $dev ($p):\t$system"
			cond_unmount "$probedir"
			continue
		}

		if [ -n "$ts" ]; then
			if [ -z "$timeshift" ] ||
			   ! in_array "$dev" $timeshift
			then
				p="$(btrfs subvolume list -t -- "$probedir" |
					sed -n '3,5p' |
					awk '{print $4;}' |
					tr '\n' ' ' |
					cut -f1 -d/)"
				[ "$p" != "@ @home timeshift-btrfs" ] ||
					timeshift="$timeshift $dev"
			fi
		fi

		cat "$probedir/etc/fstab" |sed    \
			-e '\,[[:space:]]*#,d'    \
			-e '\,^[[:space:]]\+$,d'  \
			-e 's,[[:space:]]\+,\t,g' |
		sort -k2,2 -so "$workdir/backup-fstab.$n"
		cond_unmount "$probedir"
	done

	[ -z "$scanonly" ] ||
		exit 0
	[ "$n" != 0 ] ||
		fatal "Корневой раздел Linux не найден!"
	timeshift="${timeshift## }"

	if [ -n "$rootfs_number" ] && [ "$rootfs_number" != 0 ]; then
		[ "$n" -ge "$rootfs_number" ] 2>/dev/null ||
			fatal "Указан неверный порядковый номер корневого раздела!"
	elif [ "$n" = 1 ]; then
		rootfs_number=1
	else
		fatal "Найдено более одного корневого раздела, нужно указать сохраняемый!"
	fi
}

# Пытается определить ID загрузочной записи UEFI
# в подмонтированной корневой файловой системе
#
setup_boot_loader()
{
	local ev x="- boot loader id:"

	# С использованием файла /etc/os-release
	if [ -z "$boot_loader_id" ]; then
		ev="$probedir/etc/os-release"

		if [ -r "$ev" ]; then
			boot_loader_id="$(cat -- "$ev" |
						grep -E ^ID= |
						tail -n1 |
						cut -f2- -d= |
						tr -d \")"
		fi

		if [ -n "$boot_loader_id" ]; then
			dbg "  $x $boot_loader_id (from /etc/os-release)"
		fi
	fi

	# С использованием файла /etc/sysconfig/grub2
	if [ -z "$boot_loader_id" ]; then
		ev="$probedir/etc/sysconfig/grub2"

		if [ -r "$ev" ]; then
			boot_loader_id="$(cat -- "$ev" |
						grep -E ^GRUB_BOOTLOADER_ID= |
						tail -n1 |
						cut -f2- -d= |
						tr -d \")"
		fi

		if [ -n "$boot_loader_id" ]; then
			dbg "  $x $boot_loader_id (from /etc/sysconfig/grub2)"
		fi
	fi
}

# Анализирует ранее сохранённый /etc/fstab,
# соответствующий порядковому номеру rootfs
#
parse_single_fstab()
{
	local ts dev mpoint fstype dummy opts mp

	dbg "Analyzing /etc/fstab..."

	while read dev mpoint fstype opts dummy; do
		case "$fstype" in
		swap)	[ -z "$swap_filename" ] && [ -z "$swappart" ] || {
				dbg "  - one more SWAP will be ignored: $dev"
				continue
			}
			! in_array "$dev" $swap_filelist || {
				dbg "  - the first SWAP-file found: $dev"
				swap_filename="$dev"
				continue
			}
			;;

		vfat)	[ "$mpoint" = /boot/efi ] || {
				dbg "  - foreign FAT partition will be ignored: $dev"
				continue
			}
			;;

		*)	[ -n "$dev" ] && [ -n "$mpoint" ] && [ -n "$fstype" ] ||
				continue
			is_linux_fs "$fstype" || {
				dbg "  - this record will be ignored: $dev ($fstype)"
				continue
			}
			;;
		esac

		dbg "  - checking the device $dev ($fstype)..."

		case "$dev" in
		LABEL=?*)
			dummy="$(blkid -L "${dev##LABEL=}" 2>/dev/null ||:)"
			[ -b "$dummy" ] || {
				dbg "  - unknown LABEL: '$dev', skipping"
				continue
			}
			dev="$dummy"
			;;

		UUID=?*)
			dummy="$(blkid -U "${dev##UUID=}" 2>/dev/null ||:)"
			[ -b "$dummy" ] || {
				dbg "  - unknown UUID: '$dev', skipping"
				continue
			}
			dev="$dummy"
			;;
		esac

		dummy="$(readlink -fv -- "$dev" 2>/dev/null ||:)"
		[ "${dummy:0:5}" = /dev/ ] && [ -b "$dummy" ] || {
			dbg "  - unknown device: $dev, ignoring"
			continue
		}
		dev="$dummy"

		if in_array "$dev" $skip_volumes; then
			dbg "  - the device $dev ($fstype) will be skipped"
			continue
		elif in_array "$mpoint" $skip_volumes; then
			dbg "  - the volume $mpoint ($dev) will be skipped"
			continue
		elif [ "$fstype" = swap ]; then
			dbg "  - the first SWAP partition found: $dev"
			swappart="$dev"
			continue
		else
			opts="$(printf '%s' "$opts" |
				tr ',' '\n' |
				sed -E '/^(ro|rw|noauto)$/d' |
				tr '\n' ',')"
			opts="ro${opts:+,$opts}"
			ts=
		fi

		if [ -z "$timeshift" ] || ! in_array "$dev" $timeshift; then
			dbg "  - probing the device $dev ($mpoint)..."
			mp="$(device_mpoint "$dev" "$fstype")"
		elif [ "$mpoint" = / ]; then
			dbg "  - probing the device $dev[@] (/)..."
			mp="$(device_mpoint "$dev" "$fstype" @)"
			ts=1
		elif [ "$mpoint" = /home ]; then
			dbg "  - probing the device $dev[@home] (/home)..."
			mp="$(device_mpoint "$dev" "$fstype" @home)"
			ts=1
		else
			dbg "  - unexpected mount point on $dev ($mpoint), skipping"
			continue
		fi

		if [ -n "$mp" ]; then
			mount --bind -- "$mp" "$probedir" \
				>/dev/null 2>&1 || fstype=
		else
			mount -t "$fstype" -o "$opts" -- "$dev" \
				"$probedir" >/dev/null 2>&1 || fstype=
		fi

		if [ -z "$fstype" ]; then
			dbg "  - error mounting the partition $dev, skipping"
			continue
		fi

		case "$mpoint" in
		/)	[ "$fstype" != vfat ] && check_rootdir ||
				fatal "Ошибочная запись для раздела / (root)!"
			[ -z "$rootpart" ] ||
				fatal "Повторная запись для раздела / (root)!"
			if [ -n "$ts" ]; then
				dbg "  - / (root) partition found: $dev[@] (timeshift)"
				rootpart="$dev:$opts"
			else
				dbg "  - / (root) partition found: $dev ($fstype)"
				rootpart="$dev"
			fi
			setup_boot_loader
			;;

		/boot)	[ "$fstype" != vfat ] ||
				fatal "Ошибочная запись для раздела /boot!"
			[ "$(uname -m)" = "e2k" ] && [ -f "$probedir/boot.conf" ] ||
			[ -d "$probedir/efi" ] || [ -e "$probedir/vmlinuz" ] ||
			[ -d "$probedir/lilo" ] || [ -d "$probedir/extlinux" ] ||
			[ -d "$probedir/grub" ] ||
				fatal "Ошибочная запись для раздела /boot!"
			[ -z "$bootpart" ] ||
				fatal "Повторная запись для раздела /boot!"
			dbg "  - /boot partition found: $dev ($fstype)"
			bootpart="$dev"
			;;

		/boot/efi)
			[ "$fstype" = vfat ] &&
			[ -d "$probedir/EFI" -o -d "$probedir/efi" ] ||
				fatal "Ошибочная запись для раздела /boot/efi!"
			[ -z "$esp_part" ] ||
				fatal "Повторная запись для раздела /boot/efi!"
			dbg "  - /boot/efi (ESP) partition found: $dev (vfat)"
			esp_part="$dev"
			;;

		/var)	[ "$fstype" != vfat ] && [ -d "$probedir/log" ] &&
			[ -d "$probedir/db" ] && [ -d "$probedir/cache" ] ||
				fatal "Ошибочная запись для раздела /var!"
			[ -z "$var_part" ] ||
				fatal "Повторная запись для раздела /var!"
			dbg "  - /var partition found: $dev ($fstype)"
			var_part="$dev"
			;;

		/home)	[ "$fstype" != vfat ] &&
			[ -n "$(glob "$probedir/*/.bash_profile")" ] ||
				fatal "Ошибочная запись для раздела /home!"
			[ -z "$homepart" ] ||
				fatal "Повторная запись для раздела /home!"
			if [ -n "$ts" ]; then
				dbg "  - /home partition found: $dev[@home] (timeshift)"
				homepart="$dev:$opts"
			else
				dbg "  - /home partition found: $dev ($fstype)"
				homepart="$dev"
			fi
			;;

		/mnt/?*|/srv|/srv/?*|/usr|/usr/?*|/var/?*)
			[ "$fstype" != vfat ] ||
				fatal "Ошибочная запись для раздела $mpoint!"
			in_array "$dev:$fstype:$mpoint" $other_parts &&
				fatal "Повторная запись для раздела $mpoint!" ||:
			dbg "  - $mpoint partition found: $dev ($fstype)"
			other_parts="$other_parts $dev:$fstype:$mpoint"
			;;

		*)	dbg "  - Non-FHS partition will be skipped: $mpoint ($dev)"
			;;
		esac

		cond_unmount "$probedir"
	done < "$workdir/backup-fstab.$rootfs_number"

	[ -n "$rootpart" ] ||
		fatal "Корневой раздел (/) не найден в /etc/fstab!"
	other_parts="${other_parts## }"
}

# Ищет целые диски, на которых находятся сохраняемые разделы
#
search_whole_disks()
{
	local device partdev wholedev number check

	dbg "Search whole disk drives..."; targets=

	for device in ${rootpart%%:*} $esp_part $bootpart \
		$swappart $var_part ${homepart%%:*} $other_parts
	do
		partdev="${device##/dev/}"
		[ -n "$partdev" ] ||
			continue
		[ -b "/dev/$partdev" ] ||
			continue
		[ ! -f "/sys/block/$partdev/dev" ] ||
			continue
		[ -f "/sys/class/block/$partdev/dev" ] ||
			continue
		[ -f "/sys/class/block/$partdev/partition" ] ||
			continue
		read number < "/sys/class/block/$partdev/partition"
		wholedev="${partdev%$number}"
		[ -b "/dev/$wholedev" ] ||
			wholedev="${partdev%p}"
		[ -b "/dev/$wholedev" ] ||
			continue
		wholedev="$(readlink -fv "/dev/$wholedev")"

		case "$wholedev" in
		/dev/*)	wholedev="${wholedev##/dev/}";;
		*)	continue;;
		esac

		in_array "$wholedev" $targets && continue ||:
		[ -f "/sys/block/$wholedev/$partdev/dev" ] ||
			continue
		[ -f "/sys/block/$wholedev/$partdev/partition" ] ||
			continue
		read check < "/sys/block/$wholedev/$partdev/partition"
		[ "$number" = "$check" ] ||
			continue
		read number < "/sys/class/block/$partdev/dev"
		read check < "/sys/block/$wholedev/$partdev/dev"
		[ "$number" = "$check" ] ||
			continue
		dbg "  - /dev/$wholedev found"
		targets="$targets $wholedev"
	done

	if [ -z "$targets" ]; then
		number="$(ls -X /sys/block 2>/dev/null ||:)"

		if [ -n "$number" ]; then
			for device in $number; do
				if [ -n "$skip_disk_names" ]
				then
					check=
					for partdev in $skip_disk_names; do
						case "$device" in
						${partdev}[0-9]*)
							check=1
							break
							;;
						esac
					done

					[ -z "$check" ] ||
						continue
				fi

				wholedev="/dev/$device"
				partdev="/sys/block/$device"
				[ -b "$wholedev" ] ||
					continue
				[ -f "$partdev/dev" ] ||
					continue
				in_array "$device" $targets && continue ||:
				[ -r "$partdev/ro" ] && read check < "$partdev/ro" ||
					check="$(blockdev --getro -- "$wholedev")"
				if [ "$check" != 0 ]; then
					dbg "  - $wholedev is read-only, it will be skipped"
					continue
				fi

				if [ -z "$use_removable" ] && [ -r "$partdev/removable" ]; then
					read check < "$partdev/removable"
					if [ "$check" != 0 ]; then
						dbg "  - $wholedev is removable, it will be skipped"
						continue
					fi
				fi

				check="$(blkid -o value -s PTTYPE -- "$wholedev")"
				if [ "$check" != dos ] && [ "$check" != gpt ]; then
					dbg "  - $wholedev doesn't have MBR/GPT label, it will be skipped"
					continue
				fi

				if is_protected "$device"; then
					dbg "  - $wholedev protected directry"
				else
					dbg "  - $wholedev found"
					targets="$targets $device"
				fi
			done
		fi
	fi

	targets="${targets## }"
	dbg "Whole disks:     ${targets:-<NOT FOUND>}"
}

# Монтирует целевой раздел и определяет объём занятого пространства
#
# $1=device	Монтируемый раздел, например, "/dev/sda2"
# $2=fstype	Файловая система, если известна, например, "ext4"
# $3=mpoint	Относительная точка монтирования, например, "/boot"
# $4=mntopts	Опции монтирования раздела, например, "nodev,nosuid"
#
mount_target()
{
	local device="$1" fstype="$2" subvol=
	local mpoint="$3" options="$access,$4"

	[ -n "$fstype" ] ||
		fstype="$(getfstype "${device%%:*}")"

	if [ -n "$timeshift" ] && in_array "${device%%:*}" $timeshift; then
		[ -z "$mpoint" ] && subvol=@ || subvol=@home
		options="$options,subvol=/$subvol"
		device="${device%%:*}"
	fi

	local x="-xs -B512" i usage archive
	local mp="" target="/mnt/target$mpoint"

	[ -d "$target" ] ||
		fatal "Точка монтирования $target не найдена!"
	mp="$(device_mpoint "$device" "$fstype" "$subvol")"

	if [ -z "$mp" ]; then
		mount -t "$fstype" -o "$options" -- "$device" "$target"
	elif [ -n "$cleanup" ]; then
		fatal "Текущую файловую систему очищать нельзя!"
	elif [ -n "$rename_iface" ]; then
		fatal "В текущей файловой системе переименовывать интерфейс нельзя!"
	else
		mount --bind -- "$mp" "$target"
	fi

	for i in $excludes; do
		x="$x --exclude='./$i'"
	done

	usage=$(du $x "$target" 2>/dev/null |tail -n1 |cut -f1)
	[ -n "$usage" ] && usage=$(($usage * 512)) || usage=0
	archive="$(echo -n "${mpoint:-root}" |sed -e 's,[/_\-],,g' |
					cut -c1-$archive_name_limit)"
	[ "$archive" != bootefi ] ||
		archive=esp
	echo -n "$usage" > "$metadir/$archive.used"
	usage=$(size2mib ${usage:-0})
	dbg "  - ${mpoint:-/ (root)} mounted: $device (${usage} MiB used)"
	volumes="${volumes-} $device:$fstype:${mpoint:-/}"
}

# Чистит /home
#
cleanup_home()
{
	local entry item

	dbg "Cleanup /home..."
	find . -maxdepth 1 -type d -and ! -name '.' |
		cut -c3- > "$workdir/CLEANUP.HOME"

	while read entry; do
		[ "$entry" != 'lost+found' ] ||
			continue
		[ -d "$entry" ] ||
			continue
		[ -f "$entry/.bash_profile" ] ||
		[ -f "$entry/.bash_logout" ] ||
		[ -f "$entry/.bashrc" ] ||
			continue
		dbg "  - User found: '$entry'"
		( set +e
		  set +f
		  cd "$entry"/
		  [ -z "$(glob '.xsession-errors*')" ] ||
			run rm -f -- .xsession-errors*

		  for item in cache dbus local linuxmint config/caja \
				config/gconf config/goa-1.0 config/menus \
				config/mintmenu config/parcellite \
				config/pulse xsession.d apt
		  do
			[ ! -d ".$item" ] ||
				run rm -rf --one-file-system -- ".$item"
		  done

		  for item in config/Trolltech.conf config/monitors.xml \
				ICEauthority bash_history ssh/agent
		  do
			[ ! -e ".$item" ] ||
				run rm -f --one-file-system -- ".$item"
		  done
		)
	done < "$workdir/CLEANUP.HOME"
}

# Чистит /var/log
#
cleanup_logs()
{
	dbg "Cleanup /var/log..."
	( set +e
	  set +f

	  local entry

	  [ -z "$(glob 'journal/???*')" ] ||
		run rm -rf --one-file-system journal/???*
	  run find . -type f -name '*.old' -delete
	  [ -z "$(glob Xorg.0.log alterator-net-iptables rpmpkgs)" ] ||
		run rm -f -- Xorg.0.log alterator-net-iptables rpmpkgs
	  [ ! -d timeshift ] ||
		run find timeshift -type f -name '*.log' -delete
	  find . -type f -and ! -empty |
		cut -c3- |
		grep -v -E -- '^README$' |
	  while read entry; do
		dbg "Wiping '$entry'..."
		[ -n "$dryrun" ] || :> "$entry"
	  done
	)
}

# Чистит /var
#
cleanup_var()
{
	dbg "Cleanup /var..."
	( set +e
	  set +f
	  local entry

	  [ ! -f lib/openvpn/etc/resolv.conf ] ||
		run sed -i '/^nameserver /d' lib/openvpn/etc/resolv.conf
	  [ ! -f resolv/etc/resolv.conf ] ||
		run sed -i '/^nameserver /d' resolv/etc/resolv.conf
	  [ ! -d lib/ldm/.dbus/session-bus ] ||
		run rm -rf --one-file-system lib/ldm/.dbus/session-bus
	  [ -z "$(glob 'cache/fontconfig/*.cache-?*')" ] ||
		run rm -f -- cache/fontconfig/*.cache-?*
	  [ -z "$(glob 'lib/NetworkManager/dhclient-*.lease')" ] ||
		run rm -f -- lib/NetworkManager/dhclient-*.lease
	  [ -z "$(glob 'lib/NetworkManager/dhclient-*.conf')" ] ||
		run rm -f -- lib/NetworkManager/dhclient-*.conf
	  [ -z "$(glob 'lib/dhcpcd/*.lease')" ] ||
		run rm -f -- lib/dhcpcd/*.lease

	  for entry in lib/NetworkManager/timestamps lib/dbus/machine-id \
			run/alteratord/alteratord.log run/avahi-daemon/pid \
			run/alteratord.pid run/cupsd.pid lib/random-seed \
			lib/systemd/random-seed
	  do
		[ ! -f "$entry" ] ||
			run rm -f -- "$entry"
	  done
	)
}

# Чистит /boot
#
cleanup_boot()
{
	dbg "Cleanup /boot..."
	( set +e
	  set +f
	  [ -z "$(glob 'initrd-[2-9]*.img')" ] ||
		run rm -f -- initrd-[2-9]*.img
	  [ ! -f grub/grub.cfg ] ||
		run rm -f -- grub/grub.cfg
	  [ ! -f grub/grubenv ] ||
		run rm -f -- grub/grubenv
	)
}

# Чистит корень
#
cleanup_root()
{
	dbg "Cleanup / (root)..."
	( set +e
	  set +f

	  local m kernel

	  [ ! -f etc/resolv.conf ] ||
		run sed -i '/^nameserver /d' etc/resolv.conf
	  [ -z "$(glob 'etc/openssh/ssh_host_*key*')" ] ||
		run rm -f etc/openssh/ssh_host_*key*
	  [ -z "$(glob 'etc/udev/rules.d/*persistent*.rules')" ] ||
		run rm -f etc/udev/rules.d/*persistent*.rules
	  [ -z "$(glob 'run/blkid/blkid*')" ] ||
		run rm -f run/blkid/blkid*
	  [ -z "$(glob 'tmp/.private/*')" ] ||
		run rm -rf --one-file-system tmp/.private/*
	  [ -z "$(glob 'etc/*.bak')" ] ||
		run rm -f etc/*.bak
	  [ -z "$(glob 'etc/*.old')" ] ||
		run rm -f etc/*.old
	  [ ! -d root/.cache ] ||
		run rm -rf --one-file-system root/.cache
	  [ ! -d root/.loacl ] ||
		run rm -rf --one-file-system root/.local
	  [ ! -d tmp/alterator ] ||
		run rm -rf --one-file-system tmp/alterator
	  [ ! -d tmp/hsperfdata_root ] ||
		run rm -rf --one-file-system tmp/hsperfdata_root
	  [ ! -f etc/resolv.conf.dnsmasq ] ||
		run rm -f etc/resolv.conf.dnsmasq
	  [ ! -f etc/firsttime.flag ] ||
		run rm -f etc/firsttime.flag
	  [ ! -f etc/machine-id ] ||
		run rm -f etc/machine-id
	  [ ! -f root/.bash_history ] ||
		run rm -f root/.bash_history
	  [ ! -f run/messagebus.pid ] ||
		run rm -f run/messagebus.pid

	  for kernel in lib/modules/*; do
		[ "$kernel" != 'lib/modules/*' ] ||
			break
		m="$kernel/kernel/drivers/block"
		[ ! -d "$m/drbd" ] && [ ! -d "$m/zram" ] ||
			continue
		run rm -rf --one-file-system -- "$kernel"
	  done
	)
}

# Переименовывает единственный сетевой интерфейс в eth0
#
rename_network_iface()
{
	local src="" i="" ifaces="" n=0

	ifaces="$(ls -X etc/net/ifaces/ 2>/dev/null ||:)"

	if [ -z "$ifaces" ]; then
		dbg "WARNING: No network interfaces found for renaming"
		return 0
	fi

	for i in $ifaces; do
		case "$i" in
		default|lo|unknown)
			continue
			;;
		wlan[0-9]*)
			continue
			;;
		*)	[ -d "etc/net/ifaces/$i" ] ||
				continue
			n=$((1 + $n))
			src="$i"
			;;
		esac
	done

	if [ "$n" = 1 ] && [ -n "$src" ] && [ "$src" != eth0 ]; then
		dbg "Renaming network interface '$src' => 'eth0'..."
		run mv -f -- "etc/net/ifaces/$src" "etc/net/ifaces/eth0"
	elif [ "$n" -gt 1 ]; then
		dbg "WARNING: $n network interfaces found, skip renaming"
	else
		dbg "WARNING: No network interfaces found for renaming"
	fi
}

# Убеждается, что переданный после опции $1 параметр $2 не пуст.
# Иначе выводит сообщение $3, подсказку и завершает работу.
#
not_empty()
{
	[ -n "$2" ] || {
		echo "После '$1' следует указывать $3."
		show_help 1
	} >&2
}

# Убеждается, что целевой диск ещё не был определён.
# Иначе выводит сообщение, подсказку и завершает работу.
#
tgt_empty()
{
	[ -z "$targets" ] || {
		echo "Целевой диск можно определить только один раз!"
		show_help 1
	} >&2
}

# Разбор аргументов командной строки
#
parse_args()
{
	local s_opts="+b:,n:,s:,A:,t:,l:,f:,U:,L:,X:,C,S,N,r,R,p,c,v,q,h"
	local   opts="base:,name:,skip:,tar:,loader:,fstab:,rootfs:"
		opts="$opts,uuid:,label:,current,scan-only,scanonly"
		opts="$opts,no-act,dry-run,dryrun,removable,no-nvram"
		opts="$opts,nonvram,clean,cleanup,verbose,quiet,algo:"
		opts="$opts,checksum:,checksums:,self,rename-iface"
		opts="$opts,exclude:,help"
	local dummy

	run_args="$*"
	opts=$(getopt -n "$progname" -o $s_opts -l $opts -- "$@") ||
		show_help 1 >&2
	eval set -- "$opts"

	while [ "$#" -gt 0 ]; do
		case "$1" in
		-b|--base)
			not_empty "$1" "${2-}" "путь к хранилищу бэкапов"
			backup_base="$2"
			shift
			;;

		-n|--name)
			not_empty "$1" "${2-}" "название создаваемого бэкапа"
			backup_name="$2"
			shift
			;;

		-s|--skip)
			not_empty "$1" "${2-}" "пропускаемые разделы"
			skip_volumes="$skip_volumes $(echo -n "$2" |tr ',' ' ')"
			shift
			;;

		-A|--algo|--checksum|--checksums)
			not_empty "$1" "${2-}" "алгоритмы контрольных сумм"
			opts="$(echo -n "$2" |tr ',' ' ' |tr '[[:upper:]]' '[[:lower:]]')"
			digests=
			#
			for dummy in $opts; do
				case "$dummy" in
				md5|sha1|sha256)
					digests="$digests $dummy"
					;;
				none)	digests=""; break
					;;
				*)	fatal "Неподдерживаемый алгоритм: '$dummy'!"
					;;
				esac
			done
			#
			digests="${digests## }"
			shift
			;;

		-t|--tar)
			not_empty "$1" "${2-}" "дополнительные опции tar"
			#
			for dummy in $(echo -n "$2" |tr ',' ' '); do
				tar_opts="$tar_opts --$dummy"
			done
			#
			shift
			;;

		-l|--loader)
			not_empty "$1" "${2-}" "ID записи UEFI-загрузки"
			boot_loader_id="$2"
			shift
			;;

		-f|--fstab|--rootfs)
			not_empty "$1" "${2-}" "порядковый номер RootFS"
			rootfs_number="$2"
			shift
			;;

		-U|--uuid)
			not_empty "$1" "${2-}" "UUID корневого раздела"
			tgt_empty
			tgtway="uuid"
			targets="$2"
			shift
			;;

		-L|--label)
			not_empty "$1" "${2-}" "LABEL корневого раздела"
			tgt_empty
			tgtway="label"
			targets="$2"
			shift
			;;

		-X|--exclude)
			not_empty "$1" "${2-}" "шаблон исключаемых файлов"
			excludes="$excludes $2"
			shift
			;;

		-C|--current|--self)
			tgt_empty
			targets="/"
			tgtway="root"
			;;

		-S|--scan-only|--scanonly)
			scanonly=1
			;;

		-N|--no-act|--dry-run|--dryrun)
			dryrun=1
			;;

		-r|--removable)
			use_removable=1
			;;

		-R|--rename-iface)
			rename_iface=1
			;;

		-p|--no-nvram|--nonvram)
			no_nvram=1
			;;

		-c|--clean|--cleanup)
			cleanup=1
			;;

		-v|--verbose)
			verbose=1
			;;

		-q|--quiet)
			quiet=1
			;;

		-h|--help)
			show_help
			;;

		--)	shift
			[ -z "${1-}" ] || tgt_empty
			break
			;;

		-*)	echo "Неподдерживаемая опция: '$1'" >&2
			show_help 1 >&2
			;;

		*)	tgt_empty
			break
			;;
		esac
		shift
	done

	[ "$#" -le 1 ] ||
		fatal "Слишком много аргументов, да поможет вам --help!"
	[ -n "$targets" ] || targets="${1-}"
}


# Начало
parse_args "$@"
dbg "Checking backup conditions..."

# Проверяем привелегии
[ "$EUID" = 0 ] ||
	fatal "Вы должны быть root для запуска этой программы!"

# Создаём рабочий каталог
export TMPDIR="${TMPDIR-/tmp}"
workdir="$(mktemp -dt "$progname-XXXXXXXX.tmp")"
[ "$(stat -c %d /etc)" != "$(stat -c %d -- "$workdir")" ] ||
	mount -t tmpfs -o nodev,noexec,nosuid none "$workdir"
chmod 755 "$workdir"

# Устанавливаем обработчик выхода из скрипта
trap exit_handler HUP PIPE INT QUIT TERM EXIT

# Создаём каталог метаданных и временную точку монтирования
metadir="$workdir/metadata"
probedir="$workdir/probe"
mkdir -m755 -- "$metadir"
mkdir -m755 -- "$probedir"

# Показываем кэш blkid
if [ -n "$verbose" ]; then
	dbg; dbg "BLKID cache:"
	blkid >&2
	dbg
fi

# Создаём карту всех блочных устройств
show_devmap > "$workdir"/devices.map
if [ -n "$verbose" ]; then
	dbg "Devices MAP:"
	cat "$workdir"/devices.map >&2
	dbg
fi

# Проверяем указанное целевое устройство либо пытаемся его обнаружить
case "$tgtway" in
root)	targets="$(awk '{print $1 " " $2;}' /proc/mounts 2>/dev/null |
			grep -s -E -- ' /$' |
			tail -n1 |
			cut -f1 -d ' ')" ||
		fatal "Вероятно, файловая система /proc не смонтирована!"
	;;

label)	targets="$(blkid -L "$targets" 2>/dev/null)" ||
		fatal "Указана неверная метка тома либо такого тома не существует!"
	;;

uuid)	targets="$(blkid -U "$targets" 2>/dev/null)" ||
		fatal "Указан неверный UUID либо такого раздела не существует!"
	;;
esac
#
if [ -n "$targets" ]; then
	check_target_device
	rebuild_skipped_volumes
else
	skip_volumes="$skip_volumes LABEL=alt-autorun"
	autodetect_protected
	autodetect_targets
	tgtway="auto"
fi

# Загружаем модули ядра
dbg "Loading kernel modules..."
for module in ext4 jfs xfs reiserfs btrfs zfs vfat; do
	grep -qws -- "$module" /proc/modules ||
		modprobe $module >/dev/null 2>&1 &&
			dbg "  - $module" ||:
done
dbg
unset module

# Промежуточная диагностика
if [ -n "$verbose" ]; then
	dbg "Current time:    $(LC_TIME=C date "+%F %T")"
	dbg "Exec arguments:  $run_args"
	dbg "Work directory:  $workdir"
	dbg "Backup base:     $backup_base"
	dbg "Backup name:     $backup_name"
	dbg "Kernel version:  $(uname -rom)"
	dbg "System version:  $(get_sysver)"
	dbg "Script run mode: $(test -z "$dryrun" && echo NORMAL || echo SIMULATION)"
	dbg "Removable disks: $(test -z "$use_removable" && echo DISABLED || echo ENABLED)"
	dbg "Write to NVRAM:  $(test -n "$no_nvram" && echo DISABLED || echo ENABLED)"
	dbg "Cleanup first:   $(test -z "$cleanup" && echo DISABLED || echo ENABLED)"
	dbg "RootFS number:   ${rootfs_number:-<NOT DEFINED>}"
	dbg "Checksum types:  ${digests:-<EMPTY LIST>}"
	dbg "Target devices:  $targets"
	dbg "TGT detection:   $tgtway"
	dbg
fi

# Сканируем разделы: ищем все потенциальные rootfs
search_linux_rootfs
dbg

# Анализируем ранее сохранённый файл /etc/fstab
parse_single_fstab
dbg

# Определяем диски, на которых находятся сохраняемые разделы
search_whole_disks
dbg

# Проверяем разделы на отсутствие противоречий
[ -n "$rootpart" ] && must_be_linux "${rootpart%%:*}" / ||
	fatal "Корневой раздел (/) должен быть определён!"
[ -z "$bootpart" ] ||
	must_be_linux "$bootpart" /boot
[ -z "$var_part" ] ||
	must_be_linux "$var_part" /var
[ -z "$homepart" ] ||
	must_be_linux "${homepart%%:*}" /home
#
if [ -n "$esp_part" ]; then
	expect_fs "$esp_part" vfat ||
		fatal "Ошибочный раздел ESP: $esp_part!"
fi
#
if [ -n "$swappart" ]; then
	expect_fs "$swappart" swap ||
		fatal "Ошибочный раздел SWAP: $swappart!"
fi
#
if [ -n "$other_parts" ]; then
	for record in $other_parts; do
		device="$(echo -n "$record" |cut -f1 -d:)"
		mpoint="$(echo -n "$record" |cut -f3- -d:)"
		must_be_linux "$device" "$mpoint"
	done
	#
	unset record device mpoint
fi

# Бэкап должен быть доступен только пользователю root
umask 0077

# Размонтируем целевые разделы
unmount_all_targets

# Снова монтируем целевые разделы
dbg "Mounting target filesystems..."
[ -d /mnt/target ] ||
	mkdir -m 0755 /mnt/target
#
if [ -z "$cleanup" ] && [ -z "$rename_iface" ]; then
	access=ro
else
	access=rw
fi
#
mount_target "$rootpart" "" "" "$rootopts"
[ -z "$bootpart" ] ||
	mount_target "$bootpart" "" /boot "$bootopts"
[ -z "$esp_part" ] ||
	mount_target "$esp_part" vfat /boot/efi "$vfatopts"
[ -z "$var_part" ] ||
	mount_target "$var_part" "" /var "$dataopts"
[ -z "$homepart" ] ||
	mount_target "$homepart" "" /home "$dataopts"
#
if [ -n "$other_parts" ]; then
	for record in $other_parts; do
		device="$(echo -n "$record" |cut -f1 -d:)"
		fstype="$(echo -n "$record" |cut -f2 -d:)"
		mpoint="$(echo -n "$record" |cut -f3- -d:)"
		#
		case "$mpoint" in
		/usr|/usr/?*)
			options="$rootopts"
			;;
		*)	options="$dataopts"
			;;
		esac
		#
		mount_target "$device" "$fstype" "$mpoint" "$options"
	done
	#
	unset record device fstype mpoint options
fi
#
dbg
unset access
volumes="${volumes## }"

# Проверяем наличие SWAP-файла
if [ -n "$swap_filename" ]; then
	if [ ! -f /mnt/target"$swap_filename" ]; then
		dbg "SWAP-file not found: $swap_filename"
		swap_filename=
	else
		[ "$(getfstype /mnt/target"$swap_filename")" = swap ] ||
			dbg "Invalid SWAP-file: $swap_filename"
		swap_filesize="$(du -sb /mnt/target"$swap_filename" |cut -f1)"
	fi
fi

# Хранилище бэкапов к этому моменту должно быть доступно
[ -d "$backup_base" ] ||
	fatal "Хранилище бэкапов недоступно: $backup_base!"

# А в случае Rescue Launcher, оно должно быть смонтировано
if [ "$backup_base" = /mnt/autorun ]; then
	if grep -qs " /mnt/autorun ext2 ro," /proc/mounts; then
		dbg "Remounting /mnt/autorun for R/W-access..."
		mount -o remount,rw /mnt/autorun
	elif ! grep -qs " /mnt/autorun ext2 rw," /proc/mounts; then
		dbg "Mounting /mnt/autorun (alt-autorun) media..."
		mount -t ext2 -o rw,nodev,nosuid -L alt-autorun /mnt/autorun
		need_unmount=1
	fi
fi

# Исключаем точку монирования самого хранилища бэкапов
in_array "${backup_base:1}" $excludes ||
	excludes="$excludes ${backup_base:1}"

# Каталог очередного бэкапа будет создан в процессе
backupdir="$(realpath "$backup_base")/$backup_name"
[ ! -d "$backupdir" ] ||
	fatal "Резервная копия '$backup_name' уже существует!"
dbg "Backup FullPath: $backupdir"

# Создаём всю структуру подкаталогов для очередного бэкапа
run mkdir -p -m 0700 -- "$backupdir"
:> "$metadir/LOADERS"
dbg

# Сохраняем архитектуру целевого хоста
echo -n "$(uname -m)" > "$metadir"/ARCH
dummy=

# Сохраняем названия загрузчиков
[ "$(uname -m)" != "e2k" ] || [ ! -f /mnt/target/boot/boot.conf ] ||
	echo "elbrus" >> "$metadir/LOADERS"
[ ! -d /mnt/target/boot/grub ] ||
	echo "grub" >> "$metadir/LOADERS"
[ ! -d /mnt/target/boot/lilo ] ||
	echo "lilo" >> "$metadir/LOADERS"
[ ! -d /mnt/target/boot/extlinux ] ||
	echo "extlinux" >> "$metadir/LOADERS"

# Сохраняем путь к файлу random-seed
if [ -f /mnt/target/var/lib/systemd/random-seed ]; then
	echo -n "/var/lib/systemd/random-seed" > "$metadir/RNDSEED"
elif [ -f /mnt/target/var/lib/random-seed ]; then
	echo -n "/var/lib/random-seed" > "$metadir/RNDSEED"
fi

# Определяем имя целевого хоста
net="/mnt/target/etc/sysconfig/network"
if [ -r "$net" ]; then
	grep -qs -E ^HOSTNAME= "$net" &&
		dummy="$(grep -s -E ^HOSTNAME= "$net" |
				cut -f2- -d= |
				tr -d \' |
				tr -d \")" ||:
elif [ -r /mnt/target/etc/HOSTNAME ]; then
	read dummy < /mnt/target/etc/HOSTNAME
elif [ -r /mnt/target/etc/hostname ]; then
	read dummy < /mnt/target/etc/hostname
fi

# Сохраняем имя хоста, /etc/fstab и версию выпуска ОС
[ -z "$dummy" ] || echo -n "$dummy" > "$metadir"/ORGHOST
cat /mnt/target/etc/fstab > "$metadir"/FSTABLE
get_sysver /mnt/target > "$metadir"/RELEASE
unset dummy net

# Формируем базовый список исключений для бэкапа корневого раздела
(cat <<-EOF
./proc/*
./run/*
./sys/*
./tmp/*
./root/tmp/*
./var/tmp/*
EOF
for dummy in $swap_filelist; do
	echo ".$dummy"
done
) >"$workdir/EXCLUDES"

# Сохраняем вложенные разделы
if [ -n "$other_parts" ]; then
	for record in $(echo -n "$other_parts" |tr ' ' '\n' |tac |tr '\n' ' '); do
		device="$(echo -n "$record" |cut -f1 -d:)"
		mpoint="$(echo -n "$record" |cut -f3- -d:)"
		cd "/mnt/target$mpoint/"
		[ "$mpoint" != /var/log ] || [ -z "$cleanup" ] ||
			cleanup_logs
		archive="$(echo -n "$mpoint" |sed -e 's,[/_\-],,g' |
					cut -c1-$archive_name_limit)"
		[ ! -f "$backupdir/$archive.tgz" ] ||
			fatal "Архив с таким именем уже создан: $archive.tgz!"
		backup_linux "$archive" "$mpoint" .
		echo ".$mpoint/*" >> "$workdir/EXCLUDES"
		cd / && cond_unmount "/mnt/target$mpoint"
		run save_fsmeta "$device" "$archive"
		dbg
	done
	unset record device mpoint archive
fi

# Чистим и сохраняем /var
if [ -n "$var_part" ]; then
	cd /mnt/target/var/
	if [ -n "$cleanup" ]; then
		echo -n "$other_parts" |tr ' ' '\n' |
		grep -qs -E -- ':/var/log$' || {
			cd log/ && cleanup_logs
			cd /mnt/target/var/
		}
		cleanup_var
	fi
	#
	backup_linux var /var .
	echo "./var/*" >> "$workdir/EXCLUDES"
	cd / && cond_unmount /mnt/target/var
	run save_fsmeta "$var_part" var
	dbg
fi

# Чистим и сохраняем /home
if [ -n "$homepart" ]; then
	cd /mnt/target/home/
	[ -z "$cleanup" ] ||
		cleanup_home
	backup_linux home /home .
	echo "./home/*" >> "$workdir/EXCLUDES"
	#
	if [ -n "$timeshift" ] &&
	   in_array "${homepart%%:*}" $timeshift
	then
		O="${homepart#*:}"
		printf '%s\n' "${O:3}" > "$metadir"/home.opts
		run btrfs subvolume show . > "$metadir"/home.sub
		unset O
	fi
	#
	cd / && cond_unmount /mnt/target/home
	run save_fsmeta "${homepart%%:*}" home
	dbg
fi

# Сохраняем /boot/efi
if [ -n "$esp_part" ]; then
	cd /mnt/target/boot/efi/
	backup_linux esp /boot/efi .
	[ -n "$bootpart" ] ||
		echo "./boot/efi/*" >> "$workdir/EXCLUDES"
	cd / && cond_unmount /mnt/target/boot/efi
	run save_fsmeta "$esp_part" esp
	dbg
fi

# Чистим и сохраняем /boot
if [ -n "$bootpart" ]; then
	cd /mnt/target/boot/
	[ -z "$cleanup" ] ||
		cleanup_boot
	backup_linux boot /boot "--exclude='./efi/*'" .
	echo "./boot/*" >> "$workdir/EXCLUDES"
	cd / && cond_unmount /mnt/target/boot
	run save_fsmeta "$bootpart" boot
	dbg
fi

# Чистим корень
cd /mnt/target/
if [ -n "$cleanup" ]; then
	echo -n "$other_parts" |tr ' ' '\n' |
	grep -qs -E -- ':/var/log$' || {
		cd var/log/ && cleanup_logs
		cd /mnt/target/
	}
	[ -n "$var_part" ] || {
		cd var/ && cleanup_var
		cd /mnt/target/
	}
	[ -n "$homepart" ] || {
		cd home/ && cleanup_home
		cd /mnt/target/
	}
	[ -n "$bootpart" ] || {
		cd boot/ && cleanup_boot
		cd /mnt/target/
	}
	cleanup_root
fi

# Если определено, переименовываем интерфейс
[ -z "$rename_iface" ] || rename_network_iface
[ -z "$cleanup" ] && [ -z "$rename_iface" ] || dbg

# Отладка исключений
[ -z "$verbose" ] || {
	echo "Root partition backup excludes:"
	cat "$workdir/EXCLUDES"
	echo
} >&2

# Сохраняем корень
backup_linux root "/ (корень)" -X "$workdir/EXCLUDES" .
#
if [ -n "$timeshift" ] &&
   in_array "${rootpart%%:*}" $timeshift
then
	O="${rootpart#*:}"
	printf '%s\n' "${O:3}" > "$metadir"/root.opts
	run btrfs subvolume show . > "$metadir"/root.sub
	( echo "[device]"
	  echo "${rootpart%%:*}"
	  echo
	  #
	  echo "[min-dev-size]"
	  run btrfs inspect-internal min-dev-size .
	  echo
	  #
	  echo "[subvol]"
	  run btrfs subvolume list -ut . |head -n5
	  echo
	  #
	  echo "[usage]"
	  run btrfs device usage --raw .
	  #
	  echo "[dump-super]"
	  run btrfs inspect-internal dump-super -Fa -- "${rootpart%%:*}"
	) > "$metadir"/TMSHIFT
	unset O
fi
#
cd / && cond_unmount /mnt/target
run save_fsmeta "${rootpart%%:*}" root
dbg

# Сохраняем информацию о разделе или файле SWAP
if [ -n "$swappart" ]; then
	run save_fsmeta "$swappart" swap
	dbg
elif [ -n "$swap_filesize" ]; then
	echo "$swap_filesize	$swap_filename" > "$metadir"/SWPFILE
	[ -z "$verbose" ] || {
		echo "SWAP-file record created:"
		cat -- "$metadir"/SWPFILE
		echo
	} >&2
fi

# Считаем контрольные суммы образов
[ -n "$dryrun" ] ||
	cd "$backupdir"/
if [ -n "$digests" ]; then
	pv="$(command -v pv 2>/dev/null ||:)"
	#
	for algo in $digests; do
		command -v "${algo}sum" >/dev/null 2>&1 ||
			continue
		dummy="$(echo "$algo" |tr '[[:lower:]]' '[[:upper:]]')"
		msg "Вычисляются контрольные суммы $dummy..."
		#
		case "$algo" in
		sha1)	dummy="SHA";;
		sha256)	dummy="256";;
		esac
		#
		dbg "Executing: ${algo}sum *.tgz > checksum.$dummy"
		#
		[ -z "$dryrun" ] ||
			continue
		#
		if [ -n "$verbose" ] || [ -n "$quiet" ] || [ ! -x "$pv" ]; then
			"${algo}sum" $(glob '*.tgz') > "$metadir/checksum.$dummy"
		else
			:> "$metadir/checksum.$dummy"
			#
			for filename in $(glob '*.tgz'); do
				"$pv" -N "${filename%.tgz}" "$filename" |
					"${algo}sum" |
					awk '{print $1 "  '"$filename"'";}' \
						>> "$metadir/checksum.$dummy"
			done
			#
			unset filename
		fi
	done
	#
	unset pv algo dummy
fi

# Определяем утилиту для работы с NVRAM и сохраняем запись UEFI-загрузки
if grep -qs -- ' /sys/firmware/efi/efivars efivarfs ' /proc/mounts; then
	mngr="$(command -v efibootmgr 2>/dev/null ||:)"
	[ -z "$no_nvram" ] && [ -x "$mngr" ] || mngr=
else
	mngr=
fi
#
if [ -n "$mngr" ]; then
	[ -n "$boot_loader_id" ] ||
		boot_loader_id="$default_loader_id"
	mngr="$("$mngr" -v 2>&1 |
		grep -s -E -- '^Boot[0-9]*' |
		grep -ws -- "$boot_loader_id" |
		head -n1)"
	#
	if [ -n "$mngr" ]; then
		dbg "UEFI-record found: $mngr"
		[ -n "$dryrun" ] || {
			echo -n "$boot_loader_id="
			echo "$mngr" |awk '{print $3;}'
		} > "$metadir/EFIBOOT"
	fi
fi
#
unset mngr

# Сохраняем информацию о текущей схеме
# разметки диска в случае её доступности
#
if [ -n "$targets" ]; then
	echo -n "$targets" > "$metadir/TARGETS"
	#
	for devname in $targets; do
		sfdisk -d "/dev/$devname" > "$metadir/$devname.sfdisk"
		blkid -- $(glob "/dev/$devname*")
	done > "$metadir/blkid.tab"
	#
	unset devname
fi

# Завершающая стадия
dbg "Finalizing backup..."
echo -n "$volumes" |
	tr ' ' '\n' > "$metadir/VOLUMES"
[ -z "$no_nvram" ] ||
	echo 1 > "$metadir/NONVRAM"
echo -n "tgz" > "$metadir/ZIPTYPE"
echo -n "0.2" > "$metadir/VERSION"
#
if [ -n "$dryrun" ]; then
	[ -n "$quiet" ] ||
		ls -lX1 -- "$metadir/"
else
	cd "$metadir"/
	run tar -cpzf "$backupdir/META.tgz" \
		--numeric-owner $(glob '*')
	cd "$backupdir"/
	run sync
	#
	if [ -z "$quiet" ]; then
		echo; ls -lX1; echo
		tar -tzf META.tgz; echo; echo -n "Всего: "
		du -sh --apparent-size . |tail -n1 |awk '{print $1}'
	fi
fi
cd /
msg
msg "Резервная копия '$backup_name' успешно создана!"
exit 0

