I'm currently maintaining the Conductor App for PhoneLab testbed. One of the core tasks performed by conductor is to perform system OTA update, so that we can push platform changes to our participants, either to fix bugs, or to do system level experiments (libc, Dalvik VM, etc.).

So the first step is, how to create an OTA package?

Directory Structure

Suppose we have a patched version of libc and we want to overwrite the previous one already in participants' phone, we need first to figure out where that file is in the file system. In this case, it's /system/lib/libc.so. Then our OTA package's directory structure must looks like this:

myupdate
|-- META-INF
|   `-- com
|       `-- google
|           `-- android
|               |-- update-binary
|               `-- updater-script
`-- system
    `-- lib
        `-- libc.so

The update-binary and updater-script are used to actually perform the update, I'll explain them later.

Note that the structure of the system needs to be exactly the same with what's in Android's setup, so that we can copy that directory directly to target system, and overwrite the files with the updated version.

The updater-script

The update-binary, as its name indicates, is a binary file that will parse the updater-script we write. It's quite standard and nothing special. You can obtain a copy of this file here.

The updater-scipts contains the operations we want to perform. Its written using Edify scripting language, which has quite simple and intuitive syntax. You can find more details in this xda thread.

In this case, what we need to do is quite simple: mount the /system partition and copy the files in the OTA package to target file system. So the updater-script may looks like this:

mount("ext4", "EMMC", "/dev/block/platform/omap/omap_hsmmc.0/by-name/system", "/system");
package_extract_dir("system", "/system");                                        
unmount("/system");       

First, we mount the target file system's system partition using the mount command, the arguments are:

  • FSTYPE: File system type. In this case, it's "ext4"
  • TYPE: Storage type. "EMMC" means internal solid state storage device on MMC bus, which is actually NAND flash.
  • DEV: The device to mount.
  • PATH: Mount point.

You can find all the mounted devices in Android by adb shell then mount. Here is one sample output:

shell@android:/ $ mount
rootfs / rootfs ro,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,relatime,mode=755 0 0
devpts /dev/pts devpts rw,relatime,mode=600 0 0
proc /proc proc rw,relatime 0 0
sysfs /sys sysfs rw,relatime 0 0
none /acct cgroup rw,relatime,cpuacct 0 0
tmpfs /mnt/secure tmpfs rw,relatime,mode=700 0 0
tmpfs /mnt/asec tmpfs rw,relatime,mode=755,gid=1000 0 0
tmpfs /mnt/obb tmpfs rw,relatime,mode=755,gid=1000 0 0
none /dev/cpuctl cgroup rw,relatime,cpu 0 0
/dev/block/platform/omap/omap_hsmmc.0/by-name/system /system ext4 ro,relatime,barrier=1,data=ordered 0 0
/dev/block/platform/omap/omap_hsmmc.0/by-name/efs /factory ext4 ro,relatime,barrier=1,data=ordered 0 0
/dev/block/platform/omap/omap_hsmmc.0/by-name/cache /cache ext4 rw,nosuid,nodev,noatime,errors=panic,barrier=1,nomblk_io_submit,data=ordered 0 0
/dev/block/platform/omap/omap_hsmmc.0/by-name/userdata /data ext4 rw,nosuid,nodev,noatime,errors=panic,barrier=1,nomblk_io_submit,data=ordered 0 0
/sys/kernel/debug /sys/kernel/debug debugfs rw,relatime 0 0
/dev/fuse /mnt/shell/emulated fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0

Then we do the actual copy using package_extract_dir command. This will copy the updated libc.so file.

And finally we unmount the /system partition.

Pack It Up

Inside myupdate directory, use this command to create the zip file.

zip -r9 ../myupdate.zip *

Note that the command is executed inside the myupdate directory, and the zip file is created in parent directory. This is because the META-INF and system directory must be in the root directory of the final zip file.

Sign the OTA Package

Up to this point, the OTA package we just created should be able to applied successfully on custom recoveries like CWM, in which the signature verification is turned off by default.

However, to automate the OTA process, we're using the Android RecoverySystem to reboot the phone and apply the update, in that case, the signature verification is turned on. So we need to sign the package with proper keys, which are platform keys.

Suppose you've get the platform keys named platform.x509.pem and platform.pk8, we can use the signapk.jar tool.

java -jar signapk.jar -w platform.x509.pem platform.pk8 myupdate.zip myupdate-signed.zip

Note that:

  • We need the -w flag to sign the whole zip file.
  • The sequence of the two key files: pem file goes first, then the pk8 file.

This will generate the final OTA package, myupdate-signed.zip, which WILL pass the signature verification of the recovery system.