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