Saturday, January 9, 2010

Change files on the read-only filesystem of your Android phone

I am currently working on an small application that needs to load kernel modules at the startup of the Android phone. I could eventually start up an Activity or Service using a trigger on the BOOT_COMPLETED_ACTION, (howto), but this creates some complexity as I need to load compcache kernel modules requiring lots of free memory.
Using a boot script is much better.
(Un)fortunately an application cannot change things in the /system partition as it is mounted in read only.
# mount
rootfs on / type rootfs (ro)
tmpfs on /dev type tmpfs (rw,mode=755)
devpts on /dev/pts type devpts (rw,mode=600)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
tmpfs on /sqlite_stmt_journals type tmpfs (rw,size=4096k)
/dev/block/mtdblock3 on /system type yaffs2 (ro)
/dev/block/mtdblock5 on /data type yaffs2 (rw,nosuid,nodev)
/dev/block/mtdblock4 on /cache type yaffs2 (rw,nosuid,nodev)
/dev/block/mmcblk0p2 on /system/sd type ext2 (rw,noatime,nodiratime,errors=continue)
/dev/block//vold/179:1 on /sdcard type vfat (rw,dirsync,nosuid,nodev,noexec,uid=1000,...)
Fortunately, as I have root support on my phone, I can simply remount the /system partition as rw, do my change and then remount it back to ro.
Here is how you do this in java code:
public static void saveCommandsToBootFile(String script, String filename) {
 // first remount filesystem in rw
 // save the file
 // remount the filesystem back to ro
 String command = 
  "mount -o remount,rw /system \n" +
  "echo '" + script.replace("'", "\\'") + "' > " + filename + " \n" +
  "mount -o remount,ro /system \n";
 executeCommand(command);
}

public static void executeCommand(String command) {
 Log.d(MainActivity.LOG_TAG, "Executing the following commands: \n" + command);
 Process process;
 try {
  process = Runtime.getRuntime().exec("su -c sh");
  DataOutputStream os = new DataOutputStream(process.getOutputStream());
  //DataInputStream osRes = new DataInputStream(process.getInputStream());
  os.writeBytes(command); os.flush();
  // and finally close the shell
  os.writeBytes("exit\n"); os.flush();
  process.waitFor();
 } catch (IOException e) {
  e.printStackTrace();
 } catch (InterruptedException e) {
  e.printStackTrace();
 } 
}
Some remarks you could have:
  • I didn't use java to write the file: Indeed, my java application runs in a limited environment and has no rights to write to /system/, even mounted rw. I would need to write the file temporary somewhere else, to then move it back to the final location. This looks a little to complex.
  • I escape the ' quote in the script to prevent my echo foo > bar failing.
  • An uncontrolled filename could result in command injection as root !  (Thanks to Steve Nugen from UNO to report that!)