support for per-partition fs_type

Include the recovery.fstab file in the recovery image.  Remove the
global fs_type and partition_type values from the target-files
key/value dict, and parse the recovery.fstab file instead to find
types for each partition.

(Cherrypicked from gingerbread w/some edits to resolve conflicts.)

Change-Id: Ic3ed85ac5672d8fe20280dacf43d5b82053311bb
diff --git a/core/Makefile b/core/Makefile
index 1bfa055b..36477d7 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -610,11 +610,16 @@
 recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res))
 recovery_resource_deps := $(shell find $(recovery_resources_common) \
   $(recovery_resources_private) -type f)
+recovery_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery.fstab))
 
 ifeq ($(recovery_resources_private),)
   $(info No private recovery resources for TARGET_DEVICE $(TARGET_DEVICE))
 endif
 
+ifeq ($(recovery_fstab),)
+  $(info No recovery.fstab for TARGET_DEVICE $(TARGET_DEVICE))
+endif
+
 INTERNAL_RECOVERYIMAGE_ARGS := \
 	$(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \
 	--kernel $(recovery_kernel) \
@@ -656,6 +661,7 @@
 		$(recovery_initrc) $(recovery_kernel) \
 		$(INSTALLED_2NDBOOTLOADER_TARGET) \
 		$(recovery_build_prop) $(recovery_resource_deps) \
+		$(recovery_fstab) \
 		$(RECOVERY_INSTALL_OTA_KEYS)
 	@echo ----- Making recovery image ------
 	rm -rf $(TARGET_RECOVERY_OUT)
@@ -672,6 +678,8 @@
 	cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/
 	$(foreach item,$(recovery_resources_private), \
 	  cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)
+	$(foreach item,$(recovery_fstab), \
+	  cp -f $(item) $(TARGET_RECOVERY_ROOT_OUT)/etc/recovery.fstab)
 	cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys
 	cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) \
 	        > $(TARGET_RECOVERY_ROOT_OUT)/default.prop
@@ -1058,15 +1066,6 @@
 ifdef BOARD_USERDATAIMAGE_PARTITION_SIZE
 	$(hide) echo "userdata_size=$(call image-size-from-data-size,$(BOARD_USERDATAIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/misc_info.txt
 endif
-ifneq (,$(INTERNAL_USERIMAGES_EXT_VARIANT))
-	$(hide) echo "fs_type=$(INTERNAL_USERIMAGES_EXT_VARIANT)" >> $(zip_root)/META/misc_info.txt
-	$(hide) echo "partition_type=EMMC" >> $(zip_root)/META/misc_info.txt
-	@# TODO: where is the right place to get this path from?  BoardConfig.mk?
-	$(hide) echo "partition_path=/dev/block/platform/sdhci-tegra.3/by-name/" >> $(zip_root)/META/misc_info.txt
-else
-	$(hide) echo "fs_type=yaffs2" >> $(zip_root)/META/misc_info.txt
-	$(hide) echo "partition_type=MTD" >> $(zip_root)/META/misc_info.txt
-endif
 	$(hide) echo "tool_extensions=$(tool_extensions)" >> $(zip_root)/META/misc_info.txt
 ifdef mkyaffs2_extra_flags
 	$(hide) echo "mkyaffs2_extra_flags=$(mkyaffs2_extra_flags)" >> $(zip_root)/META/misc_info.txt
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 98f70bd..8884fe8 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -72,9 +72,6 @@
     # ok if misc_info.txt doesn't exist
     pass
 
-  if "fs_type" not in d: d["fs_type"] = "yaffs2"
-  if "partition_type" not in d: d["partition_type"] = "MTD"
-
   # backwards compatibility: These values used to be in their own
   # files.  Look for them, in case we're processing an old
   # target_files zip.
@@ -123,8 +120,45 @@
   makeint("recovery_size")
   makeint("boot_size")
 
+  d["fstab"] = LoadRecoveryFSTab(zip)
+  if not d["fstab"]:
+    if "fs_type" not in d: d["fs_type"] = "yaffs2"
+    if "partition_type" not in d: d["partition_type"] = "MTD"
+
   return d
 
