store filesystem config info in target files at build time

Store a dump of the desired uid/gid/mode for every system file in the
target_files zip.  Modify ota_from_target_files to use this stored
information when it is available, instead of running fs_config from
the current client (which might be out of sync from the one where the
target_files zip was built).

b/2516887 - New android_filesystem_config.h needed

Change-Id: I8409a0265d1d50daad9c2bc033c99b74b8931b20
diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files
index 70ab55f..0454d6f 100755
--- a/tools/releasetools/ota_from_target_files
+++ b/tools/releasetools/ota_from_target_files
@@ -137,26 +137,41 @@
     return cls.ITEMS[name]
 
   @classmethod
-  def GetMetadata(cls):
-    """Run the external 'fs_config' program to determine the desired
-    uid, gid, and mode for every Item object."""
-    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
+  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])
+      output2, error = p.communicate(input)
+      assert not error
 
     for line in output.split("\n"):
       if not line: continue
       name, uid, gid, mode = line.split()
-      i = cls.ITEMS[name]
-      i.uid = int(uid)
-      i.gid = int(gid)
-      i.mode = int(mode, 8)
-      if i.dir:
-        i.children.sort(key=lambda i: i.name)
+      i = cls.ITEMS.get(name, None)
+      if i is not None:
+        i.uid = int(uid)
+        i.gid = int(gid)
+        i.mode = int(mode, 8)
+        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
+    i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
+    if i: i.uid, i.gid, i.mode = 0, 0, 0544
 
   def CountChildMetadata(self):
     """Count up the (uid, gid, mode) tuples for all children and
@@ -369,16 +384,9 @@
       os.path.join(OPTIONS.input_tmp, "BOOT")))
   recovery_img = File("recovery.img", common.BuildBootableImage(
       os.path.join(OPTIONS.input_tmp, "RECOVERY")))
-  i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
+  MakeRecoveryPatch(output_zip, recovery_img, boot_img)
 
-  Item.GetMetadata()
-
-  # GetMetadata uses the data in android_filesystem_config.h to assign
-  # the uid/gid/mode of all files.  We want to override that for the
-  # recovery patching shell script to make it executable.
-  i.uid = 0
-  i.gid = 0
-  i.mode = 0544
+  Item.GetMetadata(input_zip)
   Item.Get("system").SetPermissions(script)
 
   common.CheckSize(boot_img.data, "boot.img")
@@ -746,8 +754,7 @@
     # partition, include the binaries and image files from recovery in
     # the boot image (though not in the ramdisk) so they can be used
     # as fodder for constructing the recovery image.
-    recovery_sh_item = MakeRecoveryPatch(output_zip,
-                                         target_recovery, target_boot)
+    MakeRecoveryPatch(output_zip, target_recovery, target_boot)
     script.DeleteFiles(["/system/recovery-from-boot.p",
                         "/system/etc/install-recovery.sh"])
     print "recovery image changed; including as patch from boot."
@@ -760,11 +767,7 @@
 
   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
   temp_script = script.MakeTemporary()
-  Item.GetMetadata()
-  if updating_recovery:
-    recovery_sh_item.uid = 0
-    recovery_sh_item.gid = 0
-    recovery_sh_item.mode = 0544
+  Item.GetMetadata(target_zip)
   Item.Get("system").SetPermissions(temp_script)
 
   # Note that this call will mess up the tree of Items, so make sure