From e46bc9de8531efdc8232a08286fcf6d3fe5de3a1 Mon Sep 17 00:00:00 2001 From: Eric Hameleers Date: Fri, 29 Jan 2016 12:16:29 +0100 Subject: Add support for persistence using a loop-mounted container file. This adds an option to store persistent data in a container file instead of a directory in the USB stick's Linux filesystem. Theoretically, this would allow the Slackware Live files to be copied to a VFAT partition on a stick without erasing it, as long as the stick is made bootable and the Live kernel & initrd are added to the boot menu. To create a USB stick from the Live ISO which uses a persistence file instead of a persistence directory, run the 'iso2usb.sh' script with a new parameter: -P|--persistfile Use a 'persistence' container file instead of a directory (for use on FAT filesystem). The following example creates a 750 MB LUKS-encrypted container file 'slhome.img' which will contain /home ; as well as a file '/persistence.img' in the root of the USB's Linux partition which will be used to store the Live OS' persistent data: ./iso2usb.sh -i slackware64-live-current.iso -o /dev/sdX -c 750M -P NOTE: The persistence container file will be created as a 'sparse' file which starts as an empty file not using up any disk space, but is allowed to grow dynamically to consume a maximum value of 90% of the free disk space on the Linux partition of the USB stick. --- iso2usb.sh | 119 ++++++++++++++++++++++++++++++++++++++++++------------------- liveinit | 103 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 153 insertions(+), 69 deletions(-) diff --git a/iso2usb.sh b/iso2usb.sh index e9c27dd..87e32e7 100644 --- a/iso2usb.sh +++ b/iso2usb.sh @@ -27,9 +27,13 @@ set -e # Set to '1' if you want to ignore all warnings: FORCE=0 -# By default, we use 'persistence' as the name of the persistence directory: +# By default, we use 'persistence' as the name of the persistence directory, +# or 'persistence.img' as the name of the persistence container: PERSISTENCE="persistence" +# Default persistence type is a directory: +PERSISTTYPE="dir" + # Set to '1' if the script should not ask any questions: UNATTENDED=0 @@ -49,7 +53,7 @@ LODEV="" IMGDIR="" EFIMNT="" ISOMNT="" -LKSMNT="" +CNTMNT="" USBMNT="" # @@ -72,7 +76,7 @@ cleanup() { fi [ -n "${EFIMNT}" ] && ( /sbin/umount -f ${EFIMNT} 2>/dev/null; rmdir $EFIMNT ) [ -n "${ISOMNT}" ] && ( /sbin/umount -f ${ISOMNT} 2>/dev/null; rmdir $ISOMNT ) - [ -n "${LKSMNT}" ] && ( /sbin/umount -f ${LKSMNT} 2>/dev/null; rmdir $LKSMNT ) + [ -n "${CNTMNT}" ] && ( /sbin/umount -f ${CNTMNT} 2>/dev/null; rmdir $CNTMNT ) [ -n "${USBMNT}" ] && ( /sbin/umount -f ${USBMNT} 2>/dev/null; rmdir $USBMNT ) [ -n "${IMGDIR}" ] && ( rm -rf $IMGDIR ) set -e @@ -102,7 +106,8 @@ cat < Add seconds wait time to initialize USB. - +# -P|--persistfile Use a 'persistence' container file instead of +# a directory (for use on FAT filesystem). # # Examples: # @@ -160,6 +165,8 @@ create_container() { CNTPART=$1 CNTSIZE=$2 CNTBASE=$3 + CNTENCR=$4 # 'none' or 'luks' + CNTUSED=$5 # '/home' or 'persistence' # Determine size of the target partition (in MB), and the free space: PARTSIZE=$(df -P -BM ${CNTPART} |tail -1 |tr -s '\t' ' ' |cut -d' ' -f2) @@ -195,25 +202,28 @@ create_container() { exit 1 fi - # Create an empty container file (allow for previously created ones): - for III in $(seq 1 9); do - if [ ! -f $USBMNT/${CNTBASE}0${III} ]; then - break + # Create an empty container file (re-use previously created one): + if [ -f $USBMNT/${CNTBASE}.img ]; then + CNTFILE="${CNTBASE}.img" + CNTSIZE=$(( $(du -sk ${CNTFILE}) / 1024 )) + if [ $UNATTENDED -eq 0 ]; then + echo "*** File '${CNTFILE}' already exists (size ${CNTSIZE} MB). ***" + echo "*** If you do not want to re-use it for '$CNTUSED', ***" + echo "*** then press CONTROL-C now and rename that file! ***" + read -p "Else press ENTER to continue: " JUNK + # OK... the user was sure about the file... fi - done - if [ $III -eq 9 ]; then - echo "*** You already have NINE container files? Please clean up first." - exit 1 else - echo "--- Creating ${CNTSIZE}MB container file using 'dd if=/dev/urandom', patience please..." - dd if=/dev/urandom of=$USBMNT/${CNTBASE}0${III}.img bs=1M count=$CNTSIZE - CNTFILE="${CNTBASE}0${III}.img" + echo "--- Creating ${CNTSIZE} MB container file using 'dd if=/dev/urandom', patience please..." + CNTFILE="${CNTBASE}.img" + # Create a sparse file (not allocating any space yet): + dd of=$USBMNT/${CNTFILE} bs=1M count=0 seek=$CNTSIZE fi # Setup a loopback device that we can use with cryptsetup: LODEV=$(losetup -f) - losetup $LODEV $USBMNT/${CNTBASE}0${III}.img - if [ $DOLUKS -eq 1 ]; then + losetup $LODEV $USBMNT/${CNTFILE} + if [ "${CNTENCR}" = "luks" ]; then # Format the loop device with LUKS: echo "--- Encrypting the container file with LUKS; enter 'YES' and a passphrase..." cryptsetup -y luksFormat $LODEV @@ -221,31 +231,46 @@ create_container() { echo "--- Unlocking the LUKS container requires your passphrase again..." cryptsetup luksOpen $LODEV ${CNTBASE} CNTDEV=/dev/mapper/${CNTBASE} + # Now we allocate blocks for the LUKS device. We write encrypted zeroes, + # so that the file looks randomly filled from the outside. + # Take care not to write more bytes than the internal size of the container: + CNTIS=$(( $(lsblk -b -n -o SIZE $(readlink -f ${CNTDEV})) / 512)) + dd if=/dev/zero of=${CNTDEV} bs=512 count=${CNTIS} || true else CNTDEV=$LODEV + # Un-encrypted container files remain sparse. fi + # Format the now available block device with a linux fs: mkfs.ext4 ${CNTDEV} # Tune the ext4 filesystem: tune2fs -m 0 -c 0 -i 0 ${CNTDEV} - # Create a mount point for the unlocked container: - LKSMNT=$(mktemp -d -p /mnt -t alienlks.XXXXXX) - if [ ! -d $LKSMNT ]; then - echo "*** Failed to create a temporary mount point for the LUKS container!" - exit 1 - else - chmod 711 $LKSMNT + + if [ "${CNTUSED}" != "persistence" ]; then + # Create a mount point for the unlocked container: + CNTMNT=$(mktemp -d -p /mnt -t aliencnt.XXXXXX) + if [ ! -d $CNTMNT ]; then + echo "*** Failed to create temporary mount point for the LUKS container!" + cleanup + exit 1 + else + chmod 711 $CNTMNT + fi + # Copy the original /home (or whatever mount) content into the container: + echo "--- Copying '${CNTUSED}' from LiveOS to container..." + HOMESRC=$(find ${USBMNT} -name "0099-slackware_zzzconf*" |tail -1) + mount ${CNTDEV} ${CNTMNT} + unsquashfs -n -d ${CNTMNT}/temp ${HOMESRC} ${CNTUSED} + mv ${CNTMNT}/temp/${CNTUSED}/* ${CNTMNT}/ + rm -rf ${CNTMNT}/temp + # And clean up after ourselves: + umount ${CNTDEV} + if [ "${CNTENCR}" = "luks" ]; then + cryptsetup luksClose ${CNTBASE} + fi fi - # Copy the original /home content into the container: - echo "--- Copying /home from LiveOS to LUKS container..." - HOMESRC=$(find ${USBMNT} -name "0099-slackware_zzzconf*" |tail -1) - mount ${CNTDEV} ${LKSMNT} - unsquashfs -n -d ${LKSMNT}/temp ${HOMESRC} /home - mv ${LKSMNT}/temp/home/* ${LKSMNT}/ - rm -rf ${LKSMNT}/temp - # And clean up after ourselves: - umount ${CNTDEV} - cryptsetup luksClose ${CNTBASE} + + # Don't forget: losetup -d ${LODEV} } # End of create_container() { @@ -299,6 +324,10 @@ while [ ! -z "$1" ]; do WAIT="$2" shift 2 ;; + -P|--persistfile) + PERSISTTYPE="file" + shift + ;; *) echo "*** Unknown parameter '$1'!" exit 1 @@ -405,6 +434,7 @@ mkdir -p /mnt EFIMNT=$(mktemp -d -p /mnt -t alienefi.XXXXXX) if [ ! -d $EFIMNT ]; then echo "*** Failed to create a temporary mount point for the ISO!" + cleanup exit 1 else chmod 711 $EFIMNT @@ -412,6 +442,7 @@ fi ISOMNT=$(mktemp -d -p /mnt -t alieniso.XXXXXX) if [ ! -d $ISOMNT ]; then echo "*** Failed to create a temporary mount point for the ISO!" + cleanup exit 1 else chmod 711 $ISOMNT @@ -436,6 +467,7 @@ mkdir -p /mnt USBMNT=$(mktemp -d -p /mnt -t alienusb.XXXXXX) if [ ! -d $USBMNT ]; then echo "*** Failed to create a temporary mount point for the USB device!" + cleanup exit 1 else chmod 711 $USBMNT @@ -459,14 +491,26 @@ fi if [ $DOLUKS -eq 1 ]; then # Create LUKS container file: - create_container ${TARGET}3 ${LUKSSIZE} slhome + create_container ${TARGET}3 ${LUKSSIZE} slhome luks /home fi # Add more USB WAIT seconds to the initrd: update_initrd ${USBMNT}/boot/initrd.img -# Create persistence directory: -mkdir -p ${USBMNT}/${PERSISTENCE} +if [ "${PERSISTTYPE}" = "dir" ]; then + # Create persistence directory: + mkdir -p ${USBMNT}/${PERSISTENCE} +elif [ "${PERSISTTYPE}" = "file" ]; then + # Create container file for persistent storage. We create a sparse file + # that will at most eat up 90% of free space. Sparse means, the actual + # block allocation will start small and grows as more changes are written. + # Note: the word "persistence" below is a keyword for create_container: + create_container ${TARGET}3 90% ${PERSISTENCE} none persistence +else + echo "*** Unknown persistence type '${PERSISTTYPE}'!" + cleanup + exit 1 +fi # Use extlinux to make the USB device bootable: echo "--- Making the USB drive '$TARGET' bootable using extlinux..." @@ -501,6 +545,7 @@ if [ -f /usr/share/syslinux/gptmbr.bin ]; then cat /usr/share/syslinux/gptmbr.bin > ${TARGET} else echo "*** Failed to make USB device bootable - 'gptmbr.bin' not found!" + cleanup exit 1 fi diff --git a/liveinit b/liveinit index 84bcc92..c51dc09 100755 --- a/liveinit +++ b/liveinit @@ -209,6 +209,29 @@ if [ "$RESCUE" = "" ]; then # SLACKWARE LIVE - START # # --------------------------------------------------------------------- # + ## Support functions ## + + find_loop() { + # The losetup of busybox is different from the real losetup - watch out! + lodev=$(losetup -f) + if [ -z "$lodev" ]; then + # We exhausted the available loop devices, so create the block device: + for NOD in $(seq 0 64); do + if [ ! -b /dev/loop${NOD} ]; then + mknod -m660 /dev/loop${NOD} b 7 ${NOD} + break + fi + done + lodev=/dev/loop${NOD} + elif [ ! -b $lodev ]; then + # We exhausted the available loop devices, so create the block device: + mknod -m660 $lodev b 7 $(echo $lodev |sed %/dev/loop%%) + fi + echo "$lodev" + } + + ## End support functions ## + # We need a mounted filesystem here to be able to do a switch_root later, # so we create one in RAM: mount -t tmpfs -o defaults none /mnt @@ -344,7 +367,8 @@ if [ "$RESCUE" = "" ]; then OVLWORK=/mnt/live/.ovlwork if [ "$VIRGIN" = "0" ]; then if [ "LIVEFS" != "iso9660" -a -d /mnt/media/${PERSISTENCE} ]; then - # Looks OK, but we need to remount the media in order to write to it: + # Looks OK, but we need to remount the media in order to write + # to the persistence directory: mount -o remount,rw /mnt/media # Try a write... just to be dead sure: if touch /mnt/media/${PERSISTENCE}/.rwtest 2>/dev/null && rm /mnt/media/${PERSISTENCE}/.rwtest 2>/dev/null ; then @@ -353,20 +377,48 @@ if [ "$RESCUE" = "" ]; then UPPERDIR=/mnt/media/${PERSISTENCE} OVLWORK=/mnt/media/.ovlwork fi + elif [ "LIVEFS" != "iso9660" -a -f /mnt/media/${PERSISTENCE}.img ]; then + # Use a container file; the filesystem needs to be writable: + mount -o remount,rw /mnt/media + # Find a free loop device to mount the persistence container file: + prdev=$(find_loop) + prdir=${PERSISTENCE}_$(od -An -N1 -tu1 /dev/urandom |tr -d ' ') + mkdir -p /mnt/live/${prdir} + losetup $prdev /mnt/media/${PERSISTENCE}.img + prfs=$(blkid $prdev |rev |cut -d'"' -f2 |rev) + mount -t $prfs $prdev /mnt/live/${prdir} + if [ $? -ne 0 ]; then + echo "${INITRD}: Failed to mount persistence file '/${PERSISTENCE}.img'." + echo "${INITRD}: Falling back to RAM." + else + echo "${INITRD}: Writing persistent changes to file '/${PERSISTENCE}.img'." + UPPERDIR=/mnt/live/${prdir}/${PERSISTENCE} + OVLWORK=/mnt/live/${prdir}/.ovlwork + fi fi elif [ ! -z "$LUKSVOL" ]; then # Even without persistence, we need to be able to write to the partition # if we are using a LUKS container file: mount -o remount,rw /mnt/media fi + # Create the writable upper directory, plus the workdir which is required # for overlay to function (the two must be in the same POSIX filesystem): - mkdir -p ${UPPERDIR} - mkdir -p ${OVLWORK} + [ ! -d ${UPPERDIR} ] && mkdir -p ${UPPERDIR} + [ ! -d ${OVLWORK} ] && mkdir -p ${OVLWORK} # Create the overlay of readonly and writable directories: mkdir -p /mnt/overlay mount -t overlay -o workdir=${OVLWORK},upperdir=${UPPERDIR},lowerdir=${RODIRS} overlay /mnt/overlay + if [ $? -ne 0 -a "$VIRGIN" = "0" ]; then + # Failed to create the persistent overlay - try without persistence: + echo "${INITRD}: Failed to create persistent overlay, attempting to continue in RAM." + UPPERDIR=/mnt/live/changes + OVLWORK=/mnt/live/.ovlwork + mkdir -p ${UPPERDIR} + mkdir -p ${OVLWORK} + mount -t overlay -o workdir=${OVLWORK},upperdir=${UPPERDIR},lowerdir=${RODIRS} overlay /mnt/overlay + fi debugit @@ -502,41 +554,28 @@ EOPW luksmnt="/home" fi - # The losetup of busybox is different from the real losetup - watch out! - lodev=$(losetup -f) - if [ -z "$lodev" ]; then - # We exhausted the available loop devices, so create the block device: - for NOD in $(seq 0 64); do - if [ ! -b /dev/loop${NOD} ]; then - mknod -m660 /dev/loop${NOD} b 7 ${NOD} - break - fi - done - lodev=/dev/loop${NOD} - elif [ ! -b $lodev ]; then - # We exhausted the available loop devices, so create the block device: - mknod -m660 $lodev b 7 $(echo $lodev |sed %/dev/loop%%) - fi + # Find a free loop device: + lodev=$(find_loop) + losetup $lodev /mnt/media/$luksfil echo "Unlocking LUKS encrypted container '$luksfil' at mount point '$luksmnt'" cryptsetup luksOpen $lodev $luksnam /dev/tty0 2>&1 if [ $? -ne 0 ]; then echo "${INITRD}: Failed to unlock LUKS container '$luksfil'... trouble ahead." + else + # Create the mount directory if it does not exist (unlikely): + mkdir -p /mnt/overlay/$luksmnt + + # Let Slackware mount the unlocked container: + luksfs=$(blkid /dev/mapper/$luksnam |rev |cut -d'"' -f2 |rev) + if ! grep -q /dev/mapper/$luksnam /mnt/overlay/etc/fstab ; then + echo "/dev/mapper/$luksnam $luksmnt $luksfs defaults 1 1" >> /mnt/overlay/etc/fstab + fi + # On shutdown, ensure that the container gets locked again: + if ! grep -q "$luksnam $luksmnt" /mnt/overlay/etc/crypttab ; then + echo "$luksnam $luksmnt" >> /mnt/overlay/etc/crypttab + fi fi - - # Create the directory if it does not exist (unlikely): - mkdir -p /mnt/overlay/$luksmnt - - # Let Slackware mount the unlocked container: - luksfs=$(blkid /dev/mapper/$luksnam |rev |cut -d'"' -f2 |rev) - if ! grep -q /dev/mapper/$luksnam /mnt/overlay/etc/fstab ; then - echo "/dev/mapper/$luksnam $luksmnt $luksfs defaults 1 1" >> /mnt/overlay/etc/fstab - fi - # On shutdown, ensure that the container gets locked again: - if ! grep -q "$luksnam $luksmnt" /mnt/overlay/etc/crypttab ; then - echo "$luksnam $luksmnt" >> /mnt/overlay/etc/crypttab - fi - done fi -- cgit v1.2.3