#!/system/bin/sh set -e echo "System Image Upgrader for Ubuntu Touch" echo "Preparing command file" if [ ! -e "$1" ]; then echo "Command file doesn't exist: $1" exit 1 fi mv $1 $1.applying COMMAND_FILE=$1.applying REMOVE_LIST="$COMMAND_FILE" # Used as a security check to see if we would change the password DATA_FORMAT=0 # System Mountpoint SYSTEM_MOUNTPOINT=/cache/system echo "Starting image upgrader" # Functions check_filesystem() { # $1 => image to check (partition or device img) if [ ! -e $1 ]; then echo "Partition/image not found: $1" return 1 fi # It's fine for e2fsck to return something different than 0 set +e e2fsck -yf $1 ret=$? # From e2fsck man page: # 0 - No errors # 1 - File system errors corrected if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then echo "e2fsck is unable to fix partition/image $1, aborting (return code $ret)" exit 1 fi set -e } verify_signature() { # HACK: ignore all signatures for allowing messing around with #return 0 # $1 => validation keyring name # $2 => path to validate if [ ! -e $2 ]; then echo "File doesn't exist: $2" return 1 fi # Check against the blacklist if [ -e /tmp/system-image/blacklist/pubring.gpg ]; then export GNUPGHOME=/tmp/system-image/blacklist/ if gpg --ignore-time-conflict --verify $2 >/dev/null 2>&1; then echo "File signed by a blacklisted key: $2" return 1 fi fi # Check against the keyring export GNUPGHOME=/tmp/system-image/$1/ if [ ! -e "$GNUPGHOME" ]; then echo "Keyring doesn't exist: $1" return 1 fi if gpg --ignore-time-conflict --verify $2 >/dev/null 2>&1; then return 0 fi return 1 } install_keyring() { # $1 => full path to tarball # $2 => full path to signature # Some basic checks if [ ! -e "$1" ] || [ ! -e "$2" ]; then echo "Missing keyring files: $1 => $2" return 1 fi # Unpacking TMPDIR=$(mktemp -d -p /tmp/system-image/ tmp.XXXXXXXXXX) cd $TMPDIR busybox xzcat $1 | busybox tar --numeric-owner -xf - if [ ! -e keyring.json ] || [ ! -e keyring.gpg ]; then rm -Rf $TMPDIR echo "Invalid keyring: $1" return 1 fi # Extract the expiry keyring_expiry=$(grep "^ \"expiry\": " keyring.json | cut -d: -f2 | sed -e "s/[ \",]//g") if [ -n "$keyring_expiry" ] && [ "$keyring_expiry" -lt "$(date +%s)" ]; then rm -Rf $TMPDIR echo "Keyring expired: $1" return 1 fi # Extract the keyring type keyring_type=$(grep "^ \"type\": " keyring.json | cut -d: -f2 | sed -e "s/[, \"]//g") if [ -z "$keyring_type" ]; then rm -Rf $TMPDIR echo "Missing keyring type: $1" return 1 fi if [ -e /tmp/system-image/$keyring_type ]; then rm -Rf $TMPDIR echo "Keyring already loaded: $1" return 1 fi signer="unknown" case "$keyring_type" in archive-master) signer="" ;; image-master) signer="archive-master" ;; image-signing|blacklist) signer="image-master" ;; device-signing) signer="image-signing" ;; esac if [ -n "$signer" ] && ! verify_signature $signer $2; then rm -Rf $TMPDIR echo "Invalid signature: $1" return 1 fi mkdir /tmp/system-image/$keyring_type chmod 700 /tmp/system-image/$keyring_type mv $TMPDIR/keyring.gpg /tmp/system-image/$keyring_type/pubring.gpg chmod 600 /tmp/system-image/$keyring_type/pubring.gpg chown 0:0 /tmp/system-image/$keyring_type/pubring.gpg rm -Rf $TMPDIR return 0 } property_write() { prop="$1" prop_value="$2" prop_dir="/data/android-data/property" # everything is wiped after a format, let's get a skeleton going mkdir -p "$prop_dir" chown 0:0 "$prop_dir" chmod 700 "$prop_dir" echo -n "$prop_value" > "$prop_dir/$prop" # properties won't be read if they aren't ro root chown 0:0 "$prop_dir/$prop" chmod 600 "$prop_dir/$prop" } adb_onlock() { if [ "$DATA_FORMAT" -eq 0 ]; then return 1 fi flag="/data/.adb_onlock" # if the param != "true" we just delete the flag case $1 in true) touch "$flag" ;; false) rm -f "$flag" ;; *) echo "Unkown parameter $1, disabling" rm -f "$flag" ;; esac } set_password() { if [ "$DATA_FORMAT" -eq 0 ]; then return 1 fi user="phablet" password="$1" if [ -z "$password" ]; then return 1 fi path=/bin:/usr/bin:/sbin:/usr/sbin PATH=$path chroot "$SYSTEM_MOUNTPOINT" /bin/sh -c "echo -n "$user:$password" | chpasswd" return 0 } unset_password() { if [ "$DATA_FORMAT" -eq 0 ]; then return 1 fi # Needs implementation } usb_enable() { prop_dir="/data/android-data/property" prop="persist.sys.usb.config" prop_val="$1" # Property value ordering is important here grep -q -s mtp "$prop_dir/$prop" && [ "$prop_val" == "adb" ] && prop_val="mtp,adb" grep -q -s adb "$prop_dir/$prop" && [ "$prop_val" == "mtp" ] && prop_val="mtp,adb" property_write "$prop" "$prop_val" } usb_disable() { prop_dir="/data/android-data/property" prop="persist.sys.usb.config" prop_val="$1" remain_prop="" # Property value ordering is important here grep -q -s mtp "$prop_dir/$prop" && [ "$prop_val" == "adb" ] && remain_prop="mtp" grep -q -s adb "$prop_dir/$prop" && [ "$prop_val" == "mtp" ] && remain_prop="adb" # we should not allow empty properties for the usb config [ "$remain_prop" == "" ] && remain_prop="adb" property_write "$prop" "$remain_prop" } factory_wipe() { # only set this flag if coming from a data wipe if [ "$DATA_FORMAT" -eq 0 ]; then return 1 fi flag="/data/.factory_wipe" # if the param != "true" we just delete the flag case $1 in true) touch "$flag" ;; false) rm -f "$flag" ;; *) echo "Unkown parameter $1, disabling" rm -f "$flag" ;; esac } # Initialize GPG rm -Rf /tmp/system-image mkdir -p /tmp/system-image if [ -e /etc/system-image/archive-master.tar.xz ]; then echo "Loading keyring: archive-master.tar.xz" install_keyring /etc/system-image/archive-master.tar.xz /etc/system-image/archive-master.tar.xz.asc fi # Check the kernel command line to see whether Ubuntu should be installed to a partition # or in a file that is loop mounted. #if grep -q systempart= /proc/cmdline; then # USE_SYSTEM_PARTITION=1 #else # USE_SYSTEM_PARTITION=0 #fi # Force usage of system partition for now, the existance of Android 9.0 devices # with a too small system partition is questionable at this point. USE_SYSTEM_PARTITION=1 # However, do not use the block device name in the kernel command line, as that is not consistently # named in booting normal and recovery modes. Expect fstab to have a system mountpoint or use a fallback. if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then SYSTEM_PARTITION=$(grep "^[^#]" /etc/fstab | grep -e "\(/mnt\)*/system\(_root\)*" | cut -f 1 -d\ ) #Fall back to emmc@android if there's no system in fstab if [ "$SYSTEM_PARTITION" == "" ]; then SYSTEM_PARTITION="emmc@android" fi fi # If we are not using the system partition, ensure that /data is mounted if [ "$USE_SYSTEM_PARTITION" -eq 0 ]; then mount /data fi # Process the command file FULL_IMAGE=0 echo "Processing command file" while read line do set -- $line case "$1" in format) echo "Formatting: $2" case "$2" in system) FULL_IMAGE=1 # Cleanup files left from halium-install rm -f /data/system.img rm -f /data/android-rootfs.img rm -f /data/rootfs.img # Delete ubuntu.img if present. Either its a leftover, or we recreate it rm -f /data/ubuntu.img if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then echo "system partition: $SYSTEM_PARTITION" umount /mnt/system || true mkfs.ext4 $SYSTEM_PARTITION else # We need to use legacy image name here, halium-boot needs this to decide file layout dd if=/dev/zero of=/data/ubuntu.img seek=3M bs=1024 count=0 mkfs.ext2 -F /data/ubuntu.img fi ;; data) if [ "$USE_SYSTEM_PARTITION" -eq 1 ]; then mount /data || true fi for entry in /data/* /data/.writable_image /data/.factory_wipe; do if [ "$USE_SYSTEM_PARTITION" -eq 0 ]; then if [[ ( "$entry" == "/data/ubuntu.img" ) || ( "$entry" == "/data/rootfs.img" ) || ( "$entry" == "/data/system.img" ) || ( "$entry" == "/data/android-rootfs.img" ) ]]; then continue fi fi # Some devices use /data as /cache, so avoid removing # files that are essential to flashing and upgrading if [ "$entry" == "/data/cache" ]; then continue fi # If this is android-data, inspect closer as we need the # cache "partition" to stay functioning during use of the # system-image-upgrader script. It is bind mounted # and leads to premature cancellation otherwise. if [ "$entry" == "/data/android-data" ]; then for android_data_entry in /data/android-data/*; do if [ "$android_data_entry" != "/data/android-data/cache" ]; then # recursively unset immutable attribute set to few files # by vendor services before removal #chattr -R -i $android_data_entry rm -Rf $android_data_entry fi done else rm -Rf $entry fi done # mtp is always enabled by default usb_enable mtp DATA_FORMAT=1 if [ "$USE_SYSTEM_PARTITION" -eq 1 ]; then umount /data fi ;; *) echo "Unknown format target: $2" ;; esac ;; enable) echo "Enabling: $2" case "$2" in developer_mode) usb_enable adb ;; mtp) usb_enable mtp ;; default_password) set_password $3 ;; adb_onlock) adb_onlock true ;; factory_wipe) factory_wipe true ;; *) echo "Unknown enable target: $2" ;; esac ;; disable) echo "Disabling: $2" case "$2" in developer_mode) usb_disable adb ;; mtp) usb_disable mtp ;; default_password) unset_password ;; adb_onlock) adb_onlock false ;; factory_wipe) factory_wipe false ;; *) echo "Unknown disable target: $2" ;; esac ;; load_keyring) if [ ! -e "/cache/recovery/$2" ] || [ ! -e "/cache/recovery/$3" ]; then echo "Skipping missing file: $2" continue fi REMOVE_LIST="$REMOVE_LIST /cache/recovery/$2 /cache/recovery/$3" echo "Loading keyring: $2" install_keyring /cache/recovery/$2 /cache/recovery/$3 if [ -e /tmp/system-image/image-master/pubring.gpg ] && \ [ ! -e /tmp/system-image/blacklist/pubring.gpg ] && \ [ -e /data/system-data/var/lib/system-image/blacklist.tar.xz ] && \ [ -e /data/system-data/var/lib/system-image/blacklist.tar.xz.asc ]; then echo "Loading blacklist keyring" install_keyring /data/system-data/var/lib/system-image/blacklist.tar.xz /data/system-data/var/lib/system-image/blacklist.tar.xz.asc fi ;; mount) case "$2" in system) mkdir -p "$SYSTEM_MOUNTPOINT" if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then umount $SYSTEM_PARTITION || true umount $SYSTEM_MOUNTPOINT || true umount /mnt/system || true check_filesystem $SYSTEM_PARTITION mount $SYSTEM_PARTITION "$SYSTEM_MOUNTPOINT" else check_filesystem /data/ubuntu.img mount -o loop /data/ubuntu.img "$SYSTEM_MOUNTPOINT/" fi ;; *) echo "Unknown mount target: $2" ;; esac ;; unmount) case "$2" in system) umount "$SYSTEM_MOUNTPOINT" rmdir "$SYSTEM_MOUNTPOINT" ;; *) echo "Unknown mount target: $2" ;; esac ;; update) if [ ! -e "/cache/recovery/$2" ] || [ ! -e "/cache/recovery/$3" ]; then echo "Skipping missing file: $2" continue fi REMOVE_LIST="$REMOVE_LIST /cache/recovery/$3" if ! verify_signature device-signing /cache/recovery/$3 && \ ! verify_signature image-signing /cache/recovery/$3; then echo "Invalid signature" exit 1 fi echo "Applying update: $2" cd /cache rm -Rf partitions rm -Rf data || true # Start by removing any file listed in "removed" if [ "$FULL_IMAGE" != "1" ]; then busybox xzcat recovery/$2 | busybox tar --numeric-owner -xf - removed >/dev/null 2>&1 || true if [ -e removed ]; then while read file; do rm -Rf $file done < removed fi rm -f removed fi # Unpack everything else on top of the system partition busybox xzcat recovery/$2 | busybox tar --numeric-owner -xf - rm -f removed # WORKAROUND: fix erroneous vendor symlink if [ -h "$SYSTEM_MOUNTPOINT/vendor" ]; then VENDOR_SYMLINK_PATH=$(readlink $SYSTEM_MOUNTPOINT/vendor) if [ "$VENDOR_SYMLINK_PATH" == "/android/system/vendor" ]; then rm $SYSTEM_MOUNTPOINT/vendor ln -s /android/vendor $SYSTEM_MOUNTPOINT/vendor fi fi # Move things to data if [ -d data ]; then mv data/* /data/ || true rm -Rf data || true fi # Process partition images grep "^/dev" /etc/fstab | while read line; do set -- $line part=${2##/} path=$1 if [ -e partitions/${part}.img ] && [ -e $path ]; then if simgtest partitions/${part}.img 2>/dev/null; then echo "Flashing sparse ${part} at ${path}" simg2img partitions/${part}.img ${path} else echo "Checking if ${part} at ${path} needs updating..." new_size=$(wc -c < partitions/${part}.img) if ! head -c ${new_size} ${path} | cmp -s partitions/${part}.img; then echo " Flashing (binary content differs up to ${new_size} bytes)..." cat partitions/${part}.img > ${path} else echo " Already up-to-date" fi fi sync rm partitions/${part}.img fi done # Remove tarball to free up space, since device tarballs # extract partitions/blobs that might fill up cache, # this way we ensure we got space for the partitions/blobs rm -f recovery/$2 ;; "#") # We are processing a command in the script, ignore it ;; *) echo "Unknown command: $1" ;; esac done < $COMMAND_FILE # Remove the update files for file in $REMOVE_LIST; do rm -f $file done # If a previous SWAP of 512MB is available, remove if [ -e /data/SWAP.img ]; then echo "Removing obsolete SWAP.img" rm -f /data/SWAP.img fi # Ensure we have sane permissions if [ "$USE_SYSTEM_PARTITION" -eq 0 ];then chmod 600 /data/ubuntu.img chown 0:0 /data/ubuntu.img fi touch /data/.last_update || true sync echo "Done upgrading..." # Make sure there are always 5% reserved in /data for root usage # else filling $HOME can make the system unusable since writable # files and dirs share the space with $HOME and android does not # reserve root space in /data for ext4. # Devices with f2fs can reserve space with setting the # "reserve_root" mount option. if [ "$USE_SYSTEM_PARTITION" -eq 1 ]; then mount /data fi DATA_FS_TYPE=$(grep "/data " /proc/mounts| cut -d ' ' -f3) if [ "$DATA_FS_TYPE" == "ext4" ]; then tune2fs -m 5 $(grep "/data " /proc/mounts| sed -e 's/ .*$//') fi # Always unmount unconditionally umount /data sync