Update OTA to understand SELinux labels and capabilities

Update the OTA generation script to understand SELinux file
labels and file capabilities.

Make fs_config aware of SELinux labels and file capabilities, and
optionally output those elements whenever we output the
UID / GID / file perms. The information is emitted as a key=value pair
to allow for future extensibility.

Pass the SELinux file label and capabilities to the newly created
set_metadata() and set_metadata_recursive() calls. When the OTA
script fixes up filesystem permissions, it will also fix up the SELinux
labels and file capabilities.

If no SELinux label and capabilities are available for the file, use
the old set_perm and set_perm_recursive calls.

Bug: 8985290
Bug: 10183961
Bug: 10186213
Change-Id: I4fcfb2c234dbfb965cee9e62f060092a4274d22d
diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py
index 9ef1926..2c3b9e7 100644
--- a/tools/releasetools/edify_generator.py
+++ b/tools/releasetools/edify_generator.py
@@ -217,14 +217,33 @@
       else:
         raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
 
-  def SetPermissions(self, fn, uid, gid, mode):
+  def SetPermissions(self, fn, uid, gid, mode, selabel, capabilities):
     """Set file ownership and permissions."""
-    self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
+    if not self.info.get("use_set_metadata", False):
+      self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
+    else:
+      if capabilities is None: capabilities = "0x0"
+      cmd = 'set_metadata("%s", "uid", %d, "gid", %d, "mode", 0%o, ' \
+          '"capabilities", %s' % (fn, uid, gid, mode, capabilities)
+      if selabel is not None:
+        cmd += ', "selabel", "%s"' % ( selabel )
+      cmd += ');'
+      self.script.append(cmd)
 
-  def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode):
+  def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode, selabel, capabilities):
     """Recursively set path ownership and permissions."""
-    self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
-                       % (uid, gid, dmode, fmode, fn))
+    if not self.info.get("use_set_metadata", False):
+      self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
+                         % (uid, gid, dmode, fmode, fn))
+    else:
+      if capabilities is None: capabilities = "0x0"
+      cmd = 'set_metadata_recursive("%s", "uid", %d, "gid", %d, ' \
+          '"dmode", 0%o, "fmode", 0%o, "capabilities", %s' \
+          % (fn, uid, gid, dmode, fmode, capabilities)
+      if selabel is not None:
+        cmd += ', "selabel", "%s"' % ( selabel )
+      cmd += ');'
+      self.script.append(cmd)
 
   def MakeSymlinks(self, symlink_list):
     """Create symlinks, given a list of (dest, link) pairs."""
diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files
index 1e1d04e..a6b9b69 100755
--- a/tools/releasetools/ota_from_target_files
+++ b/tools/releasetools/ota_from_target_files
@@ -117,6 +117,8 @@
     self.uid = None
     self.gid = None
     self.mode = None
+    self.selabel = None
+    self.capabilities = None
     self.dir = dir
 
     if name:
@@ -147,82 +149,88 @@
   @classmethod
   def GetMetadata(cls, input_zip):
 
-    try:
-      # See if the target_files contains a record of what the uid,
-      # gid, and mode is supposed to be.
-      output = input_zip.read("META/filesystem_config.txt")
-    except KeyError:
-      # Run the external 'fs_config' program to determine the desired
-      # uid, gid, and mode for every Item object.  Note this uses the
-      # one in the client now, which might not be the same as the one
-      # used when this target_files was built.
-      p = common.Run(["fs_config"], stdin=subprocess.PIPE,
-                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-      suffix = { False: "", True: "/" }
-      input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
-                       for i in cls.ITEMS.itervalues() if i.name])
-      output, error = p.communicate(input)
-      assert not error
+    # The target_files contains a record of what the uid,
+    # gid, and mode are supposed to be.
+    output = input_zip.read("META/filesystem_config.txt")
 
     for line in output.split("\n"):
       if not line: continue
-      name, uid, gid, mode = line.split()
+      columns = line.split()
+      name, uid, gid, mode = columns[:4]
+      selabel = None
+      capabilities = None
+
+      # After the first 4 columns, there are a series of key=value
+      # pairs. Extract out the fields we care about.
+      for element in columns[4:]:
+        key, value = element.split("=")
+        if key == "selabel":
+          selabel = value
+        if key == "capabilities":
+          capabilities = value
+
       i = cls.ITEMS.get(name, None)
       if i is not None:
         i.uid = int(uid)
         i.gid = int(gid)
         i.mode = int(mode, 8)
+        i.selabel = selabel
+        i.capabilities = capabilities
         if i.dir:
           i.children.sort(key=lambda i: i.name)
 
     # set metadata for the files generated by this script.
     i = cls.ITEMS.get("system/recovery-from-boot.p", None)