+def LoadRecoveryFSTab(zip):
+  class Partition(object):
+    pass
+
+  try:
+    data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
+  except KeyError:
+    # older target-files that doesn't have a recovery.fstab; fall back
+    # to the fs_type and partition_type keys.
+    return
+
+  d = {}
+  for line in data.split("\n"):
+    line = line.strip()
+    if not line or line.startswith("#"): continue
+    pieces = line.split()
+    if not (3 <= len(pieces) <= 4):
+      raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
+
+    p = Partition()
+    p.mount_point = pieces[0]
+    p.fs_type = pieces[1]
+    p.device = pieces[2]
+    if len(pieces) == 4:
+      p.device2 = pieces[3]
+    else:
+      p.device2 = None
+
+    d[p.mount_point] = p
+  return d
+
+
 def DumpInfoDict(d):
   for k, v in sorted(d.items()):
     print "%-25s = (%s) %s" % (k, type(v).__name__, v)
@@ -308,12 +342,18 @@
   any, for the given target.  Raise exception if the data is too big.
   Print a warning if the data is nearing the maximum size."""
 
-  fs_type = info_dict.get("fs_type", None)
-  if not fs_type: return
-
   if target.endswith(".img"): target = target[:-4]
-  limit = info_dict.get(target + "_size", None)
-  if limit is None: return
+  mount_point = "/" + target
+
+  if info_dict["fstab"]:
+    if mount_point == "/userdata": mount_point = "/data"
+    p = info_dict["fstab"][mount_point]
+    fs_type = p.fs_type
+    limit = info_dict.get(p.device + "_size", None)
+  else:
+    fs_type = info_dict.get("fs_type", None)
+    limit = info_dict.get(target + "_size", None)
+  if not fs_type or not limit: return
 
   if fs_type == "yaffs2":
     # image size should be increased by 1/64th to account for the
diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py
index 328700f..a61ecf5 100644
--- a/tools/releasetools/edify_generator.py
+++ b/tools/releasetools/edify_generator.py
@@ -21,6 +21,10 @@
   """Class to generate scripts in the 'edify' recovery script language
   used from donut onwards."""
 
+  # map recovery.fstab's fs_types to mount/format "partition types"
+  PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
+                      "ext4": "EMMC", "emmc": "EMMC" }
+
   def __init__(self, version, info):
     self.script = []
     self.mounts = set()
@@ -131,15 +135,22 @@
     available on /cache."""
     self.script.append("assert(apply_patch_space(%d));" % (amount,))
 
