Support to build image of root file system with /system and ramdisk combined.

Added support to build system.img that combines contents of /system and
the ramdisk, and can be mounted at the root of the file system.
To enable this feature, define BoardConfig.mk variable:
BOARD_BUILD_SYSTEM_ROOT_IMAGE := true

Ideally we would just change TARGET_OUT (the path of the staging system
directory) to under TARGET_ROOT_OUT. But at this point many places in
the build system assume TARGET_OUT is independent of TARGET_ROOT_OUT and
we can't make it easily configurable.
Instead this implementation takes the least intrusive approach:
We don't change TARGET_OUT or TARGET_ROOT_OUT. We just assemble a
temporary staging directory that contains contents of both TARGET_OUT
and TARGET_ROOT_OUT, in build_image.BuildImage() of
tools/releasetools/build_image.py.
When build_image.py is directly called from the makefile, we pass in the
parameters from the global dictionary; when build_image.BuildImage() is
called from add_img_to_target_files.py, we need to override values to
point to files extracted from the target_files zip file.
We need to combine the fs_config files of both /system and ramdisk,
when fs_config is enabled.

Also this change refactored build_image.BuildImage() by moving the extra
parameters to the image property dictionary.

Bug:19868522
Change-Id: Iafc467a0e3427b0d6ad3b575abcc98ddcc9ea0f1
diff --git a/core/Makefile b/core/Makefile
index 478c966..353b49c 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -717,6 +717,9 @@
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity_signer_cmd=$(VERITY_SIGNER)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION),$(hide) echo "system_verity_block_device=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION),$(hide) echo "vendor_verity_block_device=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION)" >> $(1))
+$(if $(filter true,$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)),\
+    $(hide) echo "system_root_image=true" >> $(1);\
+    echo "ramdisk_dir=$(TARGET_ROOT_OUT)" >> $(1))
 $(if $(2),$(hide) $(foreach kv,$(2),echo "$(kv)" >> $(1);))
 endef
 
