Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 913 lines (811 sloc) 36.1 KB
#!/bin/bash
[ "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/ 2>/dev/null)" ] && exit 0
# btrfs-auto-snapshot for Linux.
# Automatically create, rotate, and destroy periodic BTRFS snapshots.
# Copyright 2013 Matus Kral <matuskral@icloud.com>
#
# I'm trying to keep structure and reuse of zfs-auto-snapshot (zevo) version
# at https://github.com/mk01/zfs-auto-snapshot.git which is based on
# original version developed by Darik Horn <dajhorn@vanadac.com>
#
# 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, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
tmp_dir='/run/btrfs-snap'
opt_clonename='xbian-copy'
opt_syslog=''
opt_dev='/dev/root'
opt_dry_run=''
opt_quiet=''
opt_verbose=''
opt_keep=''
opt_label='regular'
opt_prefix='btrfs-auto-snap'
opt_recursive=''
opt_sep='_'
opt_rootvolid='5'
opt_size=0
opt_createdirs=''
opt_mountpoint=${tmp_dir}
#opt_snapdir='.btrfs/snapshot/'
opt_snapdir=''
opt_img=''
opt_helper=''
opt_cpmode='btrfs'
#opt_remotecmd='rsh -l root media.private '
opt_remotecmd=''
# what is name for snapshot which is normaly mounted as running filesystem (if SUBVOL/@running, then use "@running")
opt_runningcopyin='@'
opt_delim='@'
opt_command='snapshot'
opt_vroot=''
opt_nocreate=''
SNAPSHOT_COUNT='0'
DESTROY_COUNT='0'
WARNING_COUNT='0'
MY_FILE_MAX=80000
MY_MIN_FREE_KBYTES=75000
. /usr/local/include/xbian-config/modules/xbiancopy/functions
print_usage ()
{
echo "Usage: $0 command [options] <'//' | name [name...]>
command Is one of
list, snapshot
rename, destroy
listvol, rollback
createvol
xbiancopy [--no-create] [--tar] [[--img]] [[[--size]]] [[[[--fs-name]]]] source dest
backuphome [dest]
If not given, snapshot is assumed.
--dev Device which is part of the btrfs filesystem (by default
is the one mounted as rootfs).
-d, --debug Print debugging messages.
-n, --dry-run Print actions without actually doing anything.
-h, --help Print this usage message.
-k, --keep=NUM Keep NUM recent snapshots and destroy older snapshots.
-l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly' (default
is 'regular').
-p, --prefix=PRE PRE is 'btrfs-auto-snap' by default.
-e, --name Snapshot name. If specified, -l and -p are overridden
and --keep is not considered.
-q, --quiet Suppress warnings and notices at the console.
-x, --exclude Exclude those subvolumes from operations (useful with '//')
(comma separated list)
-s, --syslog Write messages into the system log.
-v, --verbose Print info messages.
-i, --rootvolid ID of root volume. All actions are handled in this
volume tree (default is 0)
-m, --mountpoint Dir used to mount rootvolid. By default ${tmp_dir}
-t,--dirs Create snapshot directories if needed.
--no-create Doesn't auto create fs on 'xbiancopy'.
--tar Use tar instead of btrfs send/receive on 'xbiancopy'.
--img Create img file instead if physical partition.
--size Target size of destination img file accepting 'human-
readable' sizes (1100M, 2G, ...).
--fs-name Clone filesystem name (xbian-copy by default).
name Filesystem and volume names, or '//' for all subvolumes.
"
}
print_log () # level, message, ...
{
LEVEL=$1
shift 1
case $LEVEL in
(eme*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.emerge "$*"
echo Emergency: "$*" 1>&2
;;
(ale*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.alert "$*"
echo Alert: "$*" 1>&2
;;
(cri*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.crit "$*"
echo Critical: "$*" 1>&2
;;
(err*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.err "$*"
echo Error: "$*" 1>&2
;;
(war*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning "$*"
test -z "$opt_quiet" && echo Warning: "$*" 1>&2
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
;;
(not*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice "$*"
test -z "$opt_quiet" && echo "$*"
;;
(inf*)
# test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.info "$*"
test -n "$opt_verbose" && echo "$*"
;;
(deb*)
# test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.debug "$*"
test -n "$opt_debug" && echo Debug: "$*"
;;
(*)
test -n "$opt_syslog" && logger -t "$opt_prefix" "$*"
echo "$*" 1>&2
;;
esac
}
do_run () # [argv]
{
if [ -n "$opt_dry_run" ]
then
echo "... Running $*"
RC="$?"
else
eval $*
RC="$?"
if [ "$RC" -eq '0' ]
then
print_log debug "$*"
else
print_log warning "$* returned $RC"
fi
fi
return "$RC"
}
do_destroy ()
{
dSNAP="$1"
dPATH=''
dNAME=''
dSNAP=$(eval echo "$1 | awk '{sub(/$opt_delim/,\"$opt_snapdir$opt_delim\")}; 1'")
print_log debug "Destroying $opt_mountpoint/$dSNAP"
if do_run "btrfs sub delete $opt_mountpoint/$dSNAP"; then
DESTROY_COUNT=$(( $DESTROY_COUNT + 1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
fi
do_run "btrfs fi sync $opt_mountpoint/" >/dev/null
}
do_snapshot ()
{
for ii in $TARGETS
do
print_log debug "Snapshoting $ii."
if [ ! -d $opt_mountpoint/$ii/$opt_snapdir -a -n "$opt_createdirs" ]; then
print_log debug "Creating snapshot directory for $ii."
mkdir -p $opt_mountpoint/$ii/$opt_snapdir
fi
if [ ! -d "$opt_mountpoint/$ii/$opt_snapdir$SNAPNAME" ]; then
tmp_runningcopyin=$opt_runningcopyin
z=$(findmnt -n | grep /$ii/ -m1 | awk '{print $2}'); z=${z##*\/}; z=${z%%]}
[ -n "$z" ] && tmp_runningcopyin=$z
if do_run "btrfs sub snapshot $opt_mountpoint/$ii/$tmp_runningcopyin $opt_mountpoint/$ii/$opt_snapdir$SNAPNAME"; then
SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 ))
[ -e $opt_mountpoint/$ii/@last_good_known ] && do_run "btrfs sub delete $opt_mountpoint/$ii/@last_good_known"
do_run "btrfs sub snapshot $opt_mountpoint/$ii/$opt_snapdir$SNAPNAME $opt_mountpoint/$ii/@last_good_known"
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
fi
else
print_log warning "Snapshot or volume with this name already exists, skipping $ii/$opt_snapdir$SNAPNAME"
fi
KEEP="$opt_keep"
test -z "$KEEP" && continue
if [ "$ii" = "." ]; then
ii=""
else
ii="$ii/"
fi
for jj in $SNAPSHOTS_OLD
do
if [ -z "${jj#$ii$SNAPGLOB}" ]; then
KEEP=$(( $KEEP - 1 ))
test "$KEEP" -le '0' && do_destroy "$jj"
fi
done
done
}
do_remove_kpartx ()
{
[ -e /run/vars.xbiancopy.$$ ] && { . /run/vars.xbiancopy.$$; rm -f /run/vars.xbiancopy.$$; }
[ -n "$loopd" ] && { while ! kpartx -d $loopd &>/dev/null; do sleep 5; done; losetup -d $loopd &>/dev/null; }
[ -n "$file_max" -a "$(sysctl -n fs.file-max)" -eq "$MY_FILE_MAX" ] && sysctl -w fs.file-max=$file_max &>/dev/null
[ -n "$min_free_kbytes" -a "$(sysctl -n vm.min_free_kbytes)" -eq "$MY_MIN_FREE_KBYTES" ] && sysctl -w vm.min_free_kbytes=$min_free_kbytes &>/dev/null
}
do_rename ()
{
dSNAP="$opt_mountpoint/$2"
sSNAP="$opt_mountpoint/$1"
dSNAP=$(eval echo "$dSNAP | awk '{sub(/$opt_delim/,\"$opt_snapdir$opt_delim\")}; 1'")
sSNAP=$(eval echo "$sSNAP | awk '{sub(/$opt_delim/,\"$opt_snapdir$opt_delim\")}; 1'")
sPATH=$(basename $sSNAP)
sPATH=$(echo ${sSNAP%%$sPATH})
dPATH=$(basename $dSNAP)
dPATH=$(echo ${dSNAP%%$dPATH})
print_log debug "Renaming from $sPATH to $dPATH"
if [ "$sPATH" != "$dPATH" ]; then
print_log error "Can't rename snapshot across volumes"
exit 300
elif [ -d "$dSNAP" ]; then
print_log error "Destination name already exists!"
exit 301
elif [ ! -d "$sSNAP" ]; then
print_log error "Source name does not exists!"
exit 302
else
if do_run "mv $sSNAP $dSNAP"; then
print_log debug "Rename ok"
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
fi
fi
}
if [ "$#" -eq '0' ]; then
print_log error "Argument list empty"
exit 104
fi
[ -e /etc/default/xbian-internals ] && . /etc/default/xbian-internals
[ -z "$OPT_COPY_MODE" ] || opt_cpmode=$OPT_COPY_MODE
stopt=${1%%-*}
if [ "$1" != '//' -a "$stopt" != '' ]; then
opt_command="$1"
shift 1
else
opt_command='snapshot'
fi
GETOPT=$(getopt \
--longoptions=label:,prefix:,verbose,debug,rootvolid:,mountdir:,dev:,dry-run,dirs,syslog,keep:,name:,no-create,img,helper,tar,size:,fs-name:,exclude: \
--options=p:dvl:i:m:nhtsk:e:x: \
-- "$@" ) \
|| exit 128
eval set -- ${GETOPT}
while [ "$#" -gt '0' ]
do
case "$1" in
(--img)
opt_img=1
shift 1
;;
(--helper)
opt_helper=1
shift 1
;;
(--exclude|-x)
opt_exclude=$2
shift 2
;;
(--no-create)
opt_nocreate='1'
shift 1
;;
(--tar)
opt_cpmode='tar'
shift 1
;;
(--fs-name)
opt_clonename="$2"
shift 2
;;
(--dev)
opt_dev="$2"
shift 2
;;
(--size)
opt_size="$2"
shift 2
;;
(-e|--name)
opt_name="$2"
shift 2
;;
(-d|--debug)
opt_debug='1'
opt_quiet=''
opt_verbose='1'
shift 1
;;
(-n|--dry-run)
opt_dry_run='1'
shift 1
;;
(-p|--prefix)
opt_prefix="$2"
shift 2
;;
(-v|--verbose)
opt_verbose='1'
shift 1
;;
(-s|--syslog)
opt_syslog='1'
shift 1
;;
(-l|--label)
opt_label="$2"
shift 2
;;
(-k|--keep)
opt_keep="$2"
shift 2
;;
(-i|--rootvolid)
opt_rootvolid="$2"
shift 2
;;
(-m|--mountpoint)
opt_mountpoint="$2"
shift 2
;;
(--dirs|-t)
opt_createdirs='1'
shift 1
;;
(-h)
print_usage
shift 1
exit
;;
(--)
shift 1
break
;;
esac
done
[ "$opt_command" = xbiancopy ] && [ ! -e "$opt_dev" -o -z "$opt_dev" ] && opt_dev=$1
[ ! -e "$opt_dev" -o -z "$opt_dev" ] && opt_dev="$(findmnt -n -o SOURCE -v /)"
if [ "$(blkid -o value -s TYPE $opt_dev)" != btrfs ]; then
print_log notice "Filesystem is not of type btrfs. Not taking any actions."
exit 0
fi
if [ "$opt_command" = listvol ]; then
md=$(mktemp -d /run/lv-XXXXXXX)
trap "do_run umount $md; rmdir $md;" INT TERM EXIT
do_run mount -t btrfs $opt_dev $md
for lv in $(ls $md/); do
echo $lv
done
exit 0
elif [ "$opt_command" = volsize ]; then
md=$(mktemp -d /run/vs-XXXXXXX)
trap "do_run umount $md; rmdir $md;" INT TERM EXIT
do_run mount -t btrfs -o subvolid=0 $opt_dev $md
[ ! -d $md/$1 ] || printf "$(du -xsb $md/$1 | awk '{print $1}')\n"
exit 0
fi
print_log debug "Using command $opt_command."
if [ "$#" -eq '0' ]; then
if [ "$opt_command" != 'list' -a "$opt_command" != 'xbiancopy' -a "$opt_command" != 'backuphome' -a "$opt_command" != 'restorehome' ]; then
print_log error "The filesystem argument list is empty."
exit 133
else
set "//"
fi
fi
if [ "$opt_delim" = '' ]; then
print_log error "Snapshot's name first character can't be empty - can be longer than one character. Default shoul be '@'. Don't use this character in volume names afterwards."
exit 134
fi
if [ -n "$opt_name" ]; then
opt_prefix="$opt_name"
opt_label=''
opt_keep=''
fi
# ISO style date; fifteen characters: YYYY-MM-DD-HHMM
# On Solaris %H%M expands to 12h34.
DATE=$(date +%F-%H%M)
CONFIGPATH='/home/xbian/.kodi/userdata/addon_data/plugin.xbianconfig'
COUNTER='0'
while true; do
if $(mkdir "${tmp_dir}" > /dev/null 2>&1); then
if ! $(mount -t btrfs -o subvol=/ $opt_dev $opt_mountpoint 2>/dev/null); then
print_log error "unable to mount the filesystem"
rmdir ${tmp_dir}
exit 100
fi
trap "btrfs fi sync ${opt_mountpoint} > /dev/null; \
while ! umount ${opt_mountpoint} > /dev/null; do sleep 1; done; \
rmdir '${tmp_dir}' > /dev/null; \
mountpoint -q /tmp/btrfs-dest && umount -l /tmp/btrfs-dest; \
mountpoint -q /tmp/btrfs-source && umount -l /tmp/btrfs-source; \
do_remove_kpartx; mapAndMountFn umount; " INT TERM EXIT
break
fi
print_log error "another copy is running ... $COUNTER"
test "$COUNTER" -gt '11' && exit 99
sleep 5
COUNTER=$(( $COUNTER + 1 ))
done
[ $(( $(df "${tmp_dir}" | tail -1 | awk '{print $4}') * 100 / $(df "${tmp_dir}" | tail -1 | awk '{print $2}') )) -lt 30 ] && opt_keep=1 && echo "WARNING: Less than 30% of free space. Keeping only one single snapshot..." > /dev/stderr
#mdt=$(btrfs fi df "${tmp_dir}" | grep -m1 "Metadata, " | awk -F'=' '{print "("$2}' | grep -o "[\(0-9BKMGi\.]*" | sed 's/GiB/ * 1024 MiB/;s/MiB/ * 1024 KiB/;s/KiB/ * 1024/; s/$/ +\\/; $a0 )/1' | bc)
#mdu=$(btrfs fi df "${tmp_dir}" | grep -m1 "Metadata, " | awk -F'=' '{print "("$3}' | grep -o "[\(0-9BKMGi\.]*" | sed 's/GiB/ * 1024 MiB/;s/MiB/ * 1024 KiB/;s/KiB/ * 1024/; s/$/ +\\/; $a0 )/1' | bc)
#[ "$mdt" -gt 0 ] && [ $(( 100 - (mdu*100 / mdt) )) -lt 30 ] && opt_keep=1 && echo "WARNING: Less than 30% of free metadata space. Keeping only one single snapshot..." > /dev/stderr
test -d $opt_mountpoint/$opt_snapdir && test -n "$opt_snapdir" && opt_vroot='1'
DATE=$(date +%F-%H%M)
case $(ps -o stat= -p $$) in
*+*) BACKGROUND=0 ;;
*) BACKGROUND=1 ;;
esac
# The snapshot name after the @ symbol.
SNAPNAME="$opt_delim$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}"
# The expression for matching old snapshots. -YYYY-MM-DD-HHMM
SNAPGLOB="$opt_delim$opt_prefix${opt_label:+?$opt_label}????????????????"
BTRFS_LIST=$(btrfs sub list ${opt_mountpoint} | grep -v "/" | awk '{print $9}' | sort) \
|| { print_log error "unable to get list of volumes"; exit 102; }
for s in $(echo $opt_exclude | tr ',' ' '); do
BTRFS_LIST=$(printf "%s\n" "$BTRFS_LIST" | grep -vw $s)
done
BTRFS_LIST=$(printf "%s\n" "$BTRFS_LIST")
test -n "$opt_vroot" && BTRFS_LIST=$(printf ".\n%s\n" "$BTRFS_LIST")
case $opt_command in
(list|snapshot)
# Count the number of times '//' appears on the command line.
SLASHIES='0'
for ii in "$@"
do
test "$ii" = '//' && SLASHIES=$(( $SLASHIES + 1 ))
done
if [ "$#" -gt '1' -a "$SLASHIES" -gt '0' ]
then
print_log error "The // must be the only argument if it is given."
exit 134
fi
# Verify that each argument exists
for ii in "$@"
do
test "$ii" = '//' && continue 1
for jj in $BTRFS_LIST
do
test "$ii" = "$jj" && continue 2
done
print_log error "$ii is not existing volume."
exit 138
done
TARGETS=''
for ii in "$@"
do
if [ "$ii" = '//' ]; then
TARGETS=$BTRFS_LIST
continue
fi
TARGETS=$(printf "%s\n%s" $TARGETS $ii)
done
;;
(*)
;;
esac
if [ -n "$opt_snapdir" ]; then
SNAPSHOTS_OLD=$(btrfs sub list ${opt_mountpoint} | sort -k9r | grep ${opt_snapdir}${opt_delim} | awk '{print $9}' | awk -F"$opt_snapdir" '{print $1 $2}') \
|| { print_log error "unable to get list of snapshots"; exit 103; }
else
SNAPSHOTS_OLD=$(btrfs sub list ${opt_mountpoint} | sort -k9r | grep ${opt_delim} | awk '{print $9}') \
|| { print_log error "unable to get list of snapshots"; exit 103; }
fi
case $opt_command in
(createvol)
btrfs sub create ${opt_mountpoint}/$1
btrfs sub create ${opt_mountpoint}/$1/@
;;
(list)
for t in $TARGETS; do
printf "%s\n" $SNAPSHOTS_OLD | grep "$t/" | sort
done
;;
(snapshot)
do_snapshot
print_log notice "$SNAPNAME," \
"$SNAPSHOT_COUNT created snapshots," \
"$DESTROY_COUNT destroyed snapshots," \
"$WARNING_COUNT warnings."
;;
(destroy)
do_destroy "$1"
;;
(rename)
do_rename "$1" "$2"
;;
(rollback)
tmpd=$(mktemp -u)
tmpd=$(basename $tmpd)
do_rename $(dirname $1)/@ $(dirname $1)/@$tmpd
do_rename $1 $(dirname $1)/@
do_rename $(dirname $1)/@$tmpd $1_rollback
;;
(xbiancopy)
XBIANCOPYLOG='/tmp/xbiancopy.log'
echo "$@" > $XBIANCOPYLOG
if [ "$BACKGROUND" == 1 ]; then
PIPECMD1="tee -a $XBIANCOPYLOG"
PIPECMD2=$PIPECMD1
else
PIPECMD1="dialog --progressbox \"Preparing devices and copying /boot\" 10 70"
PIPECMD2="dialog --gauge \"Cloning all subvolumes. Please wait...\" 10 70"
fi
file_max=$(sysctl -n fs.file-max)
sysctl -w fs.file-max=$MY_FILE_MAX &>/dev/null
if [ "$(awk '/^MemTotal/{ print $2 }' /proc/meminfo)" -gt 600000 ]; then
min_free_kbytes=$(sysctl -n vm.min_free_kbytes)
sysctl -w vm.min_free_kbytes=$MY_MIN_FREE_KBYTES &>/dev/null
fi
echo "fn=" >/run/vars.xbiancopy.$$
set -o pipefail
(
[ $# -ne 2 ] && { echo "wrong aruments list"; exit 5; }
[ "$(readlink -e $1)" = "$(readlink -e $2)" ] && { echo "source and destination can not be the same block device"; exit 5; }
if [ $opt_dev != $1 ]; then
[ ! -b $1 ] && { echo "non existend source device"; exit 5; }
BTRFS_LIST=$(btrfs-auto-snapshot listvol --dev $1 //)
fi
mkdir -p /tmp/btrfs-dest
if [ -n "$opt_img" ]; then
fn=$(mapAndMountFn mount $2)
[ "$?" -eq 0 ] || { echo "can not mount fs"; exit 5; }
set -- $1 $fn
echo "fn=$2" >/run/vars.xbiancopy.$$
[ -e "$2" ] && rm -f "$2"; touch "$2" || { echo "can not create img file"; exit 5; }
[ "$(df -P "$2" | tail -1 | awk '{print $1}')" = "$(readlink -e $1)" ] && { echo "img file can not be created on the source filesystem"; exit 5; }
if grep -q "rootflags=subvol=" /proc/cmdline; then
subvolumeextra=$(cat /proc/cmdline); subvolumeextra=${subvolumeextra#*rootflags=subvol=}; subvolumeextra=${subvolumeextra%%\/@*}
export subvolumeextra
snapextra=$(cat /proc/cmdline); snapextra=${snapextra#*rootflags=subvol=$subvolumeextra"/@"}; snapextra=${snapextra%%,*}
fi
BTRFS_LIST=$(printf "%s\n%s\n" "$subvolumeextra" "$BTRFS_LIST" | sort | uniq)
snapextra=$(printf "%s\n%s\n" "@$snapextra" "@"| sort | uniq)
if [ "$opt_size" = "0" ]; then
for v in $BTRFS_LIST; do
[ -n "$v" ] || continue
export v="$v"
for s in $snapextra; do
sv=$(btrfs-auto-snapshot volsize "$v/$s")
size=$((size+sv))
done
done
size="$(($size*12/10/1024/1024))M"
else
size=$opt_size
fi
[ "$(printf "%s\n%s\n" $(df -h $2 | tail -1 | awk '{print $4}') "$size" | sort -rh | grep -m1 .)" = "$size" ] && { echo "not enough free space on destination filesystem"; exit 5; }
chattr +CA $2 &>/dev/null; truncate -s $size $2 || { echo "can't resize img file to needed size"; exit 5; }
modprobe -q loop
parted -s $2 mklabel msdos
config_offset_root=141312
echo "2048,$((config_offset_root-2048)),b,*," | sfdisk -uS -N1 -f -q $2 > /dev/null 2>&1
echo "$config_offset_root,+,83,," | sfdisk -uS -N2 -f -q $2 > /dev/null 2>&1
loopd=$(losetup -f); losetup $loopd $2
kpartx -a $loopd && echo "loopd=$loopd" >>/run/vars.xbiancopy.$$
loopd=${loopd#/dev/}
timeout=0
while ! [ -L /dev/mapper/${loopd}p1 ]; do
timeout=$(($timeout+1))
[ $timeout -gt 10 ] && { echo "Loop device not available"; exit 4; }
sleep 1;
done
if [ "$(xbian-arch)" = iMX6 ]; then
dd if=/etc/uboot-env/SPL of=/dev/$loopd bs=1K seek=1 2>/dev/null 1>&2
dd if=/etc/uboot-env/u-boot.img of=/dev/$loopd bs=1K seek=42 2>/dev/null 1>&2
mkfs.ext2 -L xbianboot /dev/mapper/${loopd}p1 2>/dev/null 1>&2
else
mkfs.msdos -F 16 -n xbianboot /dev/mapper/${loopd}p1 2>/dev/null 1>&2
fi
mount /dev/mapper/${loopd}p1 /tmp/btrfs-dest || exit 5
[ "$BACKGROUND" == 1 ] && cp -av /boot/* /tmp/btrfs-dest || cp -a /boot/* /tmp/btrfs-dest &>/dev/null
sync
if [ "$(xbian-arch)" = RPI ]; then
sed -i 's/^#initramfs /initramfs /' /tmp/btrfs-dest/config.txt
fi
if [ "$(xbian-arch)" = iMX6 ]; then
[ -e /boot/initramfs.gz.notinuse -a ! -e /boot/initramfs.gz ] && mv /tmp/btrfs-dest/initramfs.gz.notinuse /tmp/btrfs-dest/initramfs.gz
fi
while ! umount /tmp/btrfs-dest; do ! mountpoint -q /tmp/btrfs-dest && break; sleep 1; done; rmdir /tmp/btrfs-dest; sync
set -- $1 /dev/mapper/${loopd}p2
fi
mkdir -p /tmp/btrfs-source
mount -t btrfs -o noatime,subvol=/ $1 /tmp/btrfs-source
eval $opt_remotecmd findmnt -n -S $2 >/dev/null && { eval $opt_remotecmd umount $2 || { echo "can not umount destination partition"; exit 5; }; }
if [ -z "$opt_nocreate" ]; then
eval $opt_remotecmd mkfs.btrfs -O skinny-metadata -m single -L $opt_clonename -f $2 2>&1 || { echo "can not format destination $2"; exit 5; }
fi
eval $opt_remotecmd mkdir -p /tmp/btrfs-dest
if ! eval findmnt -n / | grep -q "compress=lz4" || ! eval $opt_remotecmd mount -o compress=lz4,noatime,thread_pool=1 $2 /tmp/btrfs-dest; then
if ! eval $opt_remotecmd mount -o compress=lzo,noatime,thread_pool=1 $2 /tmp/btrfs-dest; then
echo "can not mount destination partition"; exit 6
fi
fi
) | eval "$PIPECMD1"
rc=$?
set +o pipefail
. /run/vars.xbiancopy.$$
if [ "$rc" -eq 0 ]; then
if grep -q "rootflags=subvol=" /proc/cmdline; then
subvolumeextra=$(cat /proc/cmdline); subvolumeextra=${subvolumeextra#*rootflags=subvol=}; subvolumeextra=${subvolumeextra%%\/@*}
export subvolumeextra
snapextra=$(cat /proc/cmdline); snapextra=${snapextra#*rootflags=subvol=$subvolumeextra"/@"}; snapextra=${snapextra%%,*}
fi
set -o pipefail
(
BTRFS_LIST=$(printf "%s\n%s\n" "$subvolumeextra" "$BTRFS_LIST" | sort | uniq)
snapextra=$(printf "%s\n%s\n" "@$snapextra" "@"| sort | uniq)
echo "Cloning subvolumes $BTRFS_LIST ($snapextra)" | sed ':a;N;$!ba;s/\n/, /g;s/, $//g' >> $XBIANCOPYLOG
set -o pipefail
for v in $BTRFS_LIST; do
[ -n "$v" ] || continue
eval $opt_remotecmd btrfs sub create "/tmp/btrfs-dest/$v"
export v="$v"
for s in $snapextra; do
[ -n "$s" ] || continue
[ -e "/tmp/btrfs-source/$v/$s" ] || continue
[ -e "/tmp/btrfs-source/$v/$s.ro" ] && btrfs sub delete "/tmp/btrfs-source/$v/$s.ro" &>/dev/null
[ -e "/tmp/btrfs-source/$v/$s" ] || continue
btrfs sub snap -r "/tmp/btrfs-source/$v/$s" "/tmp/btrfs-source/$v/$s.ro" && sync && btrfs fi sync "/tmp/btrfs-source/$v/$s.ro"
if [ "$opt_cpmode" = tar ]; then
eval $opt_remotecmd btrfs sub create /tmp/btrfs-dest/$v/$s && eval $opt_remotecmd nice -n 10 btrfs fi sync "/tmp/btrfs-dest/$v/$s" || exit 12
fi
export siz=$(du -sxb "/tmp/btrfs-source/$v/$s.ro" | awk '{print $1}'); export sn=$s; export vln=$v;
[ "$siz" -gt 100000000 ] && export ival=20 || export ival=2
ar=0
copy_volume () {
if [ "$1" = tar ]; then
to='--warning=none --acls --xattrs'
nice -n 10 tar c $to -C /tmp/btrfs-source/$v/$s.ro . | pv -i $ival -n -s $siz | eval $opt_remotecmd nice -n 10 tar x $to -C "/tmp/btrfs-dest/$v/$s"
else
nice -n 10 btrfs send "/tmp/btrfs-source/$v/$s.ro" | pv -i $ival -n -s $siz | eval $opt_remotecmd nice -n 10 btrfs receive "/tmp/btrfs-dest/$v"
fi
}
( copy_volume $opt_cpmode ) 2>&1 | \
while read a; do
if [ "$BACKGROUND" == 1 ]; then
if ! echo "$a" | grep -q '@'; then
if (( $a > $ar )); then
echo "$(printf 'Cloning %s/%s, %d of %d, %d%% done' $v $s $((siz*a/100)) $siz $a)"
ar=$a
fi
fi
else
printf "XXX\n$a\n$(printf '\n Cloning %s/%s. Total %d. Done %d' $v $s $siz $((siz*a/100)))\nXXX\n"
fi
done
[ "$?" -eq 0 ] || { [ "$BACKGROUND" == 1 ] && printf "%s\n" "Copying subvolume $v snapshot $s failed"; exit 10; }
if [ "$opt_cpmode" = tar ]; then
eval $opt_remotecmd nice -n 10 btrfs fi sync "/tmp/btrfs-dest/$v/$s" && sync && btrfs sub delete "/tmp/btrfs-source/$v/$s.ro" || exit 11
else
eval $opt_remotecmd nice -n 10 btrfs fi sync "/tmp/btrfs-dest/$v/$s.ro" && sync && btrfs sub delete "/tmp/btrfs-source/$v/$s.ro" || exit 11
eval $opt_remotecmd nice -n 10 btrfs sub snap "/tmp/btrfs-dest/$v/$s.ro" "/tmp/btrfs-dest/$v/$s" && sync || exit 12
eval $opt_remotecmd nice -n 10 btrfs fi sync "/tmp/btrfs-dest/$v/$s" || exit 13
eval $opt_remotecmd nice -n 10 btrfs sub delete "/tmp/btrfs-dest/$v/$s.ro" && eval $opt_remotecmd nice -n 10 btrfs fi sync "/tmp/btrfs-dest/$v" || exit 14
eval $opt_remotecmd nice -n 10 btrfs fi sync "/tmp/btrfs-dest/$v" || exit 14
fi
[ "$BACKGROUND" == 1 ] && printf "%s\n" "Done copying subvolume $v snapshot $s"
done
[ "$BACKGROUND" == 1 ] && printf "%s\n" "Done copying subvolume $v"
done
exit 0
) 2>&1 | eval "$PIPECMD2"
rc=$?
set +o pipefail
fi
(
loopd=${loopd#/dev/}
if [ "$rc" -eq 0 -a -n "$opt_img" ]; then
UUID=$(blkid -s UUID -o value /dev/mapper/${loopd}p1)
[ "$BACKGROUND" == 1 ] && echo "Setting UUID=$UUID for boot partition in /etc/fstab" >> $XBIANCOPYLOG
eval $opt_remotecmd sed -i \'s%.*/boot%UUID=$UUID /boot%g\' /tmp/btrfs-dest/root/@/etc/fstab
fi
eval $opt_remotecmd umount /tmp/btrfs-dest; eval $opt_remotecmd rmdir /tmp/btrfs-dest
umount /tmp/btrfs-source; rmdir /tmp/btrfs-source
if [ "$rc" -eq 0 -a -n "$opt_img" ]; then
mkdir -p /tmp/btrfs-dest
if mount /dev/mapper/${loopd}p1 /tmp/btrfs-dest; then
z=$(grep -o "root=[^ ]*" /proc/cmdline)
UUID=$(blkid -s UUID -o value /dev/mapper/${loopd}p2)
if test -e /boot/cmdline.txt; then
[ "$BACKGROUND" == 1 ] && echo "Setting UUID=$UUID for root partition in cmdline.txt" >> $XBIANCOPYLOG
sed -i "s%$z%root=UUID=$UUID%" /tmp/btrfs-dest/cmdline.txt
fi
if test -e /boot/boot.scr.txt; then
[ "$BACKGROUND" == 1 ] && echo "Setting UUID=$UUID for root partition in boot.scr.txt" >> $XBIANCOPYLOG
( cd /tmp/btrfs-dest; sed -i "s%$z%root=UUID=$UUID%" boot.scr.txt; ./mks; )
fi
while ! umount /tmp/btrfs-dest; do ! mountpoint -q /tmp/btrfs-dest && break; sleep 1; done; rmdir /tmp/btrfs-dest; sync
fi
fi
) &>/dev/null
if [ "$rc" -eq 0 ]; then
success_msg="Done!"
[ -z "$opt_helper" -a -e ${CONFIGPATH} ] && { echo "$DATE" > ${CONFIGPATH}/$opt_command; chown xbian:xbian ${CONFIGPATH}/${opt_command}; }
else
success_msg="Fail!"
if [ -n "$fn" ]; then
[ "$BACKGROUND" == 1 ] && echo "Removing broken image file ${fn##*/}" >> $XBIANCOPYLOG
rm -f "$fn"
fi
fi
echo "$success_msg" >> $XBIANCOPYLOG
if [ -z "$opt_helper" ]; then
dialog --clear --msgbox "$success_msg
for users on xbian version below beta2 (beta1.1, beta1). beta2 handles this automaticaly.
/etc/fstab file needs to be adapted for /home to be mounted properly on the new xbian copy.
do the following steps:
1) mount -t btrfs -o subvol=root/@ LABEL=xbian-copy /mnt
2) nano /mnt/etc/fstab
3) change all lines starting with LABEL=xbian-root-btrfs to LABEL=xbian-copy .......
4) umount /mnt
for all users - change /boot/cmdline.txt if you want to boot from the copy just created. change root=LABEL=xbian-root-btrfs to root=LABEL=xbian-copy
if you created img file, cmdline.txt was updated automatically." 24 79
fi
[ -e "/tmp/xbiancopy.running" ] && touch /tmp/xbiancopy.running.$(cat /tmp/xbiancopy.running)
exit $rc
;;
(backuphome)
BACKUPHOMELOG='/tmp/backuphome.log'
if [ "$BACKGROUND" == 1 ]; then
echo "$@" > $BACKUPHOMELOG
PIPECMD="tee $BACKUPHOMELOG"
else
PIPECMD="dialog --gauge \"Cloning home. Please wait...\" 10 60"
fi
fn=$(mapAndMountFn mount $1)
[ "$?" -eq 0 ] || { echo "can not mount fs"; exit 5; }
set -- $fn
if [ "$(dirname $1)" = "/xbmc-backup" -o "$1" = "//" ]; then
FILE="/xbmc-backup/temp/backup_home_$DATE.img.gz"
mkdir /xbmc-backup/temp
find /xbmc-backup -type f -print0 | xargs -0 rm -f || :
else
FILE="$1"
[ -e $FILE ] && rm -rf $FILE
fi
packcmd=$(which lz4) || packcmd=gzip
btrfs sub snap -r "${tmp_dir}/home/@" "${tmp_dir}/home/@ro"
touch $FILE; chattr +C $FILE
echo 999>/tmp/backuphome.statusx
( btrfs send -v "${tmp_dir}/home/@ro" | pv -i 20 -n -s $(du -sxb ${tmp_dir}/home/@ro | awk '{print $1}') | $packcmd -1 >> $FILE; echo $?>/tmp/backuphome.status) 2>&1 | eval "$PIPECMD"
btrfs sub delete "${tmp_dir}/home/@ro" && btrfs fi sync ${tmp_dir}
[ $(cat /tmp/backuphome.status) -eq 0 ] || { rm -f $FILE; exit 1; }
if [ "$(dirname $1)" = "/xbmc-backup" -o "$1" = "//" ]; then
[ "$1" = "//" ] && mv $FILE /xbmc-backup/$(hostname)_backup_home_$DATE.img.gz || mv $FILE $1; rmdir /xbmc-backup/temp
mkdir -p /xbmc-backup/put_here_to_restore
fi
[ -e "/tmp/backuphome.running" ] && touch /xbmc-backup/backuphome.running.$(cat /tmp/backuphome.running)
[ -e ${CONFIGPATH} ] && { echo "$DATE" > ${CONFIGPATH}/$opt_command; chown xbian:xbian ${CONFIGPATH}/${opt_command}; }
;;
(restorehome)
[ -e $1 ] || exit 500
mv $1 $1.working
gunzip --suffix .working -t $1.working || lz4 -t $1.working || exit 500
btrfs sub delete ${tmp_dir}/home/@ro > /dev/null 2>&1
gunzip -cd < $1.working | btrfs receive -v ${tmp_dir}/home
[ $? -ne 0 ] && exit 1
rm -fr $1.working
{ pgrep xbmc.bin >/dev/null || pgrep kodi.bin >/dev/null; } && { touch /run/start.xbmc; stop -q xbmc; }
stop -q zram-swap
hm=""; for m in $(mount | awk '{print $3}'| grep "/home"); do hm=$hm" "$m; done
locks=$(lsof | grep "/home" | awk '{print $2}' | sort | uniq)
i=0; sig='-SIGTERM'
while test "$locks" != ''; do
[ $i -gt 3 ] && sig='-SIGKILL'
for p in $locks; do
kill $sig $p
done
sleep 2
locks=$(lsof | grep "/home" | awk '{print $2}' | sort | uniq)
i=$((i+1))
done
for m in $(mount | awk '{print $3}' | grep "/home" | sort -r); do while mountpoint -q "$m"; do umount "$m" || sleep 1; done; done
btrfs sub delete ${tmp_dir}/home/@
btrfs sub snap ${tmp_dir}/home/@ro ${tmp_dir}/home/@
btrfs sub delete ${tmp_dir}/home/@ro
for m in $hm; do mount $m; done
start zram-swap
[ -e /run/start.xbmc ] && { rm /run/start.xbmc; start -qn xbmc; }
;;
(*)
print_log error "Wrong command."
exit 200
;;
esac
exit 0