-    if i: i.uid, i.gid, i.mode = 0, 0, 0644
+    if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0644, None, None
     i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
-    if i: i.uid, i.gid, i.mode = 0, 0, 0544
+    if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0544, None, None
 
   def CountChildMetadata(self):
-    """Count up the (uid, gid, mode) tuples for all children and
-    determine the best strategy for using set_perm_recursive and
+    """Count up the (uid, gid, mode, selabel, capabilities) tuples for
+    all children and determine the best strategy for using set_perm_recursive and
     set_perm to correctly chown/chmod all the files to their desired
     values.  Recursively calls itself for all descendants.
 
-    Returns a dict of {(uid, gid, dmode, fmode): count} counting up
+    Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count} counting up
     all descendants of this node.  (dmode or fmode may be None.)  Also
     sets the best_subtree of each directory Item to the (uid, gid,
-    dmode, fmode) tuple that will match the most descendants of that
-    Item.
+    dmode, fmode, selabel, capabilities) tuple that will match the most
+    descendants of that Item.
     """
 
     assert self.dir
-    d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
+    d = self.descendants = {(self.uid, self.gid, self.mode, None, self.selabel, self.capabilities): 1}
     for i in self.children:
       if i.dir:
         for k, v in i.CountChildMetadata().iteritems():
           d[k] = d.get(k, 0) + v
       else:
-        k = (i.uid, i.gid, None, i.mode)
+        k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities)
         d[k] = d.get(k, 0) + 1
 
-    # Find the (uid, gid, dmode, fmode) tuple that matches the most
-    # descendants.
+    # Find the (uid, gid, dmode, fmode, selabel, capabilities)
+    # tuple that matches the most descendants.
 
     # First, find the (uid, gid) pair that matches the most
     # descendants.
     ug = {}
-    for (uid, gid, _, _), count in d.iteritems():
+    for (uid, gid, _, _, _, _), count in d.iteritems():
       ug[(uid, gid)] = ug.get((uid, gid), 0) + count
     ug = MostPopularKey(ug, (0, 0))
 
-    # Now find the dmode and fmode that match the most descendants
-    # with that (uid, gid), and choose those.
+    # Now find the dmode, fmode, selabel, and capabilities that match
+    # the most descendants with that (uid, gid), and choose those.
     best_dmode = (0, 0755)
     best_fmode = (0, 0644)
+    best_selabel = (0, None)
+    best_capabilities = (0, None)
     for k, count in d.iteritems():
       if k[:2] != ug: continue
       if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
       if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
-    self.best_subtree = ug + (best_dmode[1], best_fmode[1])
+      if k[4] is not None and count >= best_selabel[0]: best_selabel = (count, k[4])
+      if k[5] is not None and count >= best_capabilities[0]: best_capabilities = (count, k[5])
+    self.best_subtree = ug + (best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1])
 
     return d
 
@@ -234,7 +242,7 @@
     self.CountChildMetadata()
 
     def recurse(item, current):
-      # current is the (uid, gid, dmode, fmode) tuple that the current
+      # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple that the current
       # item (and all its children) have already been set to.  We only
       # need to issue set_perm/set_perm_recursive commands if we're
       # supposed to be something different.
@@ -244,17 +252,21 @@
           current = item.best_subtree
 
         if item.uid != current[0] or item.gid != current[1] or \
-           item.mode != current[2]:
-          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
+           item.mode != current[2] or item.selabel != current[4] or \
+           item.capabilities != current[5]:
+          script.SetPermissions("/"+item.name, item.uid, item.gid,
+                                item.mode, item.selabel, item.capabilities)
 
         for i in item.children:
           recurse(i, current)
       else:
         if item.uid != current[0] or item.gid != current[1] or \
-               item.mode != current[3]:
-          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
+               item.mode != current[3] or item.selabel != current[4] or \
+               item.capabilities != current[5]:
+          script.SetPermissions("/"+item.name, item.uid, item.gid,
+                                item.mode, item.selabel, item.capabilities)
 
-    recurse(self, (-1, -1, -1, -1))
+    recurse(self, (-1, -1, -1, -1, None, None))
 
 
 def CopySystemFiles(input_zip, output_zip=None,
@@ -733,7 +745,7 @@
   for item in deferred_patch_list:
     fn, tf, sf, size, _ = item
     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
-  script.SetPermissions("/system/build.prop", 0, 0, 0644)
+  script.SetPermissions("/system/build.prop", 0, 0, 0644, None, None)
 
   script.AddToZip(target_zip, output_zip)
   WriteMetadata(metadata, output_zip)