Analysis of the entire package of ota command execution process in Android Essay

Tip: This article analyzes the execution process of the android ota command



Preface

本文基于Android9.0从make命令开始一步步跟踪分析


1. Makefile

build/core/Makefile

ifeq ($(build_ota_package),true)
# -----------------------------------------------------------------
# OTA update package

name := $(TARGET_PRODUCT)
ifeq ($(TARGET_BUILD_TYPE),debug)
  name := $(name)_debug
endif
name := $(name)-ota-$(FILE_NAME_TAG)

INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip

$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)

ifeq ($(AB_OTA_UPDATER),true)
$(INTERNAL_OTA_PACKAGE_TARGET): $(BRILLO_UPDATE_PAYLOAD)
else
$(INTERNAL_OTA_PACKAGE_TARGET): $(BROTLI)
endif

$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) \
        build/make/tools/releasetools/ota_from_target_files
    @echo "Package OTA: $@"
    $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
       build/make/tools/releasetools/ota_from_target_files -v \
       --block \
       --extracted_input_target_files $(patsubst %.zip,%,$(BUILT_TARGET_FILES_PACKAGE)) \
       -p $(HOST_OUT) \
       -k $(KEY_CERT_PAIR) \
       $(if $(OEM_OTA_CONFIG), -o $(OEM_OTA_CONFIG)) \
       $(BUILT_TARGET_FILES_PACKAGE) $@

.PHONY: otapackage
otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)

endif    # build_ota_package

Here you can see that the target is otapackage, and the key script is ota_from_target_files. You can customize many parameters here.
Many manufacturers may have their own packaging commands instead of using the native otapackage. But nothing can change without its origin, and it cannot be separated from ota_from_target_files

2. ota_from_target_files file

build/tools/releasetools/ota_from_target_files

if __name__ == '__main__':
  try:
    common.CloseInheritedPipes()
    main(sys.argv[1:])
  except common.ExternalError as e:
    print("\n   ERROR: %s\n" % (e,))
    sys.exit(1)
  finally:
    common.Cleanup()

sys.argv[1:] passes all the parameters after ota_from_target_files to main,
then here we can configure some custom parameters, such as –wipe_user_data, the user data will be cleared after ota

def main(argv):

  def option_handler(o, a):
    if o in ("-k", "--package_key"):
      OPTIONS.package_key = a
    elif o in ("-i", "--incremental_from"):
      OPTIONS.incremental_source = a
    elif o == "--full_radio":
      OPTIONS.full_radio = True
    elif o == "--full_bootloader":
      OPTIONS.full_bootloader = True
    elif o == "--wipe_user_data":
      OPTIONS.wipe_user_data = True
    elif o == "--downgrade":
............
    elif o == "--skip_postinstall":
      OPTIONS.skip_postinstall = True
    else:
      return False
    return True

  args = common.ParseOptions(argv, __doc__,
                             extra_opts="b:k:i:d:e:t:2o:",
                             extra_long_opts=[
                                 "package_key=",
                                 "incremental_from=",
                                 "full_radio",
                                 "full_bootloader",
                                 "wipe_user_data",
                                 "downgrade",
                                 "override_timestamp",
                                 "extra_script=",
                                 "worker_threads=",
                                 "two_step",
                                 "include_secondary",
                                 "no_signing",
                                 "block",
                                 "binary=",
                                 "oem_settings=",
                                 "oem_no_mount",
                                 "verify",
                                 "stash_threshold=",
                                 "log_diff=",
                                 "payload_signer=",
                                 "payload_signer_args=",
                                 "extracted_input_target_files=",
                                 "skip_postinstall",
                             ], extra_option_handler=option_handler)

  if len(args) != 2:
    common.Usage(__doc__)
    sys.exit(1)

  if OPTIONS.downgrade:
    # We should only allow downgrading incrementals (as opposed to full).
    # Otherwise the device may go back from arbitrary build with this full
    # OTA package.
............
  # If the caller explicitly specified the device-specific extensions path via
  # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
  # is present in the target target_files. Otherwise, take the path of the file
  # from 'tool_extensions' in the info dict and look for that in the local
  # filesystem, relative to the current directory.
  if OPTIONS.device_specific is None:
    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
    if os.path.exists(from_input):
      print("(using device-specific extensions from target_files)")
      OPTIONS.device_specific = from_input
    else:
      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")

  if OPTIONS.device_specific is not None:
    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)

  # Generate a full OTA.
  if OPTIONS.incremental_source is None:
    with zipfile.ZipFile(args[0], 'r') as input_zip:
      WriteFullOTAPackage(
          input_zip,
          output_file=args[1])
......

Main will set different states according to the different parameters passed in.
In addition, ParseOptions in build/tools/releasetools/common.py is also called to supplement and set additional states.

def ParseOptions(argv,
                 docstring,
                 extra_opts="", extra_long_opts=(),
                 extra_option_handler=None):
  """Parse the options in argv and return any arguments that aren't
  flags.  docstring is the calling module's docstring, to be displayed
  for errors and -h.  extra_opts and extra_long_opts are for flags
  defined by the caller, which are processed by passing them to
  extra_option_handler."""

  try:
    opts, args = getopt.getopt(
        argv, "hvp:s:x:" + extra_opts,
        ["help", "verbose", "path=", "signapk_path=",
         "signapk_shared_library_path=", "extra_signapk_args=",
         "java_path=", "java_args=", "public_key_suffix=",
         "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
         "verity_signer_path=", "verity_signer_args=", "device_specific=",
         "extra="] +
        list(extra_long_opts))
  except getopt.GetoptError as err:
    Usage(docstring)
    print("**", str(err), "**")
    sys.exit(2)

  for o, a in opts:
    if o in ("-h", "--help"):
      Usage(docstring)
      sys.exit()
    elif o in ("-v", "--verbose"):
      OPTIONS.verbose = True
    elif o in ("-p", "--path"):
      OPTIONS.search_path = a
    elif o in ("--signapk_path",):
      OPTIONS.signapk_path = a
    elif o in ("--signapk_shared_library_path",):
      OPTIONS.signapk_shared_library_path = a
    elif o in ("--extra_signapk_args",):
.........

Go back to ota_from_target_files and carefully analyze the content in main, and find that there is OPTIONS.device_specific here, which will read the absolute path of a file.

There is also a WriteFullOTAPackage call, which is where the entire OTA package is generated.

Analyzing the content of WriteFullOTAPackage, it is not difficult to find that device_specific is critical.

Created with Raphaël 2.3.0 device_specific = common.DeviceSpecificParams device_specific.FullOTA_Assertions device_specific.FullOTA_InstallBegin device_specific.FullOTA_InstallEnd

So how is it called here?

3. common.py file

We then analyze the flow chart above. The first is DeviceSpecificParams,
which is in build/tools/releasetools/common.py

class DeviceSpecificParams(object):
  module = None
  def __init__(self, **kwargs):
    """Keyword arguments to the constructor become attributes of this
    object, which is passed to all functions in the device-specific
    module."""
    for k, v in kwargs.iteritems():
      setattr(self, k, v)
    self.extras = OPTIONS.extras

    if self.module is None:
      path = OPTIONS.device_specific
      if not path:
        return
      try:
        if os.path.isdir(path):
          info = imp.find_module("releasetools", [path])
        else:
          d, f = os.path.split(path)
          b, x = os.path.splitext(f)
          if x == ".py":
            f = b
          info = imp.find_module(f, [d])
        print("loaded device-specific extensions from", path)
        self.module = imp.load_module("device_specific", *info)
      except ImportError:
        print("unable to load device-specific module; assuming none")
  def _DoCall(self, function_name, *args, **kwargs):
    """Call the named function in the device-specific module, passing
    the given args and kwargs.  The first argument to the call will be
    the DeviceSpecific object itself.  If there is no module, or the
    module does not define the function, return the value of the
    'default' kwarg (which itself defaults to None)."""
    if self.module is None or not hasattr(self.module, function_name):
      return kwargs.get("default", None)
    return getattr(self.module, function_name)(*((self,) + args), **kwargs)

Here all the modules in OPTIONS.device_specific will be recorded in self.module
and then device_specific.xxxxx calls the corresponding function of common.py,
for example

  def FullOTA_InstallBegin(self):
    """Called at the start of full OTA installation."""
    return self._DoCall("FullOTA_InstallBegin")

Actually calls itself _DoCall

  def _DoCall(self, function_name, *args, **kwargs):
    """Call the named function in the device-specific module, passing
    the given args and kwargs.  The first argument to the call will be
    the DeviceSpecific object itself.  If there is no module, or the
    module does not define the function, return the value of the
    'default' kwarg (which itself defaults to None)."""
    if self.module is None or not hasattr(self.module, function_name):
      return kwargs.get("default", None)
    return getattr(self.module, function_name)(*((self,) + args), **kwargs)

First determine whether self.module has a corresponding function. If so, the function of the same name in releasetools is called; if not, it is not called.

4. releasetools.py file

This file is basically reserved for customization by major manufacturers and will not be explained in detail here.
TARGET_RELEASETOOLS_EXTENSIONS determines which one to use.
The things inside are relatively simple.

......
def FullOTA_InstallEnd(info):
    if os.path.exists(OUTPUT_PATH + '/uboot.bin'):
      WriteToOTAPackage(info, "/uboot", OUTPUT_PATH,  "uboot.bin")
    if os.path.exists(OUTPUT_PATH + '/uenv.bin'):
      WriteToOTAPackage(info, "/uboot_env", OUTPUT_PATH,  "uenv.bin")
    if os.path.exists(OUTPUT_PATH + '/logo.jpg'):
      WriteToOTAPackage(info, "/logo", OUTPUT_PATH, "logo.jpg")
    if os.path.exists(OUTPUT_PATH + '/tz.bin.lzhs'):
      WriteToOTAPackage(info, "/tzbp", OUTPUT_PATH, "tz.bin.lzhs")
    if os.path.exists(OUTPUT_PATH + '/AQ.bin'):
      WriteToOTAPackage(info, "/aq", OUTPUT_PATH, "AQ.bin")
    if os.path.exists(OUTPUT_PATH + '/pq.bin'):
      WriteToOTAPackage(info, "/pq", OUTPUT_PATH, "pq.bin")
    if os.path.exists(OUTPUT_PATH + '/rootfs.bin'):
      WriteToOTAPackage(info, "/linux_rootfs", OUTPUT_PATH, "rootfs.bin")
    if os.path.exists(OUTPUT_PATH + '/adsp.bin'):
      WriteToOTAPackage(info, "/adsp", OUTPUT_PATH, "adsp.bin")
def WriteToOTAPackage(info, dev_name, bin_path, bin_name):
  try:
    common.ZipWriteStr(info.output_zip, bin_name,
                       open(bin_path + '/' + bin_name).read())
  except KeyError:
    print ("warning: no "+ bin_name +" in input target_files; ")

  try:
    info.script.WriteRawImage(dev_name, bin_name)
  except KeyError:
    print ("warning: "+ bin_name +" write script failed;")
......

Summarize

The whole process is almost over here.

Guess you like

Origin blog.csdn.net/hmz0303hf/article/details/128317773