@@ -1417,7 +1420,7 @@
 	$(hide) mkdir -p $(zip_root)/META
 	$(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt
 	$(hide) if test -e $(tool_extensions)/releasetools.py; then $(ACP) $(tool_extensions)/releasetools.py $(zip_root)/META/; fi
-	$(hide)	echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt
+	$(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt
 	$(hide) echo "recovery_api_version=$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/misc_info.txt
 	$(hide) echo "fstab_version=$(PRIVATE_RECOVERY_FSTAB_VERSION)" >> $(zip_root)/META/misc_info.txt
 ifdef BOARD_FLASH_BLOCK_SIZE
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index 8bbe452..b3d2ede 100755
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -135,11 +135,20 @@
   fc_config = os.path.join(input_dir, "BOOT/RAMDISK/file_contexts")
   if not os.path.exists(fc_config): fc_config = None
 
+  # Override values loaded from info_dict.
+  if fs_config:
+    image_props["fs_config"] = fs_config
+  if fc_config:
+    image_props["selinux_fc"] = fc_config
+  if block_list:
+    image_props["block_list"] = block_list
+  if image_props.get("system_root_image") == "true":
+    image_props["ramdisk_dir"] = os.path.join(input_dir, "BOOT/RAMDISK")
+    image_props["ramdisk_fs_config"] = os.path.join(
+        input_dir, "META/boot_filesystem_config.txt")
+
   succ = build_image.BuildImage(os.path.join(input_dir, what),
-                                image_props, img,
-                                fs_config=fs_config,
-                                fc_config=fc_config,
-                                block_list=block_list)
+                                image_props, img)
   assert succ, "build " + what + ".img image failed"
 
   return img
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 692ec93..97353df 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -198,25 +198,39 @@
   shutil.rmtree(tempdir_name, ignore_errors=True)
   return True
 
-def BuildImage(in_dir, prop_dict, out_file,
-               fs_config=None,
-               fc_config=None,
-               block_list=None):
+def BuildImage(in_dir, prop_dict, out_file):
   """Build an image to out_file from in_dir with property prop_dict.
 
   Args:
     in_dir: path of input directory.
     prop_dict: property dictionary.
     out_file: path of the output image file.
-    fs_config: path to the fs_config file (typically
-      META/filesystem_config.txt).  If None then the configuration in
-      the local client will be used.
-    fc_config: path to the SELinux file_contexts file.  If None then
-      the value from prop_dict['selinux_fc'] will be used.
 
   Returns:
     True iff the image is built successfully.
   """
+  # system_root_image=true: build a system.img that combines the contents of /system
+  # and the ramdisk, and can be mounted at the root of the file system.
+  origin_in = in_dir
+  fs_config = prop_dict.get("fs_config")
+  if (prop_dict.get("system_root_image") == "true"
+      and prop_dict["mount_point"] == "system"):
+    in_dir = tempfile.mkdtemp()
+    # Change the mount point to "/"
+    prop_dict["mount_point"] = "/"
+    if fs_config:
+      # We need to merge the fs_config files of system and ramdisk.
+      fd, merged_fs_config = tempfile.mkstemp(prefix="root_fs_config",
+                                              suffix=".txt")
+      os.close(fd)
+      with open(merged_fs_config, "w") as fw:
+        if "ramdisk_fs_config" in prop_dict:
+          with open(prop_dict["ramdisk_fs_config"]) as fr:
+            fw.writelines(fr.readlines())
+        with open(fs_config) as fr:
+          fw.writelines(fr.readlines())
+      fs_config = merged_fs_config
+
   build_command = []
   fs_type = prop_dict.get("fs_type", "")
   run_fsck = False
@@ -244,22 +258,18 @@
       build_command.extend(["-j", prop_dict["journal_size"]])
     if "timestamp" in prop_dict:
       build_command.extend(["-T", str(prop_dict["timestamp"])])
-    if fs_config is not None:
+    if fs_config:
       build_command.extend(["-C", fs_config])
-    if block_list is not None:
-      build_command.extend(["-B", block_list])
+    if "block_list" in prop_dict:
+      build_command.extend(["-B", prop_dict["block_list"]])
     build_command.extend(["-L", prop_dict["mount_point"]])
-    if fc_config is not None:
-      build_command.append(fc_config)
-    elif "selinux_fc" in prop_dict:
+    if "selinux_fc" in prop_dict:
       build_command.append(prop_dict["selinux_fc"])
   elif fs_type.startswith("squash"):
     build_command = ["mksquashfsimage.sh"]
     build_command.extend([in_dir, out_file])
     build_command.extend(["-m", prop_dict["mount_point"]])
-    if fc_config is not None:
-      build_command.extend(["-c", fc_config])
-    elif "selinux_fc" in prop_dict:
+    if "selinux_fc" in prop_dict:
       build_command.extend(["-c", prop_dict["selinux_fc"]])
   elif fs_type.startswith("f2fs"):
     build_command = ["mkf2fsuserimg.sh"]
@@ -274,7 +284,23 @@
       build_command.append(prop_dict["selinux_fc"])
       build_command.append(prop_dict["mount_point"])
 
-  exit_code = RunCommand(build_command)
+  if in_dir != origin_in:
+    # Construct a staging directory of the root file system.
+    ramdisk_dir = prop_dict.get("ramdisk_dir")
+    if ramdisk_dir:
+      shutil.rmtree(in_dir)
+      shutil.copytree(ramdisk_dir, in_dir, symlinks=True)
+    staging_system = os.path.join(in_dir, "system")
+    shutil.rmtree(staging_system, ignore_errors=True)
+    shutil.copytree(origin_in, staging_system, symlinks=True)
+  try:
+    exit_code = RunCommand(build_command)
+  finally:
+    if in_dir != origin_in:
+      # Clean up temporary directories and files.
+      shutil.rmtree(in_dir, ignore_errors=True)
+      if fs_config:
+        os.remove(fs_config)
   if exit_code != 0:
     return False
 
@@ -334,6 +360,8 @@
     copy_prop("system_size", "partition_size")
     copy_prop("system_journal_size", "journal_size")
     copy_prop("system_verity_block_device", "verity_block_device")
+    copy_prop("system_root_image","system_root_image")
+    copy_prop("ramdisk_dir","ramdisk_dir")
   elif mount_point == "data":
     # Copy the generic fs type first, override with specific one if available.
     copy_prop("fs_type", "fs_type")