-  def Mount(self, what, mount_point):
-    """Mount the given 'what' at the given path.  'what' should be a
-    partition name for an MTD partition, or a block device for
-    anything else."""
-    what = self.info.get("partition_path", "") + what
-    self.script.append('mount("%s", "%s", "%s", "%s");' %
-                       (self.info["fs_type"], self.info["partition_type"],
-                        what, mount_point))
-    self.mounts.add(mount_point)
+  def Mount(self, mount_point):
+    """Mount the partition with the given mount_point."""
+    fstab = self.info.get("fstab", None)
+    if fstab:
+      p = fstab[mount_point]
+      self.script.append('mount("%s", "%s", "%s", "%s");' %
+                         (p.fs_type, self.PARTITION_TYPES[p.fs_type],
+                          p.device, p.mount_point))
+      self.mounts.add(p.mount_point)
+    else:
+      what = mount_point.lstrip("/")
+      what = self.info.get("partition_path", "") + what
+      self.script.append('mount("%s", "%s", "%s", "%s");' %
+                         (self.info["fs_type"], self.info["partition_type"],
+                          what, mount_point))
+      self.mounts.add(mount_point)
 
   def UnpackPackageDir(self, src, dst):
     """Unpack a given directory from the OTA package into the given
@@ -158,11 +169,20 @@
     self.script.append('ui_print("%s");' % (message,))
 
   def FormatPartition(self, partition):
-    """Format the given partition."""
-    partition = self.info.get("partition_path", "") + partition
-    self.script.append('format("%s", "%s", "%s");' %
-                       (self.info["fs_type"], self.info["partition_type"],
-                        partition))
+    """Format the given partition, specified by its mount point (eg,
+    "/system")."""
+
+    fstab = self.info.get("fstab", None)
+    if fstab:
+      p = fstab[partition]
+      self.script.append('format("%s", "%s", "%s");' %
+                         (p.fs_type, self.PARTITION_TYPES[p.fs_type], p.device))
+    else:
+      # older target-files without per-partition types
+      partition = self.info.get("partition_path", "") + partition
+      self.script.append('format("%s", "%s", "%s");' %
+                         (self.info["fs_type"], self.info["partition_type"],
+                          partition))
 
   def DeleteFiles(self, file_list):
     """Delete all files in file_list."""
@@ -196,24 +216,42 @@
       self.script.append(
           'write_firmware_image("PACKAGE:%s", "%s");' % (fn, kind))
 
-  def WriteRawImage(self, partition, fn):
-    """Write the given package file into the given partition."""
+  def WriteRawImage(self, mount_point, fn):
+    """Write the given package file into the partition for the given
+    mount point."""
 
-    if self.info["partition_type"] == "MTD":
-      self.script.append(
-          ('assert(package_extract_file("%(fn)s", "/tmp/%(partition)s.img"),\n'
-           '       write_raw_image("/tmp/%(partition)s.img", "%(partition)s"),\n'
-           '       delete("/tmp/%(partition)s.img"));')
-          % {'partition': partition, 'fn': fn})
-    elif self.info["partition_type"] == "EMMC":
-      self.script.append(
-          ('package_extract_file("%(fn)s", "%(dir)s%(partition)s");')
-          % {'partition': partition, 'fn': fn,
-             'dir': self.info.get("partition_path", ""),
-             })
+    fstab = self.info["fstab"]
+    if fstab:
+      p = fstab[mount_point]
+      partition_type = self.PARTITION_TYPES[p.fs_type]
+      args = {'device': p.device, 'fn': fn}
+      if partition_type == "MTD":
+        self.script.append(
+            ('assert(package_extract_file("%(fn)s", "/tmp/%(device)s.img"),\n'
+             '       write_raw_image("/tmp/%(device)s.img", "%(device)s"),\n'
+             '       delete("/tmp/%(device)s.img"));') % args)
+      elif partition_type == "EMMC":
+        self.script.append(
+            'package_extract_file("%(fn)s", "%(device)s");' % args)
+      else:
+        raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
     else:
-      raise ValueError("don't know how to write \"%s\" partitions" %
-                       (self.info["partition_type"],))
+      # backward compatibility with older target-files that lack recovery.fstab
+      if self.info["partition_type"] == "MTD":
+        self.script.append(
+            ('assert(package_extract_file("%(fn)s", "/tmp/%(partition)s.img"),\n'
+             '       write_raw_image("/tmp/%(partition)s.img", "%(partition)s"),\n'
+             '       delete("/tmp/%(partition)s.img"));')
+            % {'partition': partition, 'fn': fn})
+      elif self.info["partition_type"] == "EMMC":
+        self.script.append(
+            ('package_extract_file("%(fn)s", "%(dir)s%(partition)s");')
+            % {'partition': partition, 'fn': fn,
+               'dir': self.info.get("partition_path", ""),
+               })
+      else:
+        raise ValueError("don't know how to write \"%s\" partitions" %
+                         (self.info["partition_type"],))
 
   def SetPermissions(self, fn, uid, gid, mode):
     """Set file ownership and permissions."""
diff --git a/tools/releasetools/img_from_target_files b/tools/releasetools/img_from_target_files
index c756086..1447b16 100755
--- a/tools/releasetools/img_from_target_files
+++ b/tools/releasetools/img_from_target_files
@@ -61,9 +61,10 @@
   img = tempfile.NamedTemporaryFile()
 
   build_command = []
-  if OPTIONS.info_dict.get("fs_type", "").startswith("ext"):
+  if OPTIONS.info_dict["fstab"]["/data"].fs_type.startswith("ext"):
     build_command = ["mkuserimg.sh",
-                     user_dir, img.name, OPTIONS.info_dict["fs_type"], "userdata"]
+                     user_dir, img.name,
+                     OPTIONS.info_dict["fstab"]["/data"].fs_type, "userdata"]
     if "userdata_size" in OPTIONS.info_dict:
       build_command.append(str(OPTIONS.info_dict["userdata_size"]))
   else:
@@ -108,10 +109,10 @@
       pass
 
   build_command = []
-  if OPTIONS.info_dict.get("fs_type", "").startswith("ext"):
+  if OPTIONS.info_dict["fstab"]["/system"].fs_type.startswith("ext"):
     build_command = ["mkuserimg.sh",
                      os.path.join(OPTIONS.input_tmp, "system"), img.name,
-                     OPTIONS.info_dict["fs_type"], "system"]
+                     OPTIONS.info_dict["fstab"]["/system"].fs_type, "system"]
     if "system_img" in OPTIONS.info_dict:
       build_command.append(str(OPTIONS.info_dict["system_size"]))
   else:
diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files
index fdd477d..b963531 100755
--- a/tools/releasetools/ota_from_target_files
+++ b/tools/releasetools/ota_from_target_files
@@ -325,15 +325,33 @@
   common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
   Item.Get("system/recovery-from-boot.p", dir=False)
 
+
+  fstab = OPTIONS.info_dict["fstab"]
+  if fstab:
+    # TODO: this is duplicated from edify_generator.py; fix.
+    PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
+                        "ext4": "EMMC", "emmc": "EMMC" }
+
+    boot_type = PARTITION_TYPES[fstab["/boot"].fs_type]
+    boot_device = fstab["/boot"].device
+
+    recovery_type = PARTITION_TYPES[fstab["/recovery"].fs_type]
+    recovery_device = fstab["/recovery"].device
+  else:
+    # backwards compatibility for target files w/o recovery.fstab
+    boot_type = recovery_type = OPTIONS.info_dict["partition_type"]
+    boot_device = OPTIONS.info_dict.get("partition_path", "") + "boot"
+    recovery_device = OPTIONS.info_dict.get("partition_path", "") + "recovery"
+
   # Images with different content will have a different first page, so
   # we check to see if this recovery has already been installed by
   # testing just the first 2k.
   HEADER_SIZE = 2048
   header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
   sh = """#!/system/bin/sh
