Moving out live-helpers from initramfs-tools specifics.
[live-boot-grml.git] / scripts / boot / misc-helpers.sh
1 #!/bin/sh
2
3 is_in_list_separator_helper () {
4         local sep=${1}
5         shift
6         local element=${1}
7         shift
8         local list=${*}
9         echo ${list} | grep -qe "^\(.*${sep}\)\?${element}\(${sep}.*\)\?$"
10 }
11
12 is_in_space_sep_list () {
13         local element=${1}
14         shift
15         is_in_list_separator_helper "[[:space:]]" "${element}" "${*}"
16 }
17
18 is_in_comma_sep_list () {
19         local element=${1}
20         shift
21         is_in_list_separator_helper "," "${element}" "${*}"
22 }
23
24 sys2dev ()
25 {
26         sysdev=${1#/sys}
27         echo "/dev/$($udevinfo -q name -p ${sysdev} 2>/dev/null|| echo ${sysdev##*/})"
28 }
29
30 subdevices ()
31 {
32         sysblock=${1}
33         r=""
34
35         for dev in "${sysblock}"/* "${sysblock}"
36         do
37                 if [ -e "${dev}/dev" ]
38                 then
39                         r="${r} ${dev}"
40                 fi
41         done
42
43         echo ${r}
44 }
45
46 storage_devices()
47 {
48         black_listed_devices="${1}"
49         white_listed_devices="${2}"
50
51         for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -vE "loop|ram|fd")
52         do
53                 fulldevname=$(sys2dev "${sysblock}")
54
55                 if is_in_space_sep_list ${fulldevname} ${black_listed_devices} || \
56                         [ -n "${white_listed_devices}" ] && \
57                         ! is_in_space_sep_list ${fulldevname} ${white_listed_devices}
58                 then
59                         # skip this device entirely
60                         continue
61                 fi
62
63                 for dev in $(subdevices "${sysblock}")
64                 do
65                         devname=$(sys2dev "${dev}")
66
67                         if is_in_space_sep_list ${devname} ${black_listed_devices}
68                         then
69                                 # skip this subdevice
70                                 continue
71                         else
72                                 echo "${devname}"
73                         fi
74                 done
75         done
76 }
77
78 is_supported_fs ()
79 {
80         fstype="${1}"
81
82         # Validate input first
83         if [ -z "${fstype}" ]
84         then
85                 return 1
86         fi
87
88         # Try to look if it is already supported by the kernel
89         if grep -q ${fstype} /proc/filesystems
90         then
91                 return 0
92         else
93                 # Then try to add support for it the gentle way using the initramfs capabilities
94                 modprobe ${fstype}
95                 if grep -q ${fstype} /proc/filesystems
96                 then
97                         return 0
98                 # Then try the hard way if /root is already reachable
99                 else
100                         kmodule="/root/lib/modules/`uname -r`/${fstype}/${fstype}.ko"
101                         if [ -e "${kmodule}" ]
102                         then
103                                 insmod "${kmodule}"
104                                 if grep -q ${fstype} /proc/filesystems
105                                 then
106                                         return 0
107                                 fi
108                         fi
109                 fi
110         fi
111
112         return 1
113 }
114
115 get_fstype ()
116 {
117         /sbin/blkid -s TYPE -o value $1 2>/dev/null
118 }
119
120 where_is_mounted ()
121 {
122         device=${1}
123         # return first found
124         grep -m1 "^${device} " /proc/mounts | cut -f2 -d ' '
125 }
126
127 trim_path () {
128     # remove all unnecessary /:s in the path, including last one (except
129     # if path is just "/")
130     echo ${1} | sed 's|//\+|/|g' | sed 's|^\(.*[^/]\)/$|\1|'
131 }
132
133 what_is_mounted_on ()
134 {
135         local dir="$(trim_path ${1})"
136         grep -m1 "^[^ ]\+ ${dir} " /proc/mounts | cut -d' ' -f1
137 }
138
139 chown_ref ()
140 {
141         local reference="${1}"
142         shift
143         local targets=${@}
144         local owner=$(stat -c %u:%g "${reference}")
145         chown -h ${owner} ${targets}
146 }
147
148 chmod_ref ()
149 {
150         local reference="${1}"
151         shift
152         local targets=${@}
153         local rights=$(stat -c %a "${reference}")
154         chmod ${rights} ${targets}
155 }
156
157 lastline ()
158 {
159         while read lines
160         do
161                 line=${lines}
162         done
163
164         echo "${line}"
165 }
166
167 base_path ()
168 {
169         testpath="${1}"
170         mounts="$(awk '{print $2}' /proc/mounts)"
171         testpath="$(busybox realpath ${testpath})"
172
173         while true
174         do
175                 if echo "${mounts}" | grep -qs "^${testpath}"
176                 then
177                         set -- $(echo "${mounts}" | grep "^${testpath}" | lastline)
178                         echo ${1}
179                         break
180                 else
181                         testpath=$(dirname $testpath)
182                 fi
183         done
184 }
185
186 fs_size ()
187 {
188         # Returns used/free fs kbytes + 5% more
189         # You could pass a block device as ${1} or the mount point as ${2}
190
191         dev="${1}"
192         mountp="${2}"
193         used="${3}"
194
195         if [ -z "${mountp}" ]
196         then
197                 mountp="$(where_is_mounted ${dev})"
198
199                 if [ -z "${mountp}" ]
200                 then
201                         mountp="/mnt/tmp_fs_size"
202
203                         mkdir -p "${mountp}"
204                         mount -t $(get_fstype "${dev}") -o ro "${dev}" "${mountp}" || log_warning_msg "cannot mount -t $(get_fstype ${dev}) -o ro ${dev} ${mountp}"
205
206                         doumount=1
207                 fi
208         fi
209
210         if [ "${used}" = "used" ]
211         then
212                 size=$(du -ks ${mountp} | cut -f1)
213                 size=$(expr ${size} + ${size} / 20 ) # FIXME: 5% more to be sure
214         else
215                 # free space
216                 size="$(df -k | grep -s ${mountp} | awk '{print $4}')"
217         fi
218
219         if [ -n "${doumount}" ]
220         then
221                 umount "${mountp}" || log_warning_msg "cannot umount ${mountp}"
222                 rmdir "${mountp}"
223         fi
224
225         echo "${size}"
226 }
227
228 load_keymap ()
229 {
230         # Load custom keymap
231         if [ -x /bin/loadkeys -a -r /etc/boottime.kmap.gz ]
232         then
233                 loadkeys /etc/boottime.kmap.gz
234         fi
235 }
236
237 setup_loop ()
238 {
239         local fspath=${1}
240         local module=${2}
241         local pattern=${3}
242         local offset=${4}
243         local encryption=${5}
244         local readonly=${6}
245
246         # the output of setup_loop is evaluated in other functions,
247         # modprobe leaks kernel options like "libata.dma=0"
248         # as "options libata dma=0" on stdout, causing serious
249         # problems therefor, so instead always avoid output to stdout
250         modprobe -q -b "${module}" 1>/dev/null
251
252         udevadm settle
253
254         for loopdev in ${pattern}
255         do
256                 if [ "$(cat ${loopdev}/size)" -eq 0 ]
257                 then
258                         dev=$(sys2dev "${loopdev}")
259                         options=''
260
261                         if [ -n "${readonly}" ]
262                         then
263                                 if losetup --help 2>&1 | grep -q -- "-r\b"
264                                 then
265                                         options="${options} -r"
266                                 fi
267                         fi
268
269                         if [ -n "${offset}" ] && [ 0 -lt "${offset}" ]
270                         then
271                                 options="${options} -o ${offset}"
272                         fi
273
274                         if [ -z "${encryption}" ]
275                         then
276                                 losetup ${options} "${dev}" "${fspath}"
277                         else
278                                 # Loop AES encryption
279                                 while true
280                                 do
281                                         load_keymap
282
283                                         echo -n "Enter passphrase for root filesystem: " >&6
284                                         read -s passphrase
285                                         echo "${passphrase}" > /tmp/passphrase
286                                         unset passphrase
287                                         exec 9</tmp/passphrase
288                                         /sbin/losetup ${options} -e "${encryption}" -p 9 "${dev}" "${fspath}"
289                                         error=${?}
290                                         exec 9<&-
291                                         rm -f /tmp/passphrase
292
293                                         if [ 0 -eq ${error} ]
294                                         then
295                                                 unset error
296                                                 break
297                                         fi
298
299                                         echo
300                                         echo -n "There was an error decrypting the root filesystem ... Retry? [Y/n] " >&6
301                                         read answer
302
303                                         if [ "$(echo "${answer}" | cut -b1 | tr A-Z a-z)" = "n" ]
304                                         then
305                                                 unset answer
306                                                 break
307                                         fi
308                                 done
309                         fi
310
311                         echo "${dev}"
312                         return 0
313                 fi
314         done
315
316         panic "No loop devices available"
317 }
318
319 try_mount ()
320 {
321         dev="${1}"
322         mountp="${2}"
323         opts="${3}"
324         fstype="${4}"
325
326         old_mountp="$(where_is_mounted ${dev})"
327
328         if [ -n "${old_mountp}" ]
329         then
330                 if [ "${opts}" != "ro" ]
331                 then
332                         mount -o remount,"${opts}" "${dev}" "${old_mountp}" || panic "Remounting ${dev} ${opts} on ${old_mountp} failed"
333                 fi
334
335                 mount -o bind "${old_mountp}" "${mountp}" || panic "Cannot bind-mount ${old_mountp} on ${mountp}"
336         else
337                 if [ -z "${fstype}" ]
338                 then
339                         fstype=$(get_fstype "${dev}")
340                 fi
341                 mount -t "${fstype}" -o "${opts}" "${dev}" "${mountp}" || \
342                 ( echo "SKIPPING: Cannot mount ${dev} on ${mountp}, fstype=${fstype}, options=${opts}" > boot.log && return 0 )
343         fi
344 }
345
346 mount_persistence_media ()
347 {
348         local device=${1}
349         local probe=${2}
350
351         local backing="/live/persistence/$(basename ${device})"
352
353         mkdir -p "${backing}"
354         local old_backing="$(where_is_mounted ${device})"
355         if [ -z "${old_backing}" ]
356         then
357                 local fstype="$(get_fstype ${device})"
358                 local mount_opts="rw,noatime"
359                 if [ -n "${PERSISTENCE_READONLY}" ]
360                 then
361                         mount_opts="ro,noatime"
362                 fi
363                 if mount -t "${fstype}" -o "${mount_opts}" "${device}" "${backing}" >/dev/null
364                 then
365                         echo ${backing}
366                         return 0
367                 else
368                         [ -z "${probe}" ] && log_warning_msg "Failed to mount persistence media ${device}"
369                         rmdir "${backing}"
370                         return 1
371                 fi
372         elif [ "${backing}" != "${old_backing}" ]
373         then
374                 if mount --move ${old_backing} ${backing} >/dev/null
375                 then
376                         echo ${backing}
377                         return 0
378                 else
379                         [ -z "${probe}" ] && log_warning_msg "Failed to move persistence media ${device}"
380                         rmdir "${backing}"
381                         return 1
382                 fi
383         fi
384         return 0
385 }
386
387 close_persistence_media () {
388         local device=${1}
389         local backing="$(where_is_mounted ${device})"
390
391         if [ -d "${backing}" ]
392         then
393                 umount "${backing}" >/dev/null 2>&1
394                 rmdir "${backing}" >/dev/null 2>&1
395         fi
396
397         if is_active_luks_mapping ${device}
398         then
399                 /sbin/cryptsetup luksClose ${device}
400         fi
401 }
402
403 open_luks_device ()
404 {
405         dev="${1}"
406         name="$(basename ${dev})"
407         opts="--key-file=-"
408         if [ -n "${PERSISTENCE_READONLY}" ]
409         then
410                 opts="${opts} --readonly"
411         fi
412
413         if /sbin/cryptsetup status "${name}" >/dev/null 2>&1
414         then
415                 re="^[[:space:]]*device:[[:space:]]*\([^[:space:]]*\)$"
416                 opened_dev=$(cryptsetup status ${name} 2>/dev/null | grep "${re}" | sed "s|${re}|\1|")
417                 if [ "${opened_dev}" = "${dev}" ]
418                 then
419                         luks_device="/dev/mapper/${name}"
420                         echo ${luks_device}
421                         return 0
422                 else
423                         log_warning_msg "Cannot open luks device ${dev} since ${opened_dev} already is opened with its name"
424                         return 1
425                 fi
426         fi
427
428         load_keymap
429
430         while true
431         do
432                 /lib/cryptsetup/askpass "Enter passphrase for ${dev}: " | \
433                         /sbin/cryptsetup -T 1 luksOpen ${dev} ${name} ${opts}
434
435                 if [ 0 -eq ${?} ]
436                 then
437                         luks_device="/dev/mapper/${name}"
438                         echo ${luks_device}
439                         return 0
440                 fi
441
442                 echo >&6
443                 echo -n "There was an error decrypting ${dev} ... Retry? [Y/n] " >&6
444                 read answer
445
446                 if [ "$(echo "${answer}" | cut -b1 | tr A-Z a-z)" = "n" ]
447                 then
448                         return 2
449                 fi
450         done
451 }
452
453 get_gpt_name ()
454 {
455     local dev="${1}"
456     /sbin/blkid -s PART_ENTRY_NAME -p -o value ${dev} 2>/dev/null
457 }
458
459 is_gpt_device ()
460 {
461     local dev="${1}"
462     [ "$(/sbin/blkid -s PART_ENTRY_SCHEME -p -o value ${dev} 2>/dev/null)" = "gpt" ]
463 }
464
465 probe_for_gpt_name ()
466 {
467         local overlays="${1}"
468         local snapshots="${2}"
469         local dev="${3}"
470
471         local gpt_dev="${dev}"
472         if is_active_luks_mapping ${dev}
473         then
474                 # if $dev is an opened luks device, we need to check
475                 # GPT stuff on the backing device
476                 gpt_dev=$(get_luks_backing_device "${dev}")
477         fi
478
479         if ! is_gpt_device ${gpt_dev}
480         then
481                 return
482         fi
483
484         local gpt_name=$(get_gpt_name ${gpt_dev})
485         for label in ${overlays} ${snapshots}
486         do
487                 if [ "${gpt_name}" = "${label}" ]
488                 then
489                         echo "${label}=${dev}"
490                 fi
491         done
492 }
493
494 probe_for_fs_label ()
495 {
496         local overlays="${1}"
497         local snapshots="${2}"
498         local dev="${3}"
499
500         for label in ${overlays} ${snapshots}
501         do
502                 if [ "$(/sbin/blkid -s LABEL -o value $dev 2>/dev/null)" = "${label}" ]
503                 then
504                         echo "${label}=${dev}"
505                 fi
506         done
507 }
508
509 probe_for_file_name ()
510 {
511         local overlays="${1}"
512         local snapshots="${2}"
513         local dev="${3}"
514
515         local ret=""
516         local backing="$(mount_persistence_media ${dev} probe)"
517         if [ -z "${backing}" ]
518         then
519             return
520         fi
521
522         for label in ${overlays}
523         do
524                 path=${backing}/${PERSISTENCE_PATH}${label}
525                 if [ -f "${path}" ]
526                 then
527                         local loopdev=$(setup_loop "${path}" "loop" "/sys/block/loop*")
528                         ret="${ret} ${label}=${loopdev}"
529                 fi
530         done
531         for label in ${snapshots}
532         do
533                 for ext in squashfs cpio.gz ext2 ext3 ext4 jffs2
534                 do
535                         path="${PERSISTENCE_PATH}${label}.${ext}"
536                         if [ -f "${backing}/${path}" ]
537                         then
538                                 ret="${ret} ${label}=${dev}:${backing}:${path}"
539                         fi
540                 done
541         done
542
543         if [ -n "${ret}" ]
544         then
545                 echo ${ret}
546         else
547                 umount ${backing} > /dev/null 2>&1 || true
548         fi
549 }
550
551 find_persistence_media ()
552 {
553         # Scans devices for overlays and snapshots, and returns a whitespace
554         # separated list of how to use them. Only overlays with a partition
555         # label or file name in ${overlays} are returned, and ditto for
556         # snapshots with labels in ${snapshots}.
557         #
558         # When scanning a LUKS device, the user will be asked to enter the
559         # passphrase; on failure to enter it, or if no persistence partitions
560         # or files were found, the LUKS device is closed.
561         #
562         # For a snapshot file the return value is ${label}=${snapdata}", where
563         # ${snapdata} is the parameter used for try_snap().
564         #
565         # For all other cases (overlay/snapshot partition and overlay file) the
566         # return value is "${label}=${device}", where ${device} a device that
567         # can mount the content. In the case of an overlay file, the device
568         # containing the file will remain mounted as a side-effect.
569         #
570         # No devices in ${black_listed_devices} will be scanned, and if
571         # ${white_list_devices} is non-empty, only devices in it will be
572         # scanned.
573
574         local overlays="${1}"
575         local snapshots="${2}"
576         local white_listed_devices="${3}"
577         local ret=""
578
579         local black_listed_devices="$(what_is_mounted_on /live/image)"
580
581         for dev in $(storage_devices "${black_listed_devices}" "${white_listed_devices}")
582         do
583                 local result=""
584
585                 local luks_device=""
586                 # Check if it's a luks device; we'll have to open the device
587                 # in order to probe any filesystem it contains, like we do
588                 # below. activate_custom_mounts() also depends on that any luks
589                 # device already has been opened.
590                 if is_in_comma_sep_list luks ${PERSISTENCE_ENCRYPTION} && \
591                    is_luks_partition ${dev}
592                 then
593                         if luks_device=$(open_luks_device "${dev}")
594                         then
595                                 dev="${luks_device}"
596                         else
597                                 # skip $dev since we failed/chose not to open it
598                                 continue
599                         fi
600                 elif ! is_in_comma_sep_list none ${PERSISTENCE_ENCRYPTION}
601                 then
602                         # skip $dev since we don't allow unencrypted storage
603                         continue
604                 fi
605
606                 # Probe for matching GPT partition names or filesystem labels
607                 if is_in_comma_sep_list filesystem ${PERSISTENCE_STORAGE}
608                 then
609                         result=$(probe_for_gpt_name "${overlays}" "${snapshots}" ${dev})
610                         if [ -n "${result}" ]
611                         then
612                                 ret="${ret} ${result}"
613                                 continue
614                         fi
615
616                         result=$(probe_for_fs_label "${overlays}" "${snapshots}" ${dev})
617                         if [ -n "${result}" ]
618                         then
619                                 ret="${ret} ${result}"
620                                 continue
621                         fi
622                 fi
623
624                 # Probe for files with matching name on mounted partition
625                 if is_in_comma_sep_list file ${PERSISTENCE_STORAGE}
626                 then
627                         result=$(probe_for_file_name "${overlays}" "${snapshots}" ${dev})
628                         if [ -n "${result}" ]
629                         then
630                                 ret="${ret} ${result}"
631                                 continue
632                         fi
633                 fi
634
635                 # Close luks device if it isn't used
636                 if [ -z "${result}" ] && [ -n "${luks_device}" ] && \
637                    is_active_luks_mapping "${luks_device}"
638                 then
639                         /sbin/cryptsetup luksClose "${luks_device}"
640                 fi
641         done
642
643         if [ -n "${ret}" ]
644         then
645                 echo ${ret}
646         fi
647 }
648
649 get_mac ()
650 {
651         mac=""
652
653         for adaptor in /sys/class/net/*
654         do
655                 status="$(cat ${adaptor}/iflink)"
656
657                 if [ "${status}" -eq 2 ]
658                 then
659                         mac="$(cat ${adaptor}/address)"
660                         mac="$(echo ${mac} | sed 's/:/-/g' | tr '[a-z]' '[A-Z]')"
661                 fi
662         done
663
664         echo ${mac}
665 }
666
667 is_luks_partition ()
668 {
669         device="${1}"
670         /sbin/cryptsetup isLuks "${device}" 1>/dev/null 2>&1
671 }
672
673 is_active_luks_mapping ()
674 {
675         device="${1}"
676         /sbin/cryptsetup status "${device}" 1>/dev/null 2>&1
677 }
678
679 get_luks_backing_device () {
680         device=${1}
681         cryptsetup status ${device} 2> /dev/null | \
682                 awk '{if ($1 == "device:") print $2}'
683 }
684
685 removable_dev ()
686 {
687         output_format="${1}"
688         want_usb="${2}"
689         ret=
690
691         for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -vE "/(loop|ram|dm-|fd)")
692         do
693                 dev_ok=
694                 if [ "$(cat ${sysblock}/removable)" = "1" ]
695                 then
696                         if [ -z "${want_usb}" ]
697                         then
698                                 dev_ok="yes"
699                         else
700                                 if readlink ${sysblock} | grep -q usb
701                                 then
702                                         dev_ok="yes"
703                                 fi
704                         fi
705                 fi
706
707                 if [ "${dev_ok}" = "yes" ]
708                 then
709                         case "${output_format}" in
710                                 sys)
711                                         ret="${ret} ${sysblock}"
712                                         ;;
713                                 *)
714                                         devname=$(sys2dev "${sysblock}")
715                                         ret="${ret} ${devname}"
716                                         ;;
717                         esac
718                 fi
719         done
720
721         echo "${ret}"
722 }
723
724 removable_usb_dev ()
725 {
726         output_format="${1}"
727
728         removable_dev "${output_format}" "want_usb"
729 }
730
731 non_removable_dev ()
732 {
733         output_format="${1}"
734         ret=
735
736         for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -vE "/(loop|ram|dm-|fd)")
737         do
738                 if [ "$(cat ${sysblock}/removable)" = "0" ]
739                 then
740                         case "${output_format}" in
741                                 sys)
742                                         ret="${ret} ${sysblock}"
743                                         ;;
744                                 *)
745                                         devname=$(sys2dev "${sysblock}")
746                                         ret="${ret} ${devname}"
747                                         ;;
748                         esac
749                 fi
750         done
751
752         echo "${ret}"
753 }
754
755 link_files ()
756 {
757         # create source's directory structure in dest, and recursively
758         # create symlinks in dest to to all files in source. if mask
759         # is non-empty, remove mask from all source paths when
760         # creating links (will be necessary if we change root, which
761         # live-boot normally does (into $rootmnt)).
762
763         # remove multiple /:s and ensure ending on /
764         local src_dir="$(trim_path ${1})/"
765         local dest_dir="$(trim_path ${2})/"
766         local src_mask="${3}"
767
768         # This check can only trigger on the inital, non-recursive call since
769         # we create the destination before recursive calls
770         if [ ! -d "${dest_dir}" ]
771         then
772                 log_warning_msg "Must link_files into a directory"
773                 return
774         fi
775
776         find "${src_dir}" -mindepth 1 -maxdepth 1 | while read src; do
777                 local dest="${dest_dir}$(basename "${src}")"
778                 if [ -d "${src}" ]
779                 then
780                         if [ -z "$(ls -A "${src}")" ]
781                         then
782                                 continue
783                         fi
784                         if [ ! -d "${dest}" ]
785                         then
786                                 mkdir -p "${dest}"
787                                 chown_ref "${src}" "${dest}"
788                                 chmod_ref "${src}" "${dest}"
789                         fi
790                         link_files "${src}" "${dest}" "${src_mask}"
791                 else
792                         local final_src=${src}
793                         if [ -n "${src_mask}" ]
794                         then
795                                 final_src="$(echo ${final_src} | sed "s|^${src_mask}||")"
796                         fi
797                         rm -rf "${dest}" 2> /dev/null
798                         ln -s "${final_src}" "${dest}"
799                         chown_ref "${src}" "${dest}"
800                 fi
801         done
802 }
803
804 do_union ()
805 {
806         local unionmountpoint="${1}"    # directory where the union is mounted
807         local unionrw="${2}"            # branch where the union changes are stored
808         local unionro1="${3}"           # first underlying read-only branch (optional)
809         local unionro2="${4}"           # second underlying read-only branch (optional)
810
811         if [ "${UNIONTYPE}" = "aufs" ]
812         then
813                 rw_opt="rw"
814                 ro_opt="rr+wh"
815                 noxino_opt="noxino"
816         elif [ "${UNIONTYPE}" = "unionfs-fuse" ]
817         then
818                 rw_opt="RW"
819                 ro_opt="RO"
820         else
821                 rw_opt="rw"
822                 ro_opt="ro"
823         fi
824
825         case "${UNIONTYPE}" in
826                 unionfs-fuse)
827                         unionmountopts="-o cow -o noinitgroups -o default_permissions -o allow_other -o use_ino -o suid"
828                         unionmountopts="${unionmountopts} ${unionrw}=${rw_opt}"
829                         if [ -n "${unionro1}" ]
830                         then
831                                 unionmountopts="${unionmountopts}:${unionro1}=${ro_opt}"
832                         fi
833                         if [ -n "${unionro2}" ]
834                         then
835                                 unionmountopts="${unionmountopts}:${unionro2}=${ro_opt}"
836                         fi
837                         ( sysctl -w fs.file-max=391524 ; ulimit -HSn 16384
838                         unionfs-fuse ${unionmountopts} "${unionmountpoint}" ) && \
839                         ( mkdir -p /run/sendsigs.omit.d
840                         pidof unionfs-fuse >> /run/sendsigs.omit.d/unionfs-fuse || true )
841                         ;;
842
843                 overlayfs)
844                         # XXX: can unionro2 be used? (overlayfs only handles two dirs, but perhaps they can be chained?)
845                         # XXX: and can unionro1 be optional? i.e. can overlayfs skip lowerdir?
846                         unionmountopts="-o noatime,lowerdir=${unionro1},upperdir=${unionrw}"
847                         mount -t ${UNIONTYPE} ${unionmountopts} ${UNIONTYPE} "${unionmountpoint}"
848                         ;;
849
850                 *)
851                         unionmountopts="-o noatime,${noxino_opt},dirs=${unionrw}=${rw_opt}"
852                         if [ -n "${unionro1}" ]
853                         then
854                                 unionmountopts="${unionmountopts}:${unionro1}=${ro_opt}"
855                         fi
856                         if [ -n "${unionro2}" ]
857                         then
858                                 unionmountopts="${unionmountopts}:${unionro2}=${ro_opt}"
859                         fi
860                         mount -t ${UNIONTYPE} ${unionmountopts} ${UNIONTYPE} "${unionmountpoint}"
861                         ;;
862         esac
863 }
864
865 get_custom_mounts ()
866 {
867         # Side-effect: leaves $devices with live-persistence.conf mounted in /live/persistence
868         # Side-effect: prints info to file $custom_mounts
869
870         local custom_mounts=${1}
871         shift
872         local devices=${@}
873
874         local bindings="/tmp/bindings.list"
875         local links="/tmp/links.list"
876         rm -rf ${bindings} ${links} 2> /dev/null
877
878         for device in ${devices}
879         do
880                 if [ ! -b "${device}" ]
881                 then
882                         continue
883                 fi
884
885                 local device_name="$(basename ${device})"
886                 local backing=$(mount_persistence_media ${device})
887                 if [ -z "${backing}" ]
888                 then
889                         continue
890                 fi
891
892                 local include_list="${backing}/${persistence_list}"
893                 if [ ! -r "${include_list}" ]
894                 then
895                         continue
896                 fi
897
898                 if [ -n "${DEBUG}" ] && [ -e "${include_list}" ]
899                 then
900                         cp ${include_list} /live/persistence/${persistence_list}.${device_name}
901                 fi
902
903                 while read dir options # < ${include_list}
904                 do
905                         if echo ${dir} | grep -qe "^[[:space:]]*\(#.*\)\?$"
906                         then
907                                 # skipping empty or commented lines
908                                 continue
909                         fi
910
911                         if trim_path ${dir} | grep -q -e "^[^/]" -e "^/live\(/.*\)\?$" -e "^/\(.*/\)\?\.\.\?\(/.*\)\?$"
912                         then
913                                 log_warning_msg "Skipping unsafe custom mount ${dir}: must be an absolute path containing neither the \".\" nor \"..\" special dirs, and cannot be \"/live\" or any sub-directory therein."
914                                 continue
915                         fi
916
917                         local opt_source=""
918                         local opt_link=""
919                         for opt in $(echo ${options} | tr ',' ' ');
920                         do
921                                 case "${opt}" in
922                                         source=*)
923                                                 opt_source=${opt#source=}
924                                                 ;;
925                                         link)
926                                                 opt_link="yes"
927                                                 ;;
928                                         union|bind)
929                                                 ;;
930                                         *)
931                                                 log_warning_msg "Skipping custom mount with unkown option: ${opt}"
932                                                 continue 2
933                                                 ;;
934                                 esac
935                         done
936
937                         local source="${dir}"
938                         if [ -n "${opt_source}" ]
939                         then
940                                 if echo ${opt_source} | grep -q -e "^/" -e "^\(.*/\)\?\.\.\?\(/.*\)\?$" && [ "${source}" != "." ]
941                                 then
942                                         log_warning_msg "Skipping unsafe custom mount with option source=${opt_source}: must be either \".\" (the media root) or a relative path w.r.t. the media root that contains neither comas, nor the special \".\" and \"..\" path components"
943                                         continue
944                                 else
945                                         source="${opt_source}"
946                                 fi
947                         fi
948
949                         local full_source="$(trim_path ${backing}/${source})"
950                         local full_dest="$(trim_path ${rootmnt}/${dir})"
951                         if [ -n "${opt_link}" ]
952                         then
953                                 echo "${device} ${full_source} ${full_dest} ${options}" >> ${links}
954                         else
955                                 echo "${device} ${full_source} ${full_dest} ${options}" >> ${bindings}
956                         fi
957                 done < ${include_list}
958         done
959
960         # We sort the list according to destination so we're sure that
961         # we won't hide a previous mount. We also ignore duplicate
962         # destinations in a more or less arbitrary way.
963         [ -e "${bindings}" ] && sort -k3 -sbu ${bindings} >> ${custom_mounts} && rm ${bindings}
964
965         # After all mounts are considered we add symlinks so they
966         # won't be hidden by some mount.
967         [ -e "${links}" ] && cat ${links} >> ${custom_mounts} && rm ${links}
968
969         # We need to make sure that no two custom mounts have the same sources
970         # or are nested; if that is the case, too much weird stuff can happen.
971         local prev_source="impossible source" # first iteration must not match
972         local prev_dest=""
973         # This sort will ensure that a source /a comes right before a source
974         # /a/b so we only need to look at the previous source
975         sort -k2 -b ${custom_mounts} |
976         while read device source dest options
977         do
978                 if echo ${source} | grep -qe "^${prev_source}\(/.*\)\?$"
979                 then
980                         panic "Two persistence mounts have the same or nested sources: ${source} on ${dest}, and ${prev_source} on ${prev_dest}"
981                 fi
982                 prev_source=${source}
983                 prev_dest=${dest}
984         done
985 }
986
987 activate_custom_mounts ()
988 {
989         local custom_mounts="${1}" # the ouput from get_custom_mounts()
990         local used_devices=""
991
992         while read device source dest options # < ${custom_mounts}
993         do
994                 local opt_bind="yes"
995                 local opt_link=""
996                 local opt_union=""
997                 for opt in $(echo ${options} | tr ',' ' ');
998                 do
999                         case "${opt}" in
1000                                 bind)
1001                                         opt_bind="yes"
1002                                         unset opt_link opt_union
1003                                         ;;
1004                                 link)
1005                                         opt_link="yes"
1006                                         unset opt_bind opt_union
1007                                         ;;
1008                                 union)
1009                                         opt_union="yes"
1010                                         unset opt_bind opt_link
1011                                         ;;
1012                         esac
1013                 done
1014
1015                 if [ -n "$(what_is_mounted_on "${dest}")" ]
1016                 then
1017                         if [ "${dest}" = "${rootmnt}" ]
1018                         then
1019                                 umount "${dest}"
1020                         else
1021                                 log_warning_msg "Skipping custom mount ${dest}: $(what_is_mounted_on "${dest}") is already mounted there"
1022                                 continue
1023                         fi
1024                 fi
1025
1026                 if [ ! -d "${dest}" ]
1027                 then
1028                         # create the destination and delete existing files in
1029                         # its path that are in the way
1030                         path="/"
1031                         for dir in $(echo ${dest} | sed -e 's|/\+| |g')
1032                         do
1033                                 path=$(trim_path ${path}/${dir})
1034                                 if [ -f ${path} ]
1035                                 then
1036                                         rm -f ${path}
1037                                 fi
1038                                 if [ ! -e ${path} ]
1039                                 then
1040                                         mkdir -p ${path}
1041                                         if echo ${path} | grep -qe "^${rootmnt}/*home/[^/]\+"
1042                                         then
1043                                                 # if ${dest} is in /home try fixing proper ownership by assuming that the intended user is the first, which is usually the case
1044                                                 # FIXME: this should really be handled by live-config since we don't know for sure which uid a certain user has until then
1045                                                 chown 1000:1000 ${path}
1046                                         fi
1047                                 fi
1048                         done
1049                 fi
1050
1051                 # if ${source} doesn't exist on our persistence media
1052                 # we bootstrap it with $dest from the live filesystem.
1053                 # this both makes sense and is critical if we're
1054                 # dealing with /etc or other system dir.
1055                 if [ ! -d "${source}" ]
1056                 then
1057                         if [ -n "${PERSISTENCE_READONLY}" ]
1058                         then
1059                                 continue
1060                         elif [ -n "${opt_union}" ] || [ -n "${opt_link}" ]
1061                         then
1062                                 # unions and don't need to be bootstrapped
1063                                 # link dirs can't be bootstrapped in a sensible way
1064                                 mkdir -p "${source}"
1065                                 chown_ref "${dest}" "${source}"
1066                                 chmod_ref "${dest}" "${source}"
1067                         elif [ -n "${opt_bind}" ]
1068                         then
1069                                 # ensure that $dest is not copied *into* $source
1070                                 mkdir -p "$(dirname ${source})"
1071                                 cp -a "${dest}" "${source}"
1072                         fi
1073                 fi
1074
1075                 # XXX: If CONFIG_AUFS_ROBR is added to the Debian kernel we can
1076                 # ignore the loop below and set rofs_dest_backing=$dest
1077                 local rofs_dest_backing=""
1078                 if [ -n "${opt_link}"]
1079                 then
1080                         for d in /live/rofs/*
1081                         do
1082                                 if [ -n "${rootmnt}" ]
1083                                 then
1084                                         rofs_dest_backing="${d}/$(echo ${dest} | sed -e "s|${rootmnt}||")"
1085                                 else
1086                                         rofs_dest_backing="${d}/${dest}"
1087                                 fi
1088                                 if [ -d "${rofs_dest_backing}" ]
1089                                 then
1090                                         break
1091                                 else
1092                                         rofs_dest_backing=""
1093                                 fi
1094                         done
1095                 fi
1096
1097                 if [ -n "${opt_link}" ] && [ -z "${PERSISTENCE_READONLY}" ]
1098                 then
1099                         link_files ${source} ${dest} ${rootmnt}
1100                 elif [ -n "${opt_link}" ] && [ -n "${PERSISTENCE_READONLY}" ]
1101                 then
1102                         mkdir -p /live/persistence
1103                         local links_source=$(mktemp -d /live/persistence/links-source-XXXXXX)
1104                         chown_ref ${source} ${links_source}
1105                         chmod_ref ${source} ${links_source}
1106                         # We put the cow dir in the below strange place to
1107                         # make it absolutely certain that the link source
1108                         # has its own directory and isn't nested with some
1109                         # other custom mount (if so that mount's files would
1110                         # be linked, causing breakage.
1111                         local cow_dir="/live/overlay/live/persistence/$(basename ${links_source})"
1112                         mkdir -p ${cow_dir}
1113                         chown_ref "${source}" "${cow_dir}"
1114                         chmod_ref "${source}" "${cow_dir}"
1115                         do_union ${links_source} ${cow_dir} ${source} ${rofs_dest_backing}
1116                         link_files ${links_source} ${dest} ${rootmnt}
1117                 elif [ -n "${opt_union}" ] && [ -z "${PERSISTENCE_READONLY}" ]
1118                 then
1119                         do_union ${dest} ${source} ${rofs_dest_backing}
1120                 elif [ -n "${opt_bind}" ] && [ -z "${PERSISTENCE_READONLY}" ]
1121                 then
1122                         mount --bind "${source}" "${dest}"
1123                 elif [ -n "${opt_bind}" -o -n "${opt_union}" ] && [ -n "${PERSISTENCE_READONLY}" ]
1124                 then
1125                         # bind-mount and union mount are handled the same
1126                         # in read-only mode, but note that rofs_dest_backing
1127                         # is non-empty (and necessary) only for unions
1128                         if [ -n "${rootmnt}" ]
1129                         then
1130                                 local cow_dir="$(echo ${dest} | sed -e "s|^${rootmnt}|/live/overlay/|")"
1131                         else
1132                                 # This is happens if persistence is activated
1133                                 # post boot
1134                                 local cow_dir="/live/overlay/${dest}"
1135                         fi
1136                         if [ -e "${cow_dir}" ] && [ -z "${opt_link}" ]
1137                         then
1138                                 # If an earlier custom mount has files here
1139                                 # it will "block" the current mount's files
1140                                 # which is undesirable
1141                                 rm -rf "${cow_dir}"
1142                         fi
1143                         mkdir -p ${cow_dir}
1144                         chown_ref "${source}" "${cow_dir}"
1145                         chmod_ref "${source}" "${cow_dir}"
1146                         do_union ${dest} ${cow_dir} ${source} ${rofs_dest_backing}
1147                 fi
1148
1149                 PERSISTENCE_IS_ON="1"
1150                 export PERSISTENCE_IS_ON
1151
1152                 if echo ${used_devices} | grep -qve "^\(.* \)\?${device}\( .*\)\?$"
1153                 then
1154                         used_devices="${used_devices} ${device}"
1155                 fi
1156         done < ${custom_mounts}
1157
1158         echo ${used_devices}
1159 }
1160
1161 fix_backwards_compatibility ()
1162 {
1163         local device=${1}
1164         local dir=${2}
1165         local opt=${3}
1166
1167         if [ -n "${PERSISTENCE_READONLY}" ]
1168         then
1169                 return
1170         fi
1171
1172         local backing="$(mount_persistence_media ${device})"
1173         if [ -z "${backing}" ]
1174         then
1175                 return
1176         fi
1177
1178         local include_list="${backing}/${persistence_list}"
1179         if [ ! -r "${include_list}" ]
1180         then
1181                 echo "# persistence backwards compatibility:
1182 ${dir} ${opt},source=." > "${include_list}"
1183         fi
1184 }
1185
1186 is_mountpoint ()
1187 {
1188         directory="$1"
1189
1190         [ $(stat -fc%d:%D "${directory}") != $(stat -fc%d:%D "${directory}/..") ]
1191 }