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