Moving out live-helpers from initramfs-tools specifics.
[live-boot-grml.git] / bin / live-snapshot
index 7cf9bf9..e684824 100755 (executable)
@@ -3,15 +3,16 @@
 # live-snapshot - utility to manage Debian Live systems snapshots
 #
 #   This program mounts a device (fallback to /tmpfs under $MOUNTP
 # live-snapshot - utility to manage Debian Live systems snapshots
 #
 #   This program mounts a device (fallback to /tmpfs under $MOUNTP
-#   and saves the /live/cow (or a different dir) filesystem in it for reuse
-#   in another live-initramfs session. Look at manpage for more info.
+#   and saves the /live/overlway (or a different directory) filesystem in it
+#   for reuse in another live-boot session.
+#   Look at the manpage for more informations.
 #
 #
-# Copyright (C) 2006-2008 Marco Amadori <marco.amadori@gmail.com>
+# Copyright (C) 2006-2011 Marco Amadori <marco.amadori@gmail.com>
 # Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>
 #
 # Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>
 #
-# This program is free software; you can redistribute it and/or modify
+# 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
 # it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
+# the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
 #
 #
-# On Debian systems, the complete text of the GNU General Public License
-# can be found in /usr/share/common-licenses/GPL-2 file.
+# The complete text of the GNU General Public License
+# can be found in /usr/share/common-licenses/GPL-3 file.
+
+# declare here two vars from /etc/live.conf because of "set -u"
+ROOTSNAP=""
+HOMESNAP=""
+
+if [ -n "${LIVE_SNAPSHOT_CHECK_UNBOUND}" ]
+then
+       set -eu
+else
+       set -e
+fi
+
+## Begin FIXME: this is an embedded copy of the old 'live-helpers' initramfs script
+if [ ! -x "/bin/fstype" ]
+then
+       # klibc not in path -> not in initramfs
+       export PATH="${PATH}:/usr/lib/klibc/bin"
+fi
+
+# handle upgrade path from old udev (using udevinfo) to
+# recent versions of udev (using udevadm info)
+if [ -x /sbin/udevadm ]
+then
+       udevinfo='/sbin/udevadm info'
+else
+       udevinfo='udevinfo'
+fi
+
+old_root_overlay_label="live-rw"
+old_home_overlay_label="home-rw"
+custom_overlay_label="custom-ov"
+root_snapshot_label="live-sn"
+old_root_snapshot_label="live-sn"
+home_snapshot_label="home-sn"
+persistence_list="live-persistence.conf"
+
+# include all scripts for the time being until snapshots are either dropped or cleaned up
+for _SCRIPT in /lib/live/boot/*
+do
+       if [ -e "${_SCRIPT}" ]
+       then
+               . ${_SCRIPT}
+       fi
+done
+## End FIXME: this is an embedded copy of the old 'live-helpers' initramfs script
 
 
-set -eu
+LIVE_CONF="/etc/live/boot.d/snapshot.conf"
 
 
-. /usr/share/initramfs-tools/scripts/live-helpers
-. /etc/live.conf
+if [ -r "${LIVE_CONF}" ]
+then
+       . "${LIVE_CONF}"
+fi
 
 export USERNAME USERFULLNAME HOSTNAME
 
 
 export USERNAME USERFULLNAME HOSTNAME
 
-PROGRAM="$(basename $0)"
+EXECUTABLE="${0}"
+PROGRAM=$(basename "${EXECUTABLE}")
 
 # Needs to be available at run and reboot time
 SAFE_TMPDIR="/live"
 
 # Permits multiple runs
 MOUNTP="$(mktemp -d -p ${SAFE_TMPDIR} live-snapshot-mnt.XXXXXX)"
 
 # Needs to be available at run and reboot time
 SAFE_TMPDIR="/live"
 
 # Permits multiple runs
 MOUNTP="$(mktemp -d -p ${SAFE_TMPDIR} live-snapshot-mnt.XXXXXX)"
-SNAP_COW="/live/cow"
-SNAP_DEV=""
 DEST="${MOUNTP}/live-sn.cpio.gz"
 DEST="${MOUNTP}/live-sn.cpio.gz"
-SNAP_TYPE="cpio"
-DESKTOP_LINK="/home/${USERNAME}/Desktop/live-snapshot"
+DEF_SNAP_COW="/live/overlay"
+TMP_FILELIST="${PROGRAM}.list"
+
+# Command line defaults and declarations
+SNAP_COW="${DEF_SNAP_COW}"
+SNAP_DEV=""
+SNAP_MNT=""
+SNAP_OUTPUT=""
 SNAP_RESYNC_STRING=""
 SNAP_RESYNC_STRING=""
+SNAP_TYPE="cpio"
+SNAP_LIST="/etc/live-snapshot.list"
+EXCLUDE_LIST="/etc/live-snapshot.exclude_list"
 
 Error ()
 {
 
 Error ()
 {
@@ -64,6 +119,7 @@ Header ()
        echo
        echo "usage: ${PROGRAM} [-c|--cow DIRECTORY] [-d|--device DEVICE] [-o|--output FILE] [-t|--type TYPE]"
        echo "       ${PROGRAM} [-r|--resync-string STRING]"
        echo
        echo "usage: ${PROGRAM} [-c|--cow DIRECTORY] [-d|--device DEVICE] [-o|--output FILE] [-t|--type TYPE]"
        echo "       ${PROGRAM} [-r|--resync-string STRING]"
+       echo "       ${PROGRAM} [-f|--refresh]"
        echo "       ${PROGRAM} [-h|--help]"
        echo "       ${PROGRAM} [-u|--usage]"
        echo "       ${PROGRAM} [-v|--version]"
        echo "       ${PROGRAM} [-h|--help]"
        echo "       ${PROGRAM} [-u|--usage]"
        echo "       ${PROGRAM} [-v|--version]"
@@ -79,7 +135,8 @@ Help ()
        echo "  -d, --device: output snapshot device (default: ${SNAP_DEV:-auto})."
        echo "  -o, --output: output image file (default: ${DEST})."
        echo "  -r, --resync-string: internally used to resync previous made snapshots."
        echo "  -d, --device: output snapshot device (default: ${SNAP_DEV:-auto})."
        echo "  -o, --output: output image file (default: ${DEST})."
        echo "  -r, --resync-string: internally used to resync previous made snapshots."
-       echo "  -t, --type: snapshot filesystem type. Options: \"squashfs\", \"ext2\", \"ext3\", \"jffs2\" or \"cpio\".gz archive (default: ${SNAP_TYPE})"
+       echo "  -f, --refresh: try to sync a running snapshot."
+       echo "  -t, --type: snapshot filesystem type. Options: \"squashfs\", \"ext2\", \"ext3\", \"ext4\", \"jffs2\" or \"cpio\".gz archive (default: ${SNAP_TYPE})"
        echo
        echo "Look at live-snapshot(1) man page for more information."
 
        echo
        echo "Look at live-snapshot(1) man page for more information."
 
@@ -100,12 +157,12 @@ Version ()
 {
        echo "${PROGRAM}"
        echo
 {
        echo "${PROGRAM}"
        echo
-       echo "Copyright (C) 2006 Marco Amadori <marco.amadori@gmail.com>"
+       echo "Copyright (C) 2006-2011 Marco Amadori <marco.amadori@gmail.com>"
        echo "Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>"
        echo
        echo "This program is free software; you can redistribute it and/or modify"
        echo "it under the terms of the GNU General Public License as published by"
        echo "Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>"
        echo
        echo "This program is free software; you can redistribute it and/or modify"
        echo "it under the terms of the GNU General Public License as published by"
-       echo "the Free Software Foundation; either version 2 of the License, or"
+       echo "the Free Software Foundation; either version 3 of the License, or"
        echo "(at your option) any later version."
        echo
        echo "This program is distributed in the hope that it will be useful,"
        echo "(at your option) any later version."
        echo
        echo "This program is distributed in the hope that it will be useful,"
@@ -117,19 +174,40 @@ Version ()
        echo "along with this program; if not, write to the Free Software"
        echo "Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA"
        echo
        echo "along with this program; if not, write to the Free Software"
        echo "Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA"
        echo
-       echo "On Debian systems, the complete text of the GNU General Public License"
-       echo "can be found in /usr/share/common-licenses/GPL-2 file."
+       echo "The complete text of the GNU General Public License"
+       echo "can be found in /usr/share/common-licenses/GPL-3 file."
        echo
        echo
-       echo "Homepage: <http://debian-live.alioth.debian.org/>"
+       echo "Homepage: <http://live.debian.net/>"
 
        exit 0
 }
 
 
        exit 0
 }
 
+Try_refresh ()
+{
+       FOUND=""
+       if [ -n "${ROOTSNAP}" ]; then
+               "${EXECUTABLE}" --resync-string="${ROOTSNAP}"
+               FOUND="Yes"
+       fi
+
+       if [ -n "${HOMESNAP}" ]; then
+               "${EXECUTABLE}" --resync-string="${HOMESNAP}"
+               FOUND="Yes"
+       fi
+
+       if [ -z "${FOUND}" ]
+       then
+               echo "No autoconfigured snapshots found at boot;" > /dev/null 1>&2
+               echo "(no resync string in ${LIVE_CONF})." > /dev/null 1>&2
+               exit 1
+       fi
+}
+
 Parse_args ()
 {
        # Parse command line
        ARGS="${*}"
 Parse_args ()
 {
        # Parse command line
        ARGS="${*}"
-       ARGUMENTS="$(getopt --longoptions cow:,device:,output,resync-string:,type:,help,usage,version --name=${PROGRAM} --options c:d:o:t:r:,h,u,v --shell sh -- ${ARGS})"
+       ARGUMENTS="$(getopt --longoptions cow:,device:,output,resync-string:,refresh,type:,help,usage,version --name=${PROGRAM} --options c:d:o:t:r:fhuv --shell sh -- ${ARGS})"
 
        eval set -- "${ARGUMENTS}"
 
 
        eval set -- "${ARGUMENTS}"
 
@@ -161,6 +239,11 @@ Parse_args ()
                                break
                                ;;
 
                                break
                                ;;
 
+                       -f|--refresh)
+                               Try_refresh
+                               exit 0
+                               ;;
+
                        -h|--help)
                                Help
                                ;;
                        -h|--help)
                                Help
                                ;;
@@ -191,11 +274,12 @@ Defaults ()
        # Parse resync string
        if [ -n "${SNAP_RESYNC_STRING}" ]
        then
        # Parse resync string
        if [ -n "${SNAP_RESYNC_STRING}" ]
        then
-               SNAP_COW=$(echo "${SNAP_RESYNC_STRING}" | cut -f1 -d ':')
+               SNAP_COW=$(echo "${SNAP_RESYNC_STRING}" | sed -r -e 's#^([^:]*).*$#'"${DEF_SNAP_COW}"'\1#')
                SNAP_DEV=$(echo "${SNAP_RESYNC_STRING}" | cut -f2 -d ':')
                SNAP_DEV=$(echo "${SNAP_RESYNC_STRING}" | cut -f2 -d ':')
-               DEST="${MOUNTP}/$(echo ${SNAP_RESYNC_STRING} | cut -f3 -d ':')"
+               SNAP_MNT=$(echo "${SNAP_RESYNC_STRING}" | cut -f3 -d ':')
+               DEST="${MOUNTP}/${SNAP_MNT}"
 
 
-               case "${DEST}" in
+               case "${SNAP_MNT}" in
                        *.cpio.gz)
                                SNAP_TYPE="cpio"
                                ;;
                        *.cpio.gz)
                                SNAP_TYPE="cpio"
                                ;;
@@ -208,14 +292,24 @@ Defaults ()
                                SNAP_TYPE="jffs2"
                                ;;
 
                                SNAP_TYPE="jffs2"
                                ;;
 
-                       ""|*.ext2|*.ext3)
+                       *.ext2|*.ext3)
                                SNAP_TYPE="ext2"
                                ;;
                                SNAP_TYPE="ext2"
                                ;;
+
+                       "")
+                               SNAP_TYPE="whole_partition"
+                               ;;
+
+                       *.ext4)
+                               SNAP_TYPE="ext4"
+                               ;;
+
                        *)
                                Error "unrecognized resync string"
                                ;;
                esac
                        *)
                                Error "unrecognized resync string"
                                ;;
                esac
-       else
+       elif [ -z "${SNAP_OUTPUT}" ]
+       then
                # Set target file based on image
                case "${SNAP_TYPE}" in
                        cpio)
                # Set target file based on image
                case "${SNAP_TYPE}" in
                        cpio)
@@ -229,14 +323,20 @@ Defaults ()
                        ext3)
                                DEST="${MOUNTP}/live-sn.ext2"
                                ;;
                        ext3)
                                DEST="${MOUNTP}/live-sn.ext2"
                                ;;
+
+                       ext4)
+                               DEST="${MOUNTP}/live-sn.ext4"
+                               ;;
                esac
                esac
+       else
+               DEST="${SNAP_OUTPUT}"
        fi
 }
 
 Validate_input ()
 {
        case "${SNAP_TYPE}" in
        fi
 }
 
 Validate_input ()
 {
        case "${SNAP_TYPE}" in
-               cpio|squashfs|jffs2|ext2|ext3)
+               cpio|squashfs|jffs2|ext2|ext3|ext4|whole_partition)
                        ;;
 
                *)
                        ;;
 
                *)
@@ -272,24 +372,132 @@ Mount_device ()
        esac
 }
 
        esac
 }
 
+Entry_is_modified ()
+{
+       # Returns true if file exists and it is also present in "cow" directory
+       # This means it is modified in respect to read-only media, so it deserve
+       # to be saved
+
+       entry="${1}"
+
+       if [ -e "${entry}" ] || [ -L "${entry}" ]
+       then
+               if [ -e "${SNAP_COW}/${entry}" ] || [ -L "${SNAP_COW}/${entry}" ]
+               then
+                       return 0
+               fi
+       fi
+       return 1
+}
+
+Do_filelist ()
+{
+       # BUGS: does not handle deleted files yet
+       TMP_FILELIST=$1
+
+       if [ -f "${SNAP_LIST}" ]
+       then
+               # if SNAP_COW == /live/overlay/home, SNAP_RW = /home
+               SNAP_RW=$(echo "${SNAP_COW}" | sed -e "s|${DEF_SNAP_COW}||g")
+               if [ -z "${SNAP_RW}" ]
+               then
+                       SNAP_RW="/"
+               fi
+
+               cd "${SNAP_RW}"
+               # Generate include list removing empty and commented lines
+               # and transforming paths to relatives
+               for entry in $(sed -e '/^ *$/d' -e '/^#.*$/d' -e 's#^.*$#./&#' -e 's#/\+#/#g' "${SNAP_LIST}")
+               do
+                       if [ -d "${entry}" ]
+                       then
+                               find "${entry}" | while read line
+                               do
+                                       if Entry_is_modified "${line}"
+                                       then
+                                               printf "%s\000" "${line}" >> "${TMP_FILELIST}"
+                                       fi
+                               done
+                       elif Entry_is_modified "${entry}"
+                       then
+                               # if file exists and it is modified
+                               printf "%s\000" "${entry}" >> "${TMP_FILELIST}"
+                       fi
+               done
+               cd "${OLDPWD}"
+
+               # echo Working dir
+               echo "${SNAP_RW}"
+       else
+               cd "${SNAP_COW}"
+               # removing whiteouts from list
+               find . -path '*.wh.*' -prune -o -print0 >> "${TMP_FILELIST}"
+               cd "${OLDPWD}"
+               # echo Working dir
+               echo "${SNAP_COW}"
+       fi
+}
+
 Do_snapshot ()
 {
 Do_snapshot ()
 {
+       TMP_FILELIST=$(mktemp -p "${SAFE_TMPDIR}" "${TMP_FILELIST}.XXXXXX")
+       if [ -e "${EXCLUDE_LIST}" ]
+       then
+               # Create a TMP filelist removing empty lines (grep -f does not like them)
+               # and comments (for speedup and LST)
+               TMP_EXCLUDE_LIST=$(mktemp -p "${SAFE_TMPDIR}" "${PROGRAM}_excludelist.XXXXXX")
+               grep -v '^#.*$' "${EXCLUDE_LIST}" | grep -v '^ *$' > "${TMP_EXCLUDE_LIST}"
+       fi
+
        case "${SNAP_TYPE}" in
                squashfs)
        case "${SNAP_TYPE}" in
                squashfs)
-                       EXCLUDE_LIST="$(mktemp -p ${SAFE_TMPDIR} live-snapshot-exclude-list.XXXXXX)"
-                       echo "./${EXCLUDE_LIST}" > "${EXCLUDE_LIST}"
+                       echo ".${TMP_FILELIST}" > "${TMP_FILELIST}"
+                       # Removing whiteheads of unionfs
                        cd "${SNAP_COW}"
                        cd "${SNAP_COW}"
-                       find . -name '*.wh.*' >> "${EXCLUDE_LIST}"
+                       find . -name '*.wh.*' >> "${TMP_FILELIST}"
+
+                       if [ -e "${EXCLUDE_LIST}" ]
+                       then
+                               # Add explicitly excluded files
+                               cat "${TMP_EXCLUDE_LIST}" >> "${TMP_FILELIST}"
+                       fi
+
                        cd "${OLDPWD}"
                        cd "${OLDPWD}"
-                       mksquashfs "${SNAP_COW}" "${DEST}" -ef "${EXCLUDE_LIST}"
-                       rm -f "${EXCLUDE_LIST}"
+                       mksquashfs "${SNAP_COW}" "${DEST}" -ef "${TMP_FILELIST}"
                        ;;
 
                        ;;
 
-               cpio)
-                       ( cd "${SNAP_COW}" && find . -path '*.wh.*' -prune -o -print0 | cpio --quiet -o0 -H newc | gzip -9c > "${DEST}" ) || exit 1
+               cpio|whole_partition)
+                       if [ "${SNAP_TYPE}" = "cpio" ]
+                       then
+                               COPY_CMD="cpio --quiet -o0 -H newc | gzip -9c > ${DEST}"
+                       else
+                               COPY_CMD="cpio --quiet -pumd0 ${DEST}/"
+                       fi
+
+                       WORKING_DIR=$(Do_filelist "${TMP_FILELIST}")
+                       cd "${WORKING_DIR}"
+                       if [ -e "${EXCLUDE_LIST}" ]
+                       then
+
+                               # Convert \0 to \n and tag existing (rare but possible) \n in filenames,
+                               # this to let grep -F -v do a proper work in filtering out
+                               cat "${TMP_FILELIST}" | \
+                                       tr '\n' '\1' | \
+                                       tr '\0' '\n' | \
+                                       grep -F -v -f "${TMP_EXCLUDE_LIST}" | \
+                                       tr '\n' '\0' | \
+                                       tr '\1' '\n' | \
+                                       eval $COPY_CMD || exit 1
+                       else
+                               cat "${TMP_FILELIST}" | \
+                                       eval $COPY_CMD || exit 1
+                       fi
+                       cd "${OLDPWD}"
                        ;;
 
                        ;;
 
-               ext2|ext3)
+               # ext2|ext3|ext4 and jffs2 does not easily support an exclude list; files
+               # should be copied to another directory in order to filter content
+               ext2|ext3|ext4)
                        DU_DIM="$(du -ks ${SNAP_COW} | cut -f1)"
                        REAL_DIM="$(expr ${DU_DIM} + ${DU_DIM} / 20)" # Just 5% more to be sure, need something more sophistcated here...
                        genext2fs --size-in-blocks=${REAL_DIM} --reserved-percentage=0 --root="${SNAP_COW}" "${DEST}"
                        DU_DIM="$(du -ks ${SNAP_COW} | cut -f1)"
                        REAL_DIM="$(expr ${DU_DIM} + ${DU_DIM} / 20)" # Just 5% more to be sure, need something more sophistcated here...
                        genext2fs --size-in-blocks=${REAL_DIM} --reserved-percentage=0 --root="${SNAP_COW}" "${DEST}"
@@ -299,11 +507,20 @@ Do_snapshot ()
                        mkfs.jffs2 --root="${SNAP_COW}" --output="${DEST}"
                        ;;
        esac
                        mkfs.jffs2 --root="${SNAP_COW}" --output="${DEST}"
                        ;;
        esac
+
+       # Remove temporary file lists
+       for filelist in "${TMP_FILELIST}" "${TMP_EXCLUDE_LIST}"
+       do
+               if [ -f "${filelist}" ]
+               then
+                       rm -f "${filelist}"
+               fi
+       done
 }
 
 Clean ()
 {
 }
 
 Clean ()
 {
-       if echo "${DEST}" | grep -q "${MOUNTP}"
+       if [ -z "${SNAP_RESYNC_STRING}" ] && echo "${DEST}" | grep -q "${MOUNTP}"
        then
                echo "${DEST} is present on ${MOUNTP}, therefore no automatic unmounting the latter." > /dev/null 1>&2
        else
        then
                echo "${DEST} is present on ${MOUNTP}, therefore no automatic unmounting the latter." > /dev/null 1>&2
        else
@@ -312,6 +529,33 @@ Clean ()
        fi
 }
 
        fi
 }
 
+Warn_user ()
+{
+       if [ -z "${SNAP_RESYNC_STRING}" ]
+       then
+               case ${SNAP_TYPE} in
+                       cpio|ext2|ext3|ext4)
+                               echo "Please move ${DEST} (if is not already in it)" > /dev/null 1>&2
+                               echo "in a supported writable partition (e.g ext3, vfat)." > /dev/null 1>&2
+                               ;;
+
+                       squashfs)
+                               echo "To use ${DEST} you need to rebuild your media or add it" > /dev/null 1>&2
+                               echo "to your multisession disc under the \"/live\" directory." > /dev/null 1>&2
+                               ;;
+
+                       jffs2)
+                               echo "Please cat or flashcp ${DEST} to your partition in order to start using it." > /dev/null 1>&2
+                               ;;
+               esac
+
+               if grep -qv persistence /proc/cmdline
+               then
+                       echo "Remember to boot this live system with \"persistence\" specified at boot prompt." > /dev/null 1>&2
+               fi
+       fi
+}
+
 Main ()
 {
        Parse_args "${@}"
 Main ()
 {
        Parse_args "${@}"
@@ -320,6 +564,7 @@ Main ()
        trap 'Clean' EXIT
        Mount_device
        Do_snapshot
        trap 'Clean' EXIT
        Mount_device
        Do_snapshot
+       Warn_user
 }
 
 Main "${@:-}"
 }
 
 Main "${@:-}"