545942e81edb1643376d062ecbfed41d144587d0
[live-boot-grml.git] / scripts / casper
1 #!/bin/sh
2
3 # set -e
4
5 export PATH=/root/usr/bin:/root/usr/sbin:/root/bin:/root/sbin:/usr/bin:/usr/sbin:/bin:/sbin
6
7 mountpoint=/live_media
8
9 root_persistence="casper-rw"
10 home_persistence="home-rw"
11 root_snapshot_label="casper-sn"
12 home_snapshot_label="home-sn"
13
14 USERNAME="casper"
15 USERFULLNAME="Live session user"
16 HOSTNAME="live"
17 BUILD_SYSTEM="Custom"
18
19 mkdir -p $mountpoint
20
21 [ -f /etc/casper.conf ] && . /etc/casper.conf
22 export USERNAME USERFULLNAME HOSTNAME BUILD_SYSTEM
23
24 . /scripts/casper-helpers
25
26 if [ ! -f /casper.vars ]; then
27     touch /casper.vars
28 fi
29
30 parse_cmdline ()
31 {
32     PRESEEDS=
33     # looking for casper specifics options as kernel parameters
34     for x in $(cat /proc/cmdline); do
35         case $x in
36             userfullname=*)
37                 export USERFULLNAME=${x#userfullname=}
38                 export CASPERCONF="changed"
39                 ;;
40             hostname=*)
41                 export HOSTNAME=${x#hostname=}
42                 export CASPERCONF="changed"
43                 ;;
44             username=*)
45                 export USERNAME=${x#username=}
46                 export CASPERCONF="changed"
47                 ;;
48             netboot*)
49                 export NETBOOT=${x#netboot=} ;;
50             access=*)
51                 export ACCESS=${x#access=} ;;
52             xdebconf)
53                 export XDEBCONF="Yes" ;;
54             toram)
55                 export TORAM="Yes" ;;
56             todisk=*)
57                 export TODISK=${x#todisk=} ;;
58             showmounts)
59                 export SHOWMOUNTS="Yes" ;;
60             persistent)
61                 export PERSISTENT="Yes" ;;
62             nopersistent)
63                 export PERSISTENT="" ;;
64             ip*)
65                 STATICIP=${x#ip=}
66                 if [ "${STATICIP}" == "" ]; then
67                     STATICIP="frommedia"
68                 fi
69                 export STATICIP ;;
70             casper-getty)
71                 export CASPERGETTY=1 ;;
72             bootfrom=*|live-media=*)
73                 export LIVEMEDIA=${x#*=} ;;
74             live-media-encryption=*|encryption=*)
75                 export LIVEMEDIA_ENCRYPTION=${x#*=} ;;
76             live-media-timeout=*)
77                 export LIVEMEDIA_TIMEOUT=${x#live-media-timeout=} ;;
78             live-media-offset=*)
79                 export LIVEMEDIA_OFFSET=${x#live-media-offset=} ;;
80             locale=*|debian-installer/locale=*)
81                 export LOCALE=${x#*=} ;;
82             keyb=*|kbd-chooser/method=*)
83                 export KBD=${x#*=} ;;
84             klayout=*|console-setup/layoutcode=*)
85                 export KLAYOUT=${x#*=} ;;
86             koptions=*)
87                 export KOPTIONS=${x#koptions=} ;;
88             kvariant=*|console-setup/variantcode=*)
89                 export KVARIANT=${x#*=} ;;
90             kmodel=*|console-setup/modelcode=*)
91                 export KMODEL=${x#*=} ;;
92             module=*)
93                 export MODULE=${x#module=} ;;
94             preseed/file=*|file=*)
95                 export LOCATION="${x#*=}" ;;
96             */*=*)
97                 question="${x%%=*}"
98                 value="${x#*=}"
99                 PRESEEDS="${PRESEEDS}\"${question}=${value}\" "
100                 ;;
101             console=*)
102                 export DEFCONSOLE="${x#*=}" ;;
103         esac
104     done
105
106     # sort of compatibility with netboot.h from linux docs
107     if [ -z "${NETBOOT}" ]; then
108         if [ "${ROOT}" == "/dev/nfs" ]; then
109             NETBOOT="nfs"
110             export NETBOOT
111         elif [ "${ROOT}" == "/dev/cifs" ]; then
112             NETBOOT="cifs"
113             export NETBOOT
114         fi
115     fi
116
117     if [ -z "${MODULE}" ]; then
118         MODULE=order
119     fi
120 }
121
122 is_casper_path() {
123     path=$1
124     if [ -d "$path/casper" ]; then
125         if [ "$(echo $path/casper/*.squashfs)" != "$path/casper/*.squashfs" ] ||
126             [ "$(echo $path/casper/*.ext2)" != "$path/casper/*.ext2" ] ||
127             [ "$(echo $path/casper/*.dir)" != "$path/casper/*.dir" ]; then
128             return 0
129         fi
130     fi
131     return 1
132 }
133
134 get_backing_device() {
135     case "$1" in
136         *.squashfs|*.ext2)
137             echo $(setup_loop "$1" "loop" "/sys/block/loop*" '0' "${LIVEMEDIA_ENCRYPTION}")
138             ;;
139         *.dir)
140             echo "directory"
141             ;;
142         *)
143             panic "Unrecognized casper filesystem: $1"
144             ;;
145     esac
146 }
147
148 match_files_in_dir() {
149     # Does any files match pattern $1 ?
150
151     local pattern="$1"
152     if [ "$(echo $pattern)" != "$pattern" ]; then
153         return 0
154     fi
155     return 1
156 }
157
158 mount_images_in_directory() {
159     directory="$1"
160     rootmnt="$2"
161     if match_files_in_dir "$directory/casper/*.squashfs" ||
162         match_files_in_dir "$directory/casper/*.ext2" ||
163         match_files_in_dir "$directory/casper/*.dir"; then
164         setup_unionfs "$directory/casper" "$rootmnt"
165     else
166         :
167     fi
168 }
169
170 is_nice_device() {
171     sysfs_path="${1#/sys}"
172     if /lib/udev/path_id "${sysfs_path}" | grep -E -q "ID_PATH=(usb|pci-[^-]*-[ide|scsi|usb])"; then
173         return 0
174     fi
175     return 1
176 }
177
178 is_supported_fs () {
179     # FIXME: do something better like the scan of supported filesystems
180     fstype="${1}"
181     case ${fstype} in
182         vfat|iso9660|udf|ext2|ext3|ntfs)
183             return 0
184             ;;
185     esac
186     return 1
187 }
188
189 copy_live_to() {
190     copyfrom="${1}"
191     copytodev="${2}"
192     copyto="${copyfrom}_swap"
193
194     size=$(fs_size "" ${copyfrom} "used")
195
196     if [ "${copytodev}" = "ram" ]; then
197         # copying to ram:
198         freespace=$( expr $(awk '/MemFree/{print $2}' /proc/meminfo) + $( cat /proc/meminfo | grep Cached | head -n 1 | awk '/Cached/{print $2}' - ) )
199         mount_options="-o size=${size}k"
200         free_string="memory"
201         fstype="tmpfs"
202         dev="/dev/shm"
203     else
204         # it should be a writable block device
205         if [ -b "${copytodev}" ]; then
206             dev="${copytodev}"
207             free_string="space"
208             fstype=$(get_fstype "${dev}")
209             freespace=$(fs_size "${dev}")
210         else
211             [ "$quiet" != "y" ] && log_warning_msg "${copytodev} is not a block device."
212             return 1
213         fi
214     fi
215     if [ "${freespace}" -lt "${size}" ] ; then
216         [ "$quiet" != "y" ] && log_warning_msg "Not enough free ${free_string} (${freespace}k > ${size}k) to copy live media in ${copytodev}."
217         return 1
218     fi
219
220     # begin copying..
221     mkdir "${copyto}"
222     echo "mount -t ${fstype} ${mount_options} ${dev} ${copyto}"
223     mount -t "${fstype}" ${mount_options} "${dev}" "${copyto}"
224     cp -a ${copyfrom}/* ${copyto} # "cp -a" from busybox also copies hidden files
225     umount ${copyfrom}
226     mount -r -o move ${copyto} ${copyfrom}
227     rmdir ${copyto}
228     return 0
229 }
230
231 do_netmount() {
232     rc=1
233
234     modprobe "${MP_QUIET}" af_packet # For DHCP
235
236     ipconfig ${DEVICE} /tmp/net-${DEVICE}.conf | tee /netboot.config
237
238     if [ "${NFSROOT}" = "auto" ]; then
239         NFSROOT=${ROOTSERVER}:${ROOTPATH}
240     fi
241
242     [ "$quiet" != "y" ] && log_begin_msg "Trying netboot from ${NFSROOT}"
243
244     if [ "${NETBOOT}" != "nfs" ] && do_cifsmount ; then
245         rc=0
246     elif do_nfsmount ; then
247         NETBOOT="nfs"
248         export NETBOOT
249         rc=0
250     fi
251
252     [ "$quiet" != "y" ] && log_end_msg
253     return ${rc}
254 }
255
256 do_nfsmount() {
257     rc=1
258     modprobe "${MP_QUIET}" nfs
259     if [ -z "${NFSOPTS}" ]; then
260         NFSOPTS=""
261     fi
262
263     [ "$quiet" != "y" ] && log_begin_msg "Trying nfsmount -o nolock -o ro ${NFSOPTS} ${NFSROOT} ${mountpoint}"
264     # FIXME: This for loop is an ugly HACK round an nfs bug
265     for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13; do
266         nfsmount -o nolock -o ro ${NFSOPTS} "${NFSROOT}" "${mountpoint}" && rc=0 && break
267         sleep 1
268     done
269     return ${rc}
270 }
271
272 do_cifsmount() {
273     rc=1
274     if [ -x "/sbin/mount.cifs" ]; then
275         if [ -z "${NFSOPTS}" ]; then
276             CIFSOPTS="-ouser=root,password="
277         else
278             CIFSOPTS="${NFSOPTS}"
279         fi
280
281         [ "$quiet" != "y" ] && log_begin_msg "Trying mount.cifs ${NFSROOT} ${mountpoint} ${CIFSOPTS}"
282         modprobe "${MP_QUIET}" cifs
283
284         if mount.cifs "${NFSROOT}" "${mountpoint}" "${CIFSOPTS}" ; then
285             rc=0
286         fi
287     fi
288     return ${rc}
289 }
290
291 do_snap_copy ()
292 {
293     fromdev="${1}"
294     todir="${2}"
295     snap_type="${3}"
296
297     size=$(fs_size "${fromdev}" "" "used")
298
299     if [ -b "${fromdev}" ]; then
300         # look for free mem
301         if [ -n "${HOMEMOUNTED}" -a "${snap_type}" = "HOME" ]; then
302             todev=$(cat /proc/mounts | grep -s " $(base_path ${todir}) " | awk '{print $1}' )
303             freespace=$(df -k  | grep -s ${todev} | awk '{print $4}')
304         else
305             freespace=$( expr $(awk '/MemFree/{print $2}' /proc/meminfo) + $( cat /proc/meminfo | grep Cached | head -n 1 | awk '/Cached/{print $2}' - ))
306         fi
307
308         tomount="/mnt/tmpsnap"
309         if [ ! -d "${tomount}" ] ; then
310             mkdir -p "${tomount}"
311         fi
312
313         fstype=$(get_fstype "${fromdev}")
314         if [ -n "${fstype}" ]; then
315             # Copying stuff...
316             mount -t "${fstype}" -o ro "${fromdev}" "${tomount}"
317             cp -a "${tomount}"/* ${todir}
318             umount "${tomount}"
319         else
320             log_warning_msg "Unrecognized fstype: ${fstype} on ${fromdev}:${snap_type}"
321         fi
322
323         rmdir "${tomount}"
324         if echo ${fromdev} | grep -qs loop; then
325            losetup -d "${fromdev}"
326         fi
327         return 0
328     else
329         return 1
330         [ "$quiet" != "y" ] && log_warning_msg "Unable to find the snapshot ${snap_type} medium"
331     fi
332 }
333
334 try_snap ()
335 {
336     # Look for $snap_label.* in block devices and copy the contents to $snap_mount
337     #   and remember the device and filename for resync on exit in casper.init
338
339     snap_label="${1}"
340     snap_mount="${2}"
341     snap_type="${3}"
342
343     snapdata=$(find_files "${snap_label}.squashfs ${snap_label}.cpio.gz ${snap_label}.ext2")
344     if [ ! -z "${snapdata}" ]; then
345         snapdev="$(echo ${snapdata} | cut -f1 -d ' ')"
346         snapback="$(echo ${snapdata} | cut -f2 -d ' ')"
347         snapfile="$(echo ${snapdata} | cut -f3 -d ' ')"
348         if echo "${snapfile}" | grep -qs '\(squashfs\|ext2\)'; then
349             # squashfs or ext2 snapshot
350             dev=$(get_backing_device "${snapback}/${snapfile}")
351             if ! do_snap_copy "${dev}" "${snap_mount}" "${snap_type}"; then
352                  log_warning_msg "Impossible to include the ${snapfile} Snapshot"
353                  return 1
354             fi
355         else
356             # cpio.gz snapshot
357             if ! (cd "${snap_mount}" && zcat "${snapback}/${snapfile}" | cpio -i -u -d 2>/dev/null) ; then
358                 log_warning_msg "Impossible to include the ${snapfile} Snapshot"
359                 return 1
360             fi
361         fi
362         umount "${snapback}"
363     else
364         dev=$(find_cow_device "${snap_label}")
365         if [ -b ${dev} ]; then
366             if echo "${dev}" | grep -qs loop; then
367                 # strange things happens, user confused?
368                 snaploop=$( losetup ${dev} | awk '{print $3}' | tr -d '()' )
369                 snapfile=$(basename ${snaploop})
370                 snapdev=$(cat /proc/mounts | awk '{print $2,$1}' | grep -es "^$( dirname ${snaploop} )" | cut -f2 -d ' ')
371             else
372                 snapdev="${dev}"
373             fi
374             if ! do_snap_copy "${dev}" "${snap_mount}" "${snap_type}" ; then
375                 log_warning_msg "Impossible to include the ${snap_label} Snapshot"
376                 return 1
377             else
378                 if [ -n "${snapfile}" ]; then
379                      # it was a loop device, user confused
380                      umount ${snapdev}
381                 fi
382             fi
383         else
384             log_warning_msg "Impossible to include the ${snap_label} Snapshot"
385             return 1
386         fi
387     fi
388     echo "export ${snap_type}SNAP="${snap_mount}":${snapdev}:${snapfile}" >> /etc/casper.conf # for resync on reboot/halt
389     return 0
390 }
391
392 setup_unionfs() {
393     image_directory="$1"
394     rootmnt="$2"
395
396     modprobe "${MP_QUIET}" -b unionfs
397
398     # run-init can't deal with images in a subdir, but we're going to
399     # move all of these away before it runs anyway.  No, we're not,
400     # put them in / since move-mounting them into / breaks mono and
401     # some other apps.
402
403     croot="/"
404
405     # Let's just mount the read-only file systems first
406     rofsstring=""
407     rofslist=""
408     if [ "${NETBOOT}" = "nfs" ] ; then
409         roopt="nfsro" # go aroung a bug in nfs-unionfs locking
410     else
411         roopt="ro"
412     fi
413
414     # Read image names from ${MODULE}.lst if it exists
415     if [ -e "${image_directory}/${MODULE}.lst" ]; then
416         for image in $(cat "${image_directory}/${MODULE}.lst"); do
417             image_string="${image_string} ${image_directory}/${image}";
418         done
419     else
420         # If ${MODULE}.lst does not exist, create a list of images
421         for image_type in "ext2" "squashfs" "dir"; do
422             for image in "${image_directory}"/*."${image_type}"; do
423                 if [ -e "${image}" ]; then
424                     image_string="${image_string} ${image}";
425                 fi
426             done
427         done
428         # Now sort the list
429         image_string=$(echo ${image_string} | sed -e 's/ /\n/g' | sort )
430     fi
431
432     mkdir -p "${croot}"
433     for image in ${image_string}; do
434         imagename=$(basename "${image}")
435         if [ -d "${image}" ]; then
436             # it is a plain directory: do nothing
437             rofsstring="${image}=${roopt}:${rofsstring}"
438             rofslist="${image} ${rofslist}"
439         elif [ -f "${image}" ]; then
440             backdev=$(get_backing_device "$image")
441             fstype=$(get_fstype "${backdev}")
442             if [ "${fstype}" = "unknown" ]; then
443                 panic "Unknown file system type on ${backdev} (${image})"
444             fi
445             mkdir -p "${croot}/${imagename}"
446             mount -t "${fstype}" -o ro "${backdev}" "${croot}/${imagename}" || panic "Can not mount $backdev ($image) on ${croot}/${imagename}" && rofsstring="${croot}/${imagename}=${roopt}:${rofsstring}" && rofslist="${croot}/${imagename} ${rofslist}"
447         fi
448     done
449
450     rofsstring=${rofsstring%:}
451
452     mkdir -p /cow
453
454     cowdevice="tmpfs"
455     cow_fstype="tmpfs"
456
457     # Looking for "${root_persistence}" device or file
458     if [ -n "${PERSISTENT}" ]; then
459         cowprobe=$(find_cow_device "${root_persistence}")
460         if [ -b "${cowprobe}" ]; then
461             cowdevice=${cowprobe}
462             cow_fstype=$(get_fstype "${cowprobe}")
463         else
464             [ "$quiet" != "y" ] && log_warning_msg "Unable to find the persistent medium"
465         fi
466     fi
467
468     mount ${cowdevice} -t ${cow_fstype} -o rw /cow || panic "Can not mount $cowdevice on /cow"
469
470     mount -t unionfs -o dirs=/cow=rw:$rofsstring unionfs "$rootmnt" || panic "Unionfs mount failed"
471
472     # Adding other custom mounts
473     if [ -n "${PERSISTENT}" ]; then
474         # directly mount /home
475         # FIXME: add a custom mounts configurable system
476         homecow=$(find_cow_device "${home_persistence}" )
477         if [ -b "${homecow}" ]; then
478             mount -t $(get_fstype "${homecow}") -o rw "${homecow}" "${rootmnt}/home"
479             export HOMEMOUNTED=1 # used to proper calculate free space in do_snap_copy()
480         else
481             [ "$quiet" != "y" ] && log_warning_msg "Unable to find the persistent home medium"
482         fi
483         # Look for other snapshots to copy in
484         try_snap "${root_snapshot_label}" "${rootmnt}" "ROOT"
485         try_snap "${home_snapshot_label}" "${rootmnt}/home" "HOME"
486     fi
487
488     if [ -n "${SHOWMOUNTS}" ]; then
489         for d in ${rofslist}; do
490             mkdir -p "${rootmnt}/casper/${d##*/}"
491             case d in
492                 *.dir) # do nothing # mount -o bind "${d}" "${rootmnt}/casper/${d##*/}"
493                     ;;
494                 *) mount -o move "${d}" "${rootmnt}/casper/${d##*/}"
495                     ;;
496             esac
497         done
498     fi
499
500     # shows cow fs on /cow for use by casper-snapshot
501     mkdir -p "${rootmnt}/cow"
502     mount -o bind /cow "${rootmnt}/cow"
503 }
504
505 check_dev ()
506 {
507     sysdev="${1}"
508     devname="${2}"
509     if [ -z "${devname}" ]; then
510         devname=$(sys2dev "${sysdev}")
511     fi
512
513     if [ -n "${LIVEMEDIA_OFFSET}" ]; then
514         loopdevname=$(setup_loop "${devname}" "loop" "/sys/block/loop*" "${LIVEMEDIA_OFFSET}" '')
515         devname="${loopdevname}" 
516     fi
517
518     fstype=$(get_fstype "${devname}")
519     if is_supported_fs ${fstype}; then
520         mount -t ${fstype} -o ro "${devname}" $mountpoint || continue
521         if is_casper_path $mountpoint; then
522             echo $mountpoint
523             return 0
524         else
525             umount $mountpoint
526         fi
527     fi
528
529     if [ -n "${LIVEMEDIA_OFFSET}" ]; then
530         losetup -d "${loopdevname}"
531     fi
532     return 1
533 }
534
535 find_livefs() {
536     timeout="${1}"
537     # first look at the one specified in the command line
538     if [ ! -z "${LIVEMEDIA}" ]; then
539         if check_dev "null" "${LIVEMEDIA}"; then
540             return 0
541         fi
542     fi
543     # don't start autodetection before timeout has expired
544     if [ -n "${LIVEMEDIA_TIMEOUT}" ]; then
545         if [ "${timeout}" -lt "${LIVEMEDIA_TIMEOUT}" ]; then
546             return 1
547         fi
548     fi
549     # or do the scan of block devices
550     for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -v loop | grep -v ram); do
551         devname=$(sys2dev "${sysblock}")
552         fstype=$(get_fstype "${devname}")
553         if /lib/udev/cdrom_id ${devname} > /dev/null; then
554             if check_dev "null" "${devname}" ; then
555                 return 0
556             fi
557         elif is_nice_device "${sysblock}" ; then
558             for dev in $(subdevices "${sysblock}"); do
559                 if check_dev "${dev}" ; then
560                     return 0
561                 fi
562             done
563         elif [ "${fstype}" = "squashfs" -o \
564                 "${fstype}" = "ext3" -o \
565                 "${fstype}" = "ext2" ]; then
566             # This is an ugly hack situation, the block device has
567             # an image directly on it.  It's hopefully
568             # casper, so take it and run with it.
569             ln -s "${devname}" "${devname}.${fstype}"
570             echo "${devname}.${fstype}"
571             return 0
572         fi
573     done
574     return 1
575 }
576
577 pulsate() {
578     if [ -x /sbin/usplash_write ]; then
579         /sbin/usplash_write "PULSATE"
580     fi
581 }
582
583 set_usplash_timeout() {
584     if [ -x /sbin/usplash_write ]; then
585         /sbin/usplash_write "TIMEOUT 120"
586     fi
587 }
588
589 mountroot() {
590     exec 6>&1
591     exec 7>&2
592     exec > casper.log
593     exec 2>&1
594
595     parse_cmdline
596
597     set_usplash_timeout
598     [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/casper-premount"
599     pulsate
600     run_scripts /scripts/casper-premount
601     [ "$quiet" != "y" ] && log_end_msg
602
603     # Needed here too because some things (*cough* udev *cough*)
604     # changes the timeout
605
606     set_usplash_timeout
607
608     if [ ! -z "${NETBOOT}" ]; then
609         if do_netmount ; then
610             livefs_root="${mountpoint}"
611         else
612             panic "Unable to find a the network rootfs live file system"
613         fi
614     else
615         # Scan local devices for the image
616         for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; do
617             livefs_root=$(find_livefs $i)
618             if [ -n "${livefs_root}" ]; then
619                 break
620             fi
621             sleep 1
622         done
623     fi
624
625     if [ -z "${livefs_root}" ]; then
626         panic "Unable to find a medium containing a live file system"
627     fi
628
629     if [ "${TORAM}" ]; then
630         live_dest="ram"
631     elif [ "${TODISK}" ]; then
632         live_dest="${TODISK}"
633     fi
634     if [ "${live_dest}" ]; then
635         log_begin_msg "Copying live_media to ${live_dest}"
636         copy_live_to "${livefs_root}" "${live_dest}"
637         log_end_msg
638     fi
639
640     mount_images_in_directory "${livefs_root}" "${rootmnt}"
641
642     log_end_msg
643
644     maybe_break casper-bottom
645     [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/casper-bottom"
646
647     pulsate
648     run_scripts /scripts/casper-bottom
649     [ "$quiet" != "y" ] && log_end_msg
650
651     exec 1>&6 6>&-
652     exec 2>&7 7>&-
653     cp casper.log "${rootmnt}/var/log/"
654 }