Skip to content

SailfishLVM

Adam Boardman edited this page Mar 2, 2019 · 5 revisions

Sailfish's LVM config

The latest Sailfish image has moved to LVM, this is of interest to Gemian as we wish to allow Multi-OS gemini's with more than just the planet supplied options of Android + One other non-Android image.

The layout is PV 'sailfish' with LV's of 'root' and 'home'. The idea would be for Gemian to make our ramdisk 'LVM' aware, in that if on boot we discover that we have a sailfish PV with say a LV of 'stretch' we would then mount it, but still also work with the old non-LVM root and old style stowaways based devices. Its also interesting to note that Sailfish consider it perfectly fine to fully delete their entire LVM PV and re-create for a factory reset, this would of course scrub our piggyback LVM roots.

The community beta ramdisk (part of the boot partition along with the kernel) contains the following interesting files (also available on upstream github):

/etc/sysconfig/init

# Common settings for normal and recovery init.

# Amount of space to keep unallocated for refilling root or home later on.
LVM_RESERVED_MB=0

# Default size for root LV
LVM_ROOT_SIZE=2500

/etc/sysconfig/partitions

# Common partition labels used by initrd

# Default sailfish OS partition label
PHYSDEV_PART_LABEL=linux

# Default factory partition label
FIMAGE_PART_LABEL=fimage

# Factory resetable external media device paths
EXTERNAL_MEDIA_DEVICES="/dev/mmcblk[1-9]*p[1-9]*"

/sbin/root-mount

#!/bin/sh

# Jolla btrfs root mounting script, adapted for ext4
#
# Copyright (C) 2014 Jolla Ltd.
# Contact: Kalle Jokiniemi <kalle.jokiniemi@jolla.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

# Pass the partition label name as $1

log()
{
	echo "root-mount: $1" > /dev/kmsg
}

if [ -z $1 ] || [ ! -e $1 ]; then
	log "Please pass mount point as parameter!"
	exit 1
fi

. /etc/sysconfig/init
. /etc/sysconfig/partitions
. /etc/sysconfig/display

PHYSDEV_SEARCHLIST="$PHYSDEV_PART_LABEL sailfishos"
FS_RESIZED=".fs-resized"
IS_FS_RESIZED=0

for label in $PHYSDEV_SEARCHLIST; do
	PHYSDEV=$(find-mmc-bypartlabel "$label")

	if test -n "$PHYSDEV"; then
		break
	fi
done

if test -z "$PHYSDEV"; then
	log "Failed to find sailfish OS partition!"
	exit 1
fi

ROOTDEV="/dev/sailfish/root"
HOMEDEV="/dev/sailfish/home"
MOUNT_POINT=$1

LVM_RESERVED_KB=$(expr $LVM_RESERVED_MB \* 1024)

# This function should not fail even if the operations cannot be performed
check_firstboot_resize()
{
	if test "$IS_FS_RESIZED" -eq 1; then
		# The space is allocated, nothing to do.
		return 0
	fi

	FREE_EXTENTS=$(lvm vgdisplay sailfish -c | cut -d ":" -f 16)
	EXTENT_SIZE=$(lvm vgdisplay sailfish -c | cut -d ":" -f 13)
	FREE_KB=$(expr $FREE_EXTENTS \* $EXTENT_SIZE)

	# lvextend returns error if a partition was resized already,
	# so protecting it with a condition.
	if test "$FREE_KB" -gt "$LVM_RESERVED_KB"; then
		# Increase root size
		if ! lvm lvextend --size "$LVM_ROOT_SIZE"M "$ROOTDEV"; then
			log "Extending root LVM partition failed."
		fi
	fi

	e2fsck -f -y "$ROOTDEV" > /dev/kmsg
	# resize2fs returns 0 on resized partition.
	resize2fs -f "$ROOTDEV" > /dev/kmsg

	# Check how much space we can add to home
	FREE_EXTENTS=$(lvm vgdisplay sailfish -c | cut -d ":" -f 16)

	# lvextend returns error if a partition was resized already.
	if test "$FREE_EXTENTS" -gt 0; then
		HOME_KB=$(expr $FREE_EXTENTS \* $EXTENT_SIZE)
		HOME_KB=$(expr $HOME_KB - $LVM_RESERVED_KB)

		# Increase home size by HOME_KB
		if ! lvm lvextend --size +"$HOME_KB"K $HOMEDEV; then
			log "Extending home LVM partition failed."
		fi
	fi

	e2fsck -f -y "$HOMEDEV" > /dev/kmsg
	resize2fs -f "$HOMEDEV" > /dev/kmsg
	return 0
}

