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