Consistenly using 'true' instead of 'Yes' or 'yes' when using booleans.
[debian-live-boot/hramrach.git] / bin / live-snapshot
blob46ae597adc895b4a44963a89f47c8b6c17a672ee
1 #!/bin/sh
3 # live-snapshot - utility to manage Debian Live systems snapshots
5 # This program mounts a device (fallback to /tmpfs under $MOUNTP
6 # and saves the /live/overlway (or a different directory) filesystem in it
7 # for reuse in another live-boot session.
8 # Look at the manpage for more informations.
10 # Copyright (C) 2006-2011 Marco Amadori <marco.amadori@gmail.com>
11 # Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # The complete text of the GNU General Public License
27 # can be found in /usr/share/common-licenses/GPL-3 file.
29 # declare here two vars from /etc/live.conf because of "set -u"
30 ROOTSNAP=""
31 HOMESNAP=""
33 if [ -n "${LIVE_SNAPSHOT_CHECK_UNBOUND}" ]
34 then
35 set -eu
36 else
37 set -e
40 ## Begin FIXME: this is an embedded copy of the old 'live-helpers' initramfs script
41 if [ ! -x "/bin/fstype" ]
42 then
43 # klibc not in path -> not in initramfs
44 export PATH="${PATH}:/usr/lib/klibc/bin"
47 # handle upgrade path from old udev (using udevinfo) to
48 # recent versions of udev (using udevadm info)
49 if [ -x /sbin/udevadm ]
50 then
51 udevinfo='/sbin/udevadm info'
52 else
53 udevinfo='udevinfo'
56 old_root_overlay_label="live-rw"
57 old_home_overlay_label="home-rw"
58 custom_overlay_label="custom-ov"
59 root_snapshot_label="live-sn"
60 old_root_snapshot_label="live-sn"
61 home_snapshot_label="home-sn"
62 persistence_list="live-persistence.conf"
64 # include all scripts for the time being until snapshots are either dropped or cleaned up
65 for _SCRIPT in /lib/live/boot/*
67 if [ -e "${_SCRIPT}" ]
68 then
69 . ${_SCRIPT}
71 done
72 ## End FIXME: this is an embedded copy of the old 'live-helpers' initramfs script
74 LIVE_CONF="/etc/live/boot.d/snapshot.conf"
76 if [ -r "${LIVE_CONF}" ]
77 then
78 . "${LIVE_CONF}"
81 export USERNAME USERFULLNAME HOSTNAME
83 EXECUTABLE="${0}"
84 PROGRAM=$(basename "${EXECUTABLE}")
86 # Needs to be available at run and reboot time
87 SAFE_TMPDIR="/live"
89 # Permits multiple runs
90 MOUNTP="$(mktemp -d -p ${SAFE_TMPDIR} live-snapshot-mnt.XXXXXX)"
91 DEST="${MOUNTP}/live-sn.cpio.gz"
92 DEF_SNAP_COW="/live/overlay"
93 TMP_FILELIST="${PROGRAM}.list"
95 # Command line defaults and declarations
96 SNAP_COW="${DEF_SNAP_COW}"
97 SNAP_DEV=""
98 SNAP_MNT=""
99 SNAP_OUTPUT=""
100 SNAP_RESYNC_STRING=""
101 SNAP_TYPE="cpio"
102 SNAP_LIST="/etc/live-snapshot.list"
103 EXCLUDE_LIST="/etc/live-snapshot.exclude_list"
105 Error ()
107 echo "${PROGRAM}: error:" ${@}
108 exit 1
111 panic ()
113 Error ${@}
116 Header ()
118 echo "${PROGRAM} - utility to perform snapshots of Debian Live systems"
119 echo
120 echo "usage: ${PROGRAM} [-c|--cow DIRECTORY] [-d|--device DEVICE] [-o|--output FILE] [-t|--type TYPE]"
121 echo " ${PROGRAM} [-r|--resync-string STRING]"
122 echo " ${PROGRAM} [-f|--refresh]"
123 echo " ${PROGRAM} [-h|--help]"
124 echo " ${PROGRAM} [-u|--usage]"
125 echo " ${PROGRAM} [-v|--version]"
128 Help ()
130 Header
132 echo
133 echo "Options:"
134 echo " -c, --cow: copy on write directory (default: ${SNAP_COW})."
135 echo " -d, --device: output snapshot device (default: ${SNAP_DEV:-auto})."
136 echo " -o, --output: output image file (default: ${DEST})."
137 echo " -r, --resync-string: internally used to resync previous made snapshots."
138 echo " -f, --refresh: try to sync a running snapshot."
139 echo " -t, --type: snapshot filesystem type. Options: \"squashfs\", \"ext2\", \"ext3\", \"ext4\", \"jffs2\" or \"cpio\".gz archive (default: ${SNAP_TYPE})"
140 echo
141 echo "Look at live-snapshot(1) man page for more information."
143 exit 0
146 Usage ()
148 Header
150 echo
151 echo "Try \"${PROGRAM} --help\" for more information."
153 exit 0
156 Version ()
158 echo "${PROGRAM}"
159 echo
160 echo "Copyright (C) 2006-2011 Marco Amadori <marco.amadori@gmail.com>"
161 echo "Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>"
162 echo
163 echo "This program is free software; you can redistribute it and/or modify"
164 echo "it under the terms of the GNU General Public License as published by"
165 echo "the Free Software Foundation; either version 3 of the License, or"
166 echo "(at your option) any later version."
167 echo
168 echo "This program is distributed in the hope that it will be useful,"
169 echo "but WITHOUT ANY WARRANTY; without even the implied warranty of"
170 echo "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"
171 echo "GNU General Public License for more details."
172 echo
173 echo "You should have received a copy of the GNU General Public License"
174 echo "along with this program; if not, write to the Free Software"
175 echo "Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA"
176 echo
177 echo "The complete text of the GNU General Public License"
178 echo "can be found in /usr/share/common-licenses/GPL-3 file."
179 echo
180 echo "Homepage: <http://live.debian.net/>"
182 exit 0
185 Try_refresh ()
187 FOUND=""
188 if [ -n "${ROOTSNAP}" ]; then
189 "${EXECUTABLE}" --resync-string="${ROOTSNAP}"
190 FOUND="true"
193 if [ -n "${HOMESNAP}" ]; then
194 "${EXECUTABLE}" --resync-string="${HOMESNAP}"
195 FOUND="true"
198 if [ -z "${FOUND}" ]
199 then
200 echo "No autoconfigured snapshots found at boot;" > /dev/null 1>&2
201 echo "(no resync string in ${LIVE_CONF})." > /dev/null 1>&2
202 exit 1
206 Parse_args ()
208 # Parse command line
209 ARGS="${*}"
210 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})"
212 eval set -- "${ARGUMENTS}"
214 while true
216 case "${1}" in
217 -c|--cow)
218 SNAP_COW="${2}"
219 shift 2
222 -d|--device)
223 SNAP_DEV="${2}"
224 shift 2
227 -o|--output)
228 SNAP_OUTPUT="${2}"
229 shift 2
232 -t|--type)
233 SNAP_TYPE="${2}"
234 shift 2
237 -r|--resync-string)
238 SNAP_RESYNC_STRING="${2}"
239 break
242 -f|--refresh)
243 Try_refresh
244 exit 0
247 -h|--help)
248 Help
251 -u|--usage)
252 Usage
255 -v|--version)
256 Version
260 shift
261 break
265 Error "internal error."
268 esac
269 done
272 Defaults ()
274 # Parse resync string
275 if [ -n "${SNAP_RESYNC_STRING}" ]
276 then
277 SNAP_COW=$(echo "${SNAP_RESYNC_STRING}" | sed -r -e 's#^([^:]*).*$#'"${DEF_SNAP_COW}"'\1#')
278 SNAP_DEV=$(echo "${SNAP_RESYNC_STRING}" | cut -f2 -d ':')
279 SNAP_MNT=$(echo "${SNAP_RESYNC_STRING}" | cut -f3 -d ':')
280 DEST="${MOUNTP}/${SNAP_MNT}"
282 case "${SNAP_MNT}" in
283 *.cpio.gz)
284 SNAP_TYPE="cpio"
287 *.squashfs)
288 SNAP_TYPE="squashfs"
291 *.jffs2)
292 SNAP_TYPE="jffs2"
295 *.ext2|*.ext3)
296 SNAP_TYPE="ext2"
300 SNAP_TYPE="whole_partition"
303 *.ext4)
304 SNAP_TYPE="ext4"
308 Error "unrecognized resync string"
310 esac
311 elif [ -z "${SNAP_OUTPUT}" ]
312 then
313 # Set target file based on image
314 case "${SNAP_TYPE}" in
315 cpio)
316 DEST="${MOUNTP}/live-sn.cpio.gz"
319 squashfs|jffs2|ext2)
320 DEST="${MOUNTP}/live-sn.${SNAP_TYPE}"
323 ext3)
324 DEST="${MOUNTP}/live-sn.ext2"
327 ext4)
328 DEST="${MOUNTP}/live-sn.ext4"
330 esac
331 else
332 DEST="${SNAP_OUTPUT}"
336 Validate_input ()
338 case "${SNAP_TYPE}" in
339 cpio|squashfs|jffs2|ext2|ext3|ext4|whole_partition)
343 Error "invalid filesystem type \"${SNAP_TYPE}\""
345 esac
347 if [ ! -d "${SNAP_COW}" ]
348 then
349 Error "${SNAP_COW} is not a directory"
352 if [ "$(id -u)" -ne 0 ]
353 then
354 Error "you are not root"
358 Mount_device ()
360 case "${SNAP_DEV}" in
362 # create a temp
363 mount -t tmpfs -o rw tmpfs "${MOUNTP}"
367 if [ -b "${SNAP_DEV}" ]
368 then
369 try_mount "${SNAP_DEV}" "${MOUNTP}" rw
372 esac
375 Entry_is_modified ()
377 # Returns true if file exists and it is also present in "cow" directory
378 # This means it is modified in respect to read-only media, so it deserve
379 # to be saved
381 entry="${1}"
383 if [ -e "${entry}" ] || [ -L "${entry}" ]
384 then
385 if [ -e "${SNAP_COW}/${entry}" ] || [ -L "${SNAP_COW}/${entry}" ]
386 then
387 return 0
390 return 1
393 Do_filelist ()
395 # BUGS: does not handle deleted files yet
396 TMP_FILELIST=$1
398 if [ -f "${SNAP_LIST}" ]
399 then
400 # if SNAP_COW == /live/overlay/home, SNAP_RW = /home
401 SNAP_RW=$(echo "${SNAP_COW}" | sed -e "s|${DEF_SNAP_COW}||g")
402 if [ -z "${SNAP_RW}" ]
403 then
404 SNAP_RW="/"
407 cd "${SNAP_RW}"
408 # Generate include list removing empty and commented lines
409 # and transforming paths to relatives
410 for entry in $(sed -e '/^ *$/d' -e '/^#.*$/d' -e 's#^.*$#./&#' -e 's#/\+#/#g' "${SNAP_LIST}")
412 if [ -d "${entry}" ]
413 then
414 find "${entry}" | while read line
416 if Entry_is_modified "${line}"
417 then
418 printf "%s\000" "${line}" >> "${TMP_FILELIST}"
420 done
421 elif Entry_is_modified "${entry}"
422 then
423 # if file exists and it is modified
424 printf "%s\000" "${entry}" >> "${TMP_FILELIST}"
426 done
427 cd "${OLDPWD}"
429 # echo Working dir
430 echo "${SNAP_RW}"
431 else
432 cd "${SNAP_COW}"
433 # removing whiteouts from list
434 find . -path '*.wh.*' -prune -o -print0 >> "${TMP_FILELIST}"
435 cd "${OLDPWD}"
436 # echo Working dir
437 echo "${SNAP_COW}"
441 Do_snapshot ()
443 TMP_FILELIST=$(mktemp -p "${SAFE_TMPDIR}" "${TMP_FILELIST}.XXXXXX")
444 if [ -e "${EXCLUDE_LIST}" ]
445 then
446 # Create a TMP filelist removing empty lines (grep -f does not like them)
447 # and comments (for speedup and LST)
448 TMP_EXCLUDE_LIST=$(mktemp -p "${SAFE_TMPDIR}" "${PROGRAM}_excludelist.XXXXXX")
449 grep -v '^#.*$' "${EXCLUDE_LIST}" | grep -v '^ *$' > "${TMP_EXCLUDE_LIST}"
452 case "${SNAP_TYPE}" in
453 squashfs)
454 echo ".${TMP_FILELIST}" > "${TMP_FILELIST}"
455 # Removing whiteheads of unionfs
456 cd "${SNAP_COW}"
457 find . -name '*.wh.*' >> "${TMP_FILELIST}"
459 if [ -e "${EXCLUDE_LIST}" ]
460 then
461 # Add explicitly excluded files
462 cat "${TMP_EXCLUDE_LIST}" >> "${TMP_FILELIST}"
465 cd "${OLDPWD}"
466 mksquashfs "${SNAP_COW}" "${DEST}" -ef "${TMP_FILELIST}"
469 cpio|whole_partition)
470 if [ "${SNAP_TYPE}" = "cpio" ]
471 then
472 COPY_CMD="cpio --quiet -o0 -H newc | gzip -9c > ${DEST}"
473 else
474 COPY_CMD="cpio --quiet -pumd0 ${DEST}/"
477 WORKING_DIR=$(Do_filelist "${TMP_FILELIST}")
478 cd "${WORKING_DIR}"
479 if [ -e "${EXCLUDE_LIST}" ]
480 then
482 # Convert \0 to \n and tag existing (rare but possible) \n in filenames,
483 # this to let grep -F -v do a proper work in filtering out
484 cat "${TMP_FILELIST}" | \
485 tr '\n' '\1' | \
486 tr '\0' '\n' | \
487 grep -F -v -f "${TMP_EXCLUDE_LIST}" | \
488 tr '\n' '\0' | \
489 tr '\1' '\n' | \
490 eval $COPY_CMD || exit 1
491 else
492 cat "${TMP_FILELIST}" | \
493 eval $COPY_CMD || exit 1
495 cd "${OLDPWD}"
498 # ext2|ext3|ext4 and jffs2 does not easily support an exclude list; files
499 # should be copied to another directory in order to filter content
500 ext2|ext3|ext4)
501 DU_DIM="$(du -ks ${SNAP_COW} | cut -f1)"
502 REAL_DIM="$(expr ${DU_DIM} + ${DU_DIM} / 20)" # Just 5% more to be sure, need something more sophistcated here...
503 genext2fs --size-in-blocks=${REAL_DIM} --reserved-percentage=0 --root="${SNAP_COW}" "${DEST}"
506 jffs2)
507 mkfs.jffs2 --root="${SNAP_COW}" --output="${DEST}"
509 esac
511 # Remove temporary file lists
512 for filelist in "${TMP_FILELIST}" "${TMP_EXCLUDE_LIST}"
514 if [ -f "${filelist}" ]
515 then
516 rm -f "${filelist}"
518 done
521 Clean ()
523 if [ -z "${SNAP_RESYNC_STRING}" ] && echo "${DEST}" | grep -q "${MOUNTP}"
524 then
525 echo "${DEST} is present on ${MOUNTP}, therefore no automatic unmounting the latter." > /dev/null 1>&2
526 else
527 umount "${MOUNTP}"
528 rmdir "${MOUNTP}"
532 Warn_user ()
534 if [ -z "${SNAP_RESYNC_STRING}" ]
535 then
536 case ${SNAP_TYPE} in
537 cpio|ext2|ext3|ext4)
538 echo "Please move ${DEST} (if is not already in it)" > /dev/null 1>&2
539 echo "in a supported writable partition (e.g ext3, vfat)." > /dev/null 1>&2
542 squashfs)
543 echo "To use ${DEST} you need to rebuild your media or add it" > /dev/null 1>&2
544 echo "to your multisession disc under the \"/live\" directory." > /dev/null 1>&2
547 jffs2)
548 echo "Please cat or flashcp ${DEST} to your partition in order to start using it." > /dev/null 1>&2
550 esac
552 if grep -qv persistence /proc/cmdline
553 then
554 echo "Remember to boot this live system with \"persistence\" specified at boot prompt." > /dev/null 1>&2
559 Main ()
561 Parse_args "${@}"
562 Defaults
563 Validate_input
564 trap 'Clean' EXIT
565 Mount_device
566 Do_snapshot
567 Warn_user
570 Main "${@:-}"