# If mounted filesystem $1 has .clear-device file, reset factory image.
factory_reset_if_needed()
{
	if test -f $1/usr/share/lipstick/devicelock/.clear-device; then
		if test -f $1/usr/share/lipstick/devicelock/.clear-device-enable-reboot; then
			REBOOT=1
		fi
		if test -f $1/usr/share/lipstick/devicelock/.clear-device-full-wipe; then
			export SAILFISHOS_WIPE_PARTITIONS="1"
		fi
		if test -f $1/usr/share/lipstick/devicelock/.clear-device-external-media; then
			CLEAR_EXTERNAL_MEDIA=1
		fi
		umount $1

		log "Mounting sysfs."
		mount /sys

		write()
		{
			echo -n "$2" > $1
		}

		# Minimize power consumption by lowering display brightness to minimum
		write $DISPLAY_BRIGHTNESS_PATH $DISPLAY_BRIGHTNESS

		yamui -a 1100 -t "Resetting to factory state, please do not power off!" \
		animation-recover-001 animation-recover-002 animation-recover-003 \
		animation-recover-004 animation-recover-005 animation-recover-006 \
		animation-recover-007 animation-recover-008 &

		YAMUIPID=$!

		if factory-reset-lvm $LVM_ROOT_SIZE $LVM_RESERVED_MB; then
			# Successful recovery.

			if test "$CLEAR_EXTERNAL_MEDIA" = "1" && ! factory-reset-external $EXTERNAL_MEDIA_DEVICES; then
				log "Requested clear of external storage but the command failed."
			fi

			kill "$YAMUIPID"
			# TODO: maybe replace this with a big-ass OK-sign png..
			yamui -t "Factory reset successful" &

			sleep 4

			if test "$REBOOT" = "1"; then
				reboot -f
			else
				poweroff -f
			fi
		else
			kill "$YAMUIPID"
			# TODO: maybe replace this with a big-ass OK-sign png...
			yamui -t "Factory reset failed" &
			sleep 4
			# We failed to reset, reboot to recovery mode
			log "Factory reset failed. Rebooting to recovery mode."
			reboot2 recovery
		fi
	fi
}

# Make sure all of the disk is assigned for use
lvm pvresize $PHYSDEV

# Activate LVs on sailfish volume group
if ! lvm vgchange -a y sailfish; then
	# TODO: Consider doing a factory reset here instead of aborting...
	#reset_factory_image
	log "No sailfish VG found, aborting system boot!"
	exit 1
fi

# Check factory reset need and read FS resize status.
if mount -t ext4 $ROOTDEV $1; then
	factory_reset_if_needed $1

	if test -f "$1/$FS_RESIZED"; then
		IS_FS_RESIZED=1
	fi

	umount $1
fi

# Check and resize root and home in case usage indicates first boot
check_firstboot_resize

# TODO: revisit for encryption, where home would not be mounted here.
if (mount -t ext4 $ROOTDEV $1 && mount -t ext4 $HOMEDEV $1/home); then
	log "Root and home partitions are mounted."
	# If we are here then filesystem is already resized.
	touch "$1/$FS_RESIZED"
	exit 0
fi

# We should not get here ever...
log "Can't continue system boot. Exiting."
exit 1

/sbin/find-mmc-bypartlabel

#!/bin/sh
#
# Tool for finding mmc device node path based on it's part label.
#
# Copyright (C) 2015 Jolla Ltd.
# Contact: Kalle Jokiniemi <kalle.jokiniemi@jolla.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

# Pass the partition label name as $1
if test -z $1; then
	echo "$0: Error, no label name given!" > /dev/kmsg
	exit 1
fi