-if ! applypatch -c %(partition_type)s:%(partition_path)srecovery:%(header_size)d:%(header_sha1)s; then
+if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(header_size)d:%(header_sha1)s; then
   log -t recovery "Installing new recovery image"
-  applypatch %(partition_type)s:%(partition_path)sboot:%(boot_size)d:%(boot_sha1)s %(partition_type)s:%(partition_path)srecovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
+  applypatch %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
 else
   log -t recovery "Recovery image already installed"
 fi
@@ -343,8 +361,10 @@
         'header_sha1': header_sha1,
         'recovery_size': recovery_img.size,
         'recovery_sha1': recovery_img.sha1,
-        'partition_type': OPTIONS.info_dict["partition_type"],
-        'partition_path': OPTIONS.info_dict.get("partition_path", ""),
+        'boot_type': boot_type,
+        'boot_device': boot_device,
+        'recovery_type': recovery_type,
+        'recovery_device': recovery_device,
         }
   common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
   return Item.Get("system/etc/install-recovery.sh", dir=False)
@@ -379,10 +399,10 @@
   script.ShowProgress(0.5, 0)
 
   if OPTIONS.wipe_user_data:
-    script.FormatPartition("userdata")
+    script.FormatPartition("/data")
 
-  script.FormatPartition("system")
-  script.Mount("system", "/system")
+  script.FormatPartition("/system")
+  script.Mount("/system")
   script.UnpackPackageDir("recovery", "/system")
   script.UnpackPackageDir("system", "/system")
 
@@ -407,7 +427,7 @@
   script.ShowProgress(0.2, 0)
 
   script.ShowProgress(0.2, 10)
-  script.WriteRawImage("boot", "boot.img")
+  script.WriteRawImage("/boot", "boot.img")
 
   script.ShowProgress(0.1, 0)
   device_specific.FullOTA_InstallEnd()
@@ -527,7 +547,7 @@
   metadata["pre-build"] = source_fp
   metadata["post-build"] = target_fp
 
-  script.Mount("system", "/system")
+  script.Mount("/system")
   script.AssertSomeFingerprint(source_fp, target_fp)
 
   source_boot = common.File("/tmp/boot.img",
@@ -592,7 +612,7 @@
 
   if OPTIONS.wipe_user_data:
     script.Print("Erasing user data...")
-    script.FormatPartition("userdata")
+    script.FormatPartition("/data")
 
   script.Print("Removing unneeded files...")
   script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +