Adding debian version 2.0~a1-1.
[live-boot-grml.git] / bin / live-snapshot
1 #!/bin/sh
2
3 # live-snapshot - utility to manage Debian Live systems snapshots
4 #
5 #   This program mounts a device (fallback to /tmpfs under $MOUNTP
6 #   and saves the /live/cow (or a different directory) filesystem in it
7 #   for reuse in another live-boot session.
8 #   Look at the manpage for more informations.
9 #
10 # Copyright (C) 2006-2008 Marco Amadori <marco.amadori@gmail.com>
11 # Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>
12 #
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.
17 #
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.
22 #
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/>.
25 #
26 # On Debian systems, the complete text of the GNU General Public License
27 # can be found in /usr/share/common-licenses/GPL-3 file.
28
29 # declare here two vars from /etc/live.conf because of "set -u"
30 ROOTSNAP=""
31 HOMESNAP=""
32
33 if [ -n "${LIVE_SNAPSHOT_CHECK_UNBOUND}" ]
34 then
35         set -eu
36 else
37         set -e
38 fi
39
40 . /usr/share/initramfs-tools/scripts/live-helpers
41
42 LIVE_CONF="/etc/live.conf"
43 . "${LIVE_CONF}"
44
45 export USERNAME USERFULLNAME HOSTNAME
46
47 EXECUTABLE="${0}"
48 PROGRAM=$(basename "${EXECUTABLE}")
49
50 # Needs to be available at run and reboot time
51 SAFE_TMPDIR="/live"
52
53 # Permits multiple runs
54 MOUNTP="$(mktemp -d -p ${SAFE_TMPDIR} live-snapshot-mnt.XXXXXX)"
55 DEST="${MOUNTP}/live-sn.cpio.gz"
56 DEF_SNAP_COW="/live/cow"
57 TMP_FILELIST="${PROGRAM}.list"
58
59 # Command line defaults and declarations
60 SNAP_COW="${DEF_SNAP_COW}"
61 SNAP_DEV=""
62 SNAP_MNT=""
63 SNAP_OUTPUT=""
64 SNAP_RESYNC_STRING=""
65 SNAP_TYPE="cpio"
66 SNAP_LIST="/etc/live-snapshot.list"
67 EXCLUDE_LIST="/etc/live-snapshot.exclude_list"
68
69 Error ()
70 {
71         echo "${PROGRAM}: error:" ${@}
72         exit 1
73 }
74
75 panic ()
76 {
77         Error ${@}
78 }
79
80 Header ()
81 {
82         echo "${PROGRAM} - utility to perform snapshots of Debian Live systems"
83         echo
84         echo "usage: ${PROGRAM} [-c|--cow DIRECTORY] [-d|--device DEVICE] [-o|--output FILE] [-t|--type TYPE]"
85         echo "       ${PROGRAM} [-r|--resync-string STRING]"
86         echo "       ${PROGRAM} [-f|--refresh]"
87         echo "       ${PROGRAM} [-h|--help]"
88         echo "       ${PROGRAM} [-u|--usage]"
89         echo "       ${PROGRAM} [-v|--version]"
90 }
91
92 Help ()
93 {
94         Header
95
96         echo
97         echo "Options:"
98         echo "  -c, --cow: copy on write directory (default: ${SNAP_COW})."
99         echo "  -d, --device: output snapshot device (default: ${SNAP_DEV:-auto})."
100         echo "  -o, --output: output image file (default: ${DEST})."
101         echo "  -r, --resync-string: internally used to resync previous made snapshots."
102         echo "  -f, --refresh: try to sync a running snapshot."
103         echo "  -t, --type: snapshot filesystem type. Options: \"squashfs\", \"ext2\", \"ext3\", \"ext4\", \"jffs2\" or \"cpio\".gz archive (default: ${SNAP_TYPE})"
104         echo
105         echo "Look at live-snapshot(1) man page for more information."
106
107         exit 0
108 }
109
110 Usage ()
111 {
112         Header
113
114         echo
115         echo "Try \"${PROGRAM} --help\" for more information."
116
117         exit 0
118 }
119
120 Version ()
121 {
122         echo "${PROGRAM}"
123         echo
124         echo "Copyright (C) 2006 Marco Amadori <marco.amadori@gmail.com>"
125         echo "Copyright (C) 2008 Chris Lamb <chris@chris-lamb.co.uk>"
126         echo
127         echo "This program is free software; you can redistribute it and/or modify"
128         echo "it under the terms of the GNU General Public License as published by"
129         echo "the Free Software Foundation; either version 2 of the License, or"
130         echo "(at your option) any later version."
131         echo
132         echo "This program is distributed in the hope that it will be useful,"
133         echo "but WITHOUT ANY WARRANTY; without even the implied warranty of"
134         echo "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"
135         echo "GNU General Public License for more details."
136         echo
137         echo "You should have received a copy of the GNU General Public License"
138         echo "along with this program; if not, write to the Free Software"
139         echo "Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA"
140         echo
141         echo "On Debian systems, the complete text of the GNU General Public License"
142         echo "can be found in /usr/share/common-licenses/GPL-2 file."
143         echo
144         echo "Homepage: <http://debian-live.alioth.debian.org/>"
145
146         exit 0
147 }
148
149 Try_refresh ()
150 {
151         FOUND=""
152         if [ -n "${ROOTSNAP}" ]; then
153                 "${EXECUTABLE}" --resync-string="${ROOTSNAP}"
154                 FOUND="Yes"
155         fi
156
157         if [ -n "${HOMESNAP}" ]; then
158                 "${EXECUTABLE}" --resync-string="${HOMESNAP}"
159                 FOUND="Yes"
160         fi
161
162         if [ -z "${FOUND}" ]
163         then
164                 echo "No autoconfigured snapshots found at boot;" > /dev/null 1>&2
165                 echo "(no resync string in ${LIVE_CONF})." > /dev/null 1>&2
166                 exit 1
167         fi
168 }
169
170 Parse_args ()
171 {
172         # Parse command line
173         ARGS="${*}"
174         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})"
175
176         eval set -- "${ARGUMENTS}"
177
178         while true
179         do
180                 case "${1}" in
181                         -c|--cow)
182                                 SNAP_COW="${2}"
183                                 shift 2
184                                 ;;
185
186                         -d|--device)
187                                 SNAP_DEV="${2}"
188                                 shift 2
189                                 ;;
190
191                         -o|--output)
192                                 SNAP_OUTPUT="${2}"
193                                 shift 2
194                                 ;;
195
196                         -t|--type)
197                                 SNAP_TYPE="${2}"
198                                 shift 2
199                                 ;;
200
201                         -r|--resync-string)
202                                 SNAP_RESYNC_STRING="${2}"
203                                 break
204                                 ;;
205
206                         -f|--refresh)
207                                 Try_refresh
208                                 exit 0
209                                 ;;
210
211                         -h|--help)
212                                 Help
213                                 ;;
214
215                         -u|--usage)
216                                 Usage
217                                 ;;
218
219                         -v|--version)
220                                 Version
221                                 ;;
222
223                         --)
224                                 shift
225                                 break
226                                 ;;
227
228                         *)
229                                 Error "internal error."
230                                 ;;
231
232                 esac
233         done
234 }
235
236 Defaults ()
237 {
238         # Parse resync string
239         if [ -n "${SNAP_RESYNC_STRING}" ]
240         then
241                 SNAP_COW=$(echo "${SNAP_RESYNC_STRING}" | sed -e 's|^/root\([^:.]*\).*$|'"${DEF_SNAP_COW}"'\1|')
242                 SNAP_DEV=$(echo "${SNAP_RESYNC_STRING}" | cut -f2 -d ':')
243                 SNAP_MNT=$(echo "${SNAP_RESYNC_STRING}" | cut -f3 -d ':')
244                 DEST="${MOUNTP}/${SNAP_MNT}"
245
246                 case "${SNAP_MNT}" in
247                         *.cpio.gz)
248                                 SNAP_TYPE="cpio"
249                                 ;;
250
251                         *.squashfs)
252                                 SNAP_TYPE="squashfs"
253                                 ;;
254
255                         *.jffs2)
256                                 SNAP_TYPE="jffs2"
257                                 ;;
258
259                         *.ext2|*.ext3)
260                                 SNAP_TYPE="ext2"
261                                 ;;
262
263                         "")
264                                 SNAP_TYPE="whole_partition"
265                                 ;;
266
267                         *.ext4)
268                                 SNAP_TYPE="ext4"
269                                 ;;
270
271                         *)
272                                 Error "unrecognized resync string"
273                                 ;;
274                 esac
275         elif [ -z "${SNAP_OUTPUT}" ]
276         then
277                 # Set target file based on image
278                 case "${SNAP_TYPE}" in
279                         cpio)
280                                 DEST="${MOUNTP}/live-sn.cpio.gz"
281                                 ;;
282
283                         squashfs|jffs2|ext2)
284                                 DEST="${MOUNTP}/live-sn.${SNAP_TYPE}"
285                                 ;;
286
287                         ext3)
288                                 DEST="${MOUNTP}/live-sn.ext2"
289                                 ;;
290
291                         ext4)
292                                 DEST="${MOUNTP}/live-sn.ext4"
293                                 ;;
294                 esac
295         else
296                 DEST="${SNAP_OUTPUT}"
297         fi
298 }
299
300 Validate_input ()
301 {
302         case "${SNAP_TYPE}" in
303                 cpio|squashfs|jffs2|ext2|ext3|ext4|whole_partition)
304                         ;;
305
306                 *)
307                         Error "invalid filesystem type \"${SNAP_TYPE}\""
308                         ;;
309         esac
310
311         if [ ! -d "${SNAP_COW}" ]
312         then
313                 Error "${SNAP_COW} is not a directory"
314         fi
315
316         if [ "$(id -u)" -ne 0 ]
317         then
318                 Error "you are not root"
319         fi
320 }
321
322 Mount_device ()
323 {
324         case "${SNAP_DEV}" in
325                 "")
326                         # create a temp
327                         mount -t tmpfs -o rw tmpfs "${MOUNTP}"
328                         ;;
329
330                 *)
331                         if [ -b "${SNAP_DEV}" ]
332                         then
333                                 try_mount "${SNAP_DEV}" "${MOUNTP}" rw
334                         fi
335                         ;;
336         esac
337 }
338
339 Entry_is_modified ()
340 {
341         # Returns true if file exists and it is also present in "cow" directory
342         # This means it is modified in respect to read-only media, so it deserve
343         # to be saved
344
345         entry="${1}"
346
347         if [ -e "${entry}" ] || [ -L "${entry}" ]
348         then
349                 if [ -e "${SNAP_COW}/${entry}" ] || [ -L "${SNAP_COW}/${entry}" ]
350                 then
351                         return 0
352                 fi
353         fi
354         return 1
355 }
356
357 Do_filelist ()
358 {
359         # BUGS: does not handle deleted files yet
360         TMP_FILELIST=$1
361
362         if [ -f "${SNAP_LIST}" ]
363         then
364                 # if SNAP_COW == /live/cow/home, SNAP_RW = /home
365                 SNAP_RW=$(echo "${SNAP_COW}" | sed -e "s|${DEF_SNAP_COW}||g")
366                 if [ -z "${SNAP_RW}" ]
367                 then
368                         SNAP_RW="/"
369                 fi
370
371                 cd "${SNAP_RW}"
372                 # Generate include list removing empty and commented lines
373                 # and transforming paths to relatives
374                 for entry in $(sed -e '/^ *$/d' -e '/^#.*$/d' -e 's#^.*$#./&#' -e 's#/\+#/#g' "${SNAP_LIST}")
375                 do
376                         if [ -d "${entry}" ]
377                         then
378                                 find "${entry}" | while read line
379                                 do
380                                         if Entry_is_modified "${line}"
381                                         then
382                                                 printf "%s\000" "${line}" >> "${TMP_FILELIST}"
383                                         fi
384                                 done
385                         elif Entry_is_modified "${entry}"
386                         then
387                                 # if file exists and it is modified
388                                 printf "%s\000" "${entry}" >> "${TMP_FILELIST}"
389                         fi
390                 done
391                 cd "${OLDPWD}"
392
393                 # echo Working dir
394                 echo "${SNAP_RW}"
395         else
396                 cd "${SNAP_COW}"
397                 # removing whiteouts from list
398                 find . -path '*.wh.*' -prune -o -print0 >> "${TMP_FILELIST}"
399                 cd "${OLDPWD}"
400                 # echo Working dir
401                 echo "${SNAP_COW}"
402         fi
403 }
404
405 Do_snapshot ()
406 {
407         TMP_FILELIST=$(mktemp -p "${SAFE_TMPDIR}" "${TMP_FILELIST}.XXXXXX")
408
409         case "${SNAP_TYPE}" in
410                 squashfs)
411                         echo ".${TMP_FILELIST}" > "${TMP_FILELIST}"
412                         # Removing whiteheads of unionfs
413                         cd "${SNAP_COW}"
414                         find . -name '*.wh.*' >> "${TMP_FILELIST}"
415
416                         if [ -e "${EXCLUDE_LIST}" ]
417                         then
418                                 # Add explicitly excluded files
419                                 grep -v '^#.*$' "${EXCLUDE_LIST}" | grep -v '^ *$' >> "${TMP_FILELIST}"
420                         fi
421
422                         cd "${OLDPWD}"
423                         mksquashfs "${SNAP_COW}" "${DEST}" -ef "${TMP_FILELIST}"
424                         ;;
425
426                 cpio|whole_partition)
427                         if [ "${SNAP_TYPE}" = "cpio" ]
428                         then
429                                 COPY_CMD="cpio --quiet -o0 -H newc | gzip -9c > ${DEST}"
430                         else
431                                 COPY_CMD="cpio --quiet -pumd0 ${DEST}/"
432                         fi
433
434                         WORKING_DIR=$(Do_filelist "${TMP_FILELIST}")
435                         cd "${WORKING_DIR}"
436                         if [ -e "${EXCLUDE_LIST}" ]
437                         then
438                                 # Convert \0 to \n and tag existing (rare but possible) \n in filenames,
439                                 # this to let grep -F -v do a proper work in filtering out
440                                 cat "${TMP_FILELIST}" | \
441                                         tr '\n' '\1' | \
442                                         tr '\0' '\n' | \
443                                         grep -F -v -f "${EXCLUDE_LIST}" | \
444                                         tr '\n' '\0' | \
445                                         tr '\1' '\n' | \
446                                         eval $COPY_CMD || exit 1
447                         else
448                                 cat "${TMP_FILELIST}" | \
449                                         eval $COPY_CMD || exit 1
450                         fi
451                         cd "${OLDPWD}"
452                         ;;
453
454                 # ext2|ext3|ext4 and jffs2 does not easily support an exclude list; files
455                 # should be copied to another directory in order to filter content
456                 ext2|ext3|ext4)
457                         DU_DIM="$(du -ks ${SNAP_COW} | cut -f1)"
458                         REAL_DIM="$(expr ${DU_DIM} + ${DU_DIM} / 20)" # Just 5% more to be sure, need something more sophistcated here...
459                         genext2fs --size-in-blocks=${REAL_DIM} --reserved-percentage=0 --root="${SNAP_COW}" "${DEST}"
460                         ;;
461
462                 jffs2)
463                         mkfs.jffs2 --root="${SNAP_COW}" --output="${DEST}"
464                         ;;
465         esac
466
467         if [ -f "${TMP_FILELIST}" ]
468         then
469                 rm -f "${TMP_FILELIST}"
470         fi
471 }
472
473 Clean ()
474 {
475         if [ -z "${SNAP_RESYNC_STRING}" ] && echo "${DEST}" | grep -q "${MOUNTP}"
476         then
477                 echo "${DEST} is present on ${MOUNTP}, therefore no automatic unmounting the latter." > /dev/null 1>&2
478         else
479                 umount "${MOUNTP}"
480                 rmdir "${MOUNTP}"
481         fi
482 }
483
484 Warn_user ()
485 {
486         if [ -z "${SNAP_RESYNC_STRING}" ]
487         then
488                 case ${SNAP_TYPE} in
489                         cpio|ext2|ext3|ext4)
490                                 echo "Please move ${DEST} (if is not already in it)" > /dev/null 1>&2
491                                 echo "in a supported writable partition (e.g ext3, vfat)." > /dev/null 1>&2
492                                 ;;
493
494                         squashfs)
495                                 echo "To use ${DEST} you need to rebuild your media or add it" > /dev/null 1>&2
496                                 echo "to your multisession disc under the \"/live\" directory." > /dev/null 1>&2
497                                 ;;
498
499                         jffs2)
500                                 echo "Please cat or flashcp ${DEST} to your partition in order to start using it." > /dev/null 1>&2
501                                 ;;
502                 esac
503
504                 if grep -qv persistent /proc/cmdline
505                 then
506                         echo "Remember to boot this live system with \"persistent\" specified at boot prompt." > /dev/null 1>&2
507                 fi
508         fi
509 }
510
511 Main ()
512 {
513         Parse_args "${@}"
514         Defaults
515         Validate_input
516         trap 'Clean' EXIT
517         Mount_device
518         Do_snapshot
519         Warn_user
520 }
521
522 Main "${@:-}"