if ! test -d /sys/class/block; then
	echo "$0: Error, please mount sysfs" > /dev/kmsg
	exit 1
fi

while [ ! -d /sys/class/block/mmcblk0p* ]; do
    echo "find-mmc-bypartlabel: Waiting for /sys/class/block/mmcblk0p*..." > /dev/kmsg
    (( i++ ))
    sleep 0.5
    if [ $i == 10 ]; then
        echo "find-mmc-bypartlabel: Error: timeout waiting for /sys/class/block/mmcblk0p*" > /dev/kmsg
        exit 1
    fi
done

for mmc_sysfs in $(ls -d /sys/class/block/mmcblk0p*); do
	if cat $mmc_sysfs/uevent | grep -w PARTNAME=$1 > /dev/null; then
		FIMAGE_DEV_NAME=$(echo $mmc_sysfs | cut -d "/" -f 5)
		echo "/dev/$FIMAGE_DEV_NAME"
		exit 0
	fi
done

echo "$0: Error, could not find partition label \"$1\"" > /dev/kmsg
exit 1

/sbin/factory-reset-external

#!/bin/sh
#
# External storage factory reset for Sailfish OS.
#
# Copyright (C) 2017 Jolla Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

# Parameters:
# $@ -> Device paths of partitions to reformat.

format_script()
{
cat << 'EOF'
#!/bin/sh

function log
{
	echo "$0: $1" > /dev/kmsg
}

for device in $@; do
	unset TYPE
	unset LABEL
	unset UUID

	if [ ! -e "$device" ]; then
		continue
	fi

	partitionname=$( basename $device )
	partitionblock=$( readlink -f /sys/class/block/$partitionname )

	if [ ! -e "$partitionblock" ]; then
		continue
	fi

	deviceblock=$( dirname "$partitionblock" )

	if ! grep -Fxq SD $deviceblock/device/type; then
		log "$device is not an SD-card! Skipping"
		continue
	fi

	eval "$(/sbin/blkid -c /dev/null -o export $device)"

	case "$TYPE" in
		vfat)
			format_command=/sbin/mkfs.vfat
			format_arguments=()
			if [ ! -z "$UUID" ]; then
				format_arguments=( "${format_arguments[@]}" -i "${UUID//-}" )
			fi
			if [ ! -z "$LABEL" ]; then
				format_arguments=( "${format_arguments[@]}" -n "${LABEL}" )
			fi
			;;
		ext4)
			format_command=/sbin/mkfs.ext4
			format_arguments=( -F -E root_owner=100000:100000 )
			if [ ! -z "$UUID" ]; then
				format_arguments=( "${format_arguments[@]}" -U "${UUID}" )
			fi
			if [ ! -z "$LABEL" ]; then
				format_arguments=( "${format_arguments[@]}" -L "${LABEL}")
			fi
			;;
		*)
			if [ -z "$TYPE" ]; then
				continue
			fi

			format_command=
			format_arguments=
			;;
	esac

	if [ -f $format_command ]; then
		log "Formatting $device as $TYPE"

		if test "$SAILFISHOS_WIPE_PARTITIONS" = "1"; then
			dd if=/dev/zero of=$device bs=1M
		fi

		echo $format_command "${format_arguments[@]}" $device
		$format_command "${format_arguments[@]}" $device || log "Failed to format $device"
	else
		TEMPMOUNT=$(mktemp -d)

		if mount $device $TEMPMOUNT; then
			log "Erasing data from $device"

			find $TEMPMOUNT -mindepth 1 -delete

			if test "$SAILFISHOS_WIPE_PARTITIONS" = "1"; then
				dd if=/dev/zero of=$TEMPMOUNT/large
				rm -f $TEMPMOUNT/large
			fi

			umount $TEMPMOUNT
		else
			log "Failed to mount $device to erase data"

			if test "$SAILFISHOS_WIPE_PARTITIONS" = "1"; then
				log "Erasing partition"
				dd if=/dev/zero of=$device
			fi
		fi

		rmdir $TEMPMOUNT
	fi
done
EOF
}

