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