Merge "Add logic to handle changes to file_contexts during update." into jb-mr2-dev
diff --git a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index c94f7c1..9601e9a 100644
--- a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -56,9 +56,9 @@
 
     private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate";
 
-    private final File updateDir;
-    private final File updateContent;
-    private final File updateVersion;
+    protected final File updateDir;
+    protected final File updateContent;
+    protected final File updateVersion;
 
     public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath,
                                        String updateMetadataPath, String updateVersionPath) {
@@ -222,7 +222,7 @@
         return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
     }
 
-    private void writeUpdate(File dir, File file, byte[] content) throws IOException {
+    protected void writeUpdate(File dir, File file, byte[] content) throws IOException {
         FileOutputStream out = null;
         File tmp = null;
         try {
diff --git a/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
index 748849e..e8337f6 100644
--- a/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
+++ b/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
@@ -18,28 +18,127 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.FileUtils;
 import android.os.SELinux;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Base64;
 import android.util.Slog;
 
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
 public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
 
+    private static final String TAG = "SELinuxPolicyInstallReceiver";
+
+    private static final String sepolicyPath = "sepolicy";
+    private static final String fileContextsPath = "file_contexts";
+    private static final String propertyContextsPath = "property_contexts";
+    private static final String seappContextsPath = "seapp_contexts";
+
     public SELinuxPolicyInstallReceiver() {
-        super("/data/security/", "sepolicy", "metadata/", "version");
+        super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version");
     }
 
-    @Override
-    protected void install(byte[] encodedContent, int version) throws IOException {
-        super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
+    private void backupContexts(File contexts) {
+        new File(contexts, seappContextsPath).renameTo(
+                new File(contexts, seappContextsPath + "_backup"));
+
+        new File(contexts, propertyContextsPath).renameTo(
+                new File(contexts, propertyContextsPath + "_backup"));
+
+        new File(contexts, fileContextsPath).renameTo(
+                new File(contexts, fileContextsPath + "_backup"));
+
+        new File(contexts, sepolicyPath).renameTo(
+                new File(contexts, sepolicyPath + "_backup"));
+    }
+
+    private void copyUpdate(File contexts) {
+        new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath));
+        new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath));
+        new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath));
+        new File(updateDir, sepolicyPath).renameTo(new File(contexts, sepolicyPath));
+    }
+
+    private int readInt(BufferedInputStream reader) throws IOException {
+        int value = 0;
+        for (int i=0; i < 4; i++) {
+            value = (value << 8) | reader.read();
+        }
+        return value;
+    }
+
+    private int[] readChunkLengths(BufferedInputStream bundle) throws IOException {
+        int[] chunks = new int[4];
+        chunks[0] = readInt(bundle);
+        chunks[1] = readInt(bundle);
+        chunks[2] = readInt(bundle);
+        chunks[3] = readInt(bundle);
+        return chunks;
+    }
+
+    private void installFile(File destination, BufferedInputStream stream, int length)
+            throws IOException {
+        byte[] chunk = new byte[length];
+        stream.read(chunk, 0, length);
+        writeUpdate(updateDir, destination, Base64.decode(chunk, Base64.DEFAULT));
+    }
+
+    private void unpackBundle() throws IOException {
+        BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent));
+        int[] chunkLengths = readChunkLengths(stream);
+        installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[0]);
+        installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[1]);
+        installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[2]);
+        installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[3]);
+    }
+
+    private void applyUpdate() throws IOException, ErrnoException {
+        Slog.i(TAG, "Applying SELinux policy");
+        File contexts = new File(updateDir.getParentFile(), "contexts");
+        File current = new File(updateDir.getParentFile(), "current");
+        File update = new File(updateDir.getParentFile(), "update");
+        File tmp = new File(updateDir.getParentFile(), "tmp");
+        if (current.exists()) {
+            Libcore.os.symlink(updateDir.getPath(), update.getPath());
+            Libcore.os.rename(update.getPath(), current.getPath());
+        } else {
+            Libcore.os.symlink(updateDir.getPath(), current.getPath());
+        }
+        contexts.mkdirs();
+        backupContexts(contexts);
+        copyUpdate(contexts);
+        Libcore.os.symlink(contexts.getPath(), tmp.getPath());
+        Libcore.os.rename(tmp.getPath(), current.getPath());
+        SystemProperties.set("selinux.reload_policy", "1");
+    }
+
+    private void setEnforcingMode(Context context) {
+        boolean mode = Settings.Global.getInt(context.getContentResolver(),
+            Settings.Global.SELINUX_STATUS, 0) == 1;
+        SELinux.setSELinuxEnforce(mode);
     }
 
     @Override
     protected void postInstall(Context context, Intent intent) {
-           boolean mode = Settings.Global.getInt(context.getContentResolver(),
-                                                Settings.Global.SELINUX_STATUS, 0) == 1;
-           SELinux.setSELinuxEnforce(mode);
+        try {
+            unpackBundle();
+            applyUpdate();
+            setEnforcingMode(context);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "SELinux policy update malformed: ", e);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not update selinux policy: ", e);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Could not update selinux policy: ", e);
+        }
     }
 }