# Format devices from the rootfs where there is a full shell and more file-system tools.
TEMPMOUNT=$(mktemp -d)
if mount /dev/sailfish/root $TEMPMOUNT; then
	mount -t tmpfs tmpfs $TEMPMOUNT/tmp
	mount -t devtmpfs devtmpfs $TEMPMOUNT/dev
	mount -t proc proc $TEMPMOUNT/proc
	mount -t sysfs sys $TEMPMOUNT/sys
	format_script > $TEMPMOUNT/tmp/format-devices
	chmod 755 $TEMPMOUNT/tmp/format-devices
	chroot $TEMPMOUNT /tmp/format-devices $@
	umount $TEMPMOUNT/tmp $TEMPMOUNT/dev $TEMPMOUNT/proc $TEMPMOUNT/sys

	# Clean up
	umount $TEMPMOUNT
else
	echo "$0: Failed to mount rootfs" > /dev/kmsg
	if test "$SAILFISH_WIPE_PARTITIONS" = "$1"; then
		for device in $@; do
			dd if=/dev/zero of=$device
		done
	fi
fi
rmdir $TEMPMOUNT

/sbin/factory-reset-lvm

#!/bin/sh
#
# Factory reset tool for LVM based Sailfish OS filesystems.
#
# Copyright (C) 2015 Jolla Ltd.
# Contact: Kalle Jokiniemi <kalle.jokiniemi@jolla.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

# Parameters:
# $1 -> Root size in MB
# $2 -> Reserve space for later use in MB

ROOTDEV="/dev/sailfish/root"
HOMEDEV="/dev/sailfish/home"
ROOTIMG="root.img"
HOMEIMG="home.img"
NAME=$0

PHYSDEV_PART_LABEL="sailfish"
FIMAGE_PART_LABEL="fimage"
# From https://github.com/mer-hybris/hybris-initrd
. /etc/sysconfig/partitions

flash_script()
{
cat << 'EOF'
#!/bin/sh
for SCRIPT in /var/lib/platform-updates/* ; do
	if [ -x $SCRIPT ]; then
		echo "$SCRIPT ... "
		$SCRIPT && echo "OK" || echo "FAILED"
	fi
done
EOF
}

flash_firmwares()
{
	if test -z $1; then
		echo "$NAME: No mount point given" > /dev/kmsg
		exit 1
	fi
	mount -t tmpfs tmpfs $1/tmp
	mount -t devtmpfs devtmpfs $1/dev
	mount -t proc proc $1/proc
	flash_script > $1/tmp/flash-firmwares
	chmod 755 $1/tmp/flash-firmwares
	chroot $1 /tmp/flash-firmwares
	umount $1/tmp $1/dev $1/proc
}

if test -z $1 || ! test $1 -ge 0; then
	echo "$NAME: Please pass root size in MB as parameter!" > /dev/kmsg
	exit 1
fi

if test -z $2 || ! test $2 -ge 0; then
	echo "$NAME: Please pass reserve size in MB as parameter!" > /dev/kmsg
	exit 1
fi

ROOT_SIZE=$1
RESERVE_KB=$(expr $2 \* 1024)

echo "$NAME: Starting factory reset.." > /dev/kmsg

PHYSDEV=$(find-mmc-bypartlabel "$PHYSDEV_PART_LABEL")
if test $? != "0"; then
	echo "$NAME: Error: could not find sailfish partition" > /dev/kmsg
	exit 1
fi

FIMAGE_DEV_PATH=$(find-mmc-bypartlabel "$FIMAGE_PART_LABEL")
if test $? != "0"; then
	echo "$NAME: Error: could not find fimage partition" > /dev/kmsg
	exit 1
fi

echo "$NAME: fimage partition in $FIMAGE_DEV_PATH" > /dev/kmsg

FIMAGE_MOUNT=$(mktemp -d)
if ! mount "$FIMAGE_DEV_PATH" "$FIMAGE_MOUNT"; then
	echo "$NAME: Error, could not mount fimage for factory reset!" > /dev/kmsg
	rmdir $FIMAGE_MOUNT
	exit 1
fi

# Find the biggest versioned Sailfish folder and use that
SAILFISH_FIMAGE=$(ls -d $FIMAGE_MOUNT/Sailfish* | tail -1)
WORKDIR=$(pwd)

if test -z $SAILFISH_FIMAGE; then
	echo "$NAME: Error: Could not find a recovery image folder!" > /dev/kmsg
	exit 1
fi

# Check that the factory images are ok to use and detect compression method.
cd $SAILFISH_FIMAGE
if test -f $ROOTIMG.lzo && test -f $HOMEIMG.lzo; then
	ROOTIMG="$ROOTIMG.lzo"
	HOMEIMG="$HOMEIMG.lzo"
	DECOMPRESS_CMD="lzopcat"
elif test -f $ROOTIMG.gz && test -f $HOMEIMG.gz; then
	ROOTIMG="$ROOTIMG.gz"
	HOMEIMG="$HOMEIMG.gz"
	DECOMPRESS_CMD="pigz -d -c"
elif test -f $ROOTIMG.bz2 && test -f $HOMEIMG.bz2; then
	ROOTIMG="$ROOTIMG.bz2"
	HOMEIMG="$HOMEIMG.bz2"
	DECOMPRESS_CMD="bzip2 -d -c"
elif test -f $ROOTIMG.xz && test -f $HOMEIMG.xz; then
	ROOTIMG="$ROOTIMG.xz"
	HOMEIMG="$HOMEIMG.xz"
	DECOMPRESS_CMD="xz -d -c"
else
	echo "$NAME: Error: cannot find sailfish recovery image!" > /dev/kmsg
	exit 1
fi

if ! md5sum -c $ROOTIMG.md5 > /dev/kmsg; then
	echo "$NAME: Error: root recovery image corrupted!" > /dev/kmsg
	exit 1
fi

if ! md5sum -c $HOMEIMG.md5 > /dev/kmsg; then
	echo "$NAME: Error: home recovery image corrupted!" > /dev/kmsg
	exit 1
fi

cd $WORKDIR

# Clean up old LVM if it happens to exist
lvm vgchange -a n
lvm vgremove -y sailfish
lvm pvremove -y $PHYSDEV

if test "$SAILFISHOS_WIPE_PARTITIONS" = "1"; then
	dd if=/dev/zero of=$PHYSDEV bs=1M
fi

# Create the LVM setup
if ! lvm pvcreate $PHYSDEV; then
	echo "$NAME: Error, could create LVM physical device for $PHYSDEV" > /dev/kmsg
	exit 1
fi

# If the PV exists, creating VG should never fail
lvm vgcreate sailfish $PHYSDEV

# Checking for errors to maybe catch wrong root size parameter
if ! lvm lvcreate -L "$ROOT_SIZE"M --name root sailfish; then
	echo "$NAME: Error, could create root LV" > /dev/kmsg
	exit 1
fi


# Calculate home size
FREE_EXTENTS=$(lvm vgdisplay sailfish -c | cut -d ":" -f 16)
EXTENT_SIZE=$(lvm vgdisplay sailfish -c | cut -d ":" -f 13)
FREE_KB=$(expr $FREE_EXTENTS \* $EXTENT_SIZE)
HOME_SIZE=$(expr $FREE_KB - $RESERVE_KB)

# Check for too big reserve (not enough room left for home) case (1024kB * 64 = 64MB)
if test $HOME_SIZE -le 65536; then
	echo "$NAME: Error: too big reserve, not enough space for home" > /dev/kmsg
	exit 1
fi

# Create home LV
lvm lvcreate -L "$HOME_SIZE"K --name home sailfish

# Start restoring Sailfish OS from the factory images
$DECOMPRESS_CMD $SAILFISH_FIMAGE/$ROOTIMG > $ROOTDEV
$DECOMPRESS_CMD $SAILFISH_FIMAGE/$HOMEIMG > $HOMEDEV
sync

resize2fs -f $ROOTDEV
resize2fs -f $HOMEDEV

sync

# Flash firmwares from the resetted root
TEMPMOUNT=$(mktemp -d)
mount $ROOTDEV $TEMPMOUNT
flash_firmwares $TEMPMOUNT

# Clean up
umount $TEMPMOUNT
rmdir $TEMPMOUNT

umount $FIMAGE_MOUNT
rmdir $FIMAGE_MOUNT