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