SailfishLVM
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