diff options
Diffstat (limited to 'tools/releasetools/common.py')
-rw-r--r-- | tools/releasetools/common.py | 175 |
1 files changed, 147 insertions, 28 deletions
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index b992da3..0da5f35 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -84,17 +84,24 @@ def CloseInheritedPipes(): pass -def LoadInfoDict(zip): +def LoadInfoDict(input): """Read and parse the META/misc_info.txt key/value pairs from the input target files and return a dict.""" + def read_helper(fn): + if isinstance(input, zipfile.ZipFile): + return input.read(fn) + else: + path = os.path.join(input, *fn.split("/")) + try: + with open(path) as f: + return f.read() + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError(fn) d = {} try: - for line in zip.read("META/misc_info.txt").split("\n"): - line = line.strip() - if not line or line.startswith("#"): continue - k, v = line.split("=", 1) - d[k] = v + d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n")) except KeyError: # ok if misc_info.txt doesn't exist pass @@ -105,20 +112,20 @@ def LoadInfoDict(zip): if "mkyaffs2_extra_flags" not in d: try: - d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip() + d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip() except KeyError: # ok if flags don't exist pass if "recovery_api_version" not in d: try: - d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip() + d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip() except KeyError: raise ValueError("can't find recovery API version in input target-files") if "tool_extensions" not in d: try: - d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip() + d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip() except KeyError: # ok if extensions don't exist pass @@ -127,7 +134,7 @@ def LoadInfoDict(zip): d["fstab_version"] = "1" try: - data = zip.read("META/imagesizes.txt") + data = read_helper("META/imagesizes.txt") for line in data.split("\n"): if not line: continue name, value = line.split(" ", 1) @@ -146,39 +153,43 @@ def LoadInfoDict(zip): makeint("recovery_api_version") makeint("blocksize") makeint("system_size") + makeint("vendor_size") makeint("userdata_size") makeint("cache_size") makeint("recovery_size") makeint("boot_size") makeint("fstab_version") - d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"]) - d["build.prop"] = LoadBuildProp(zip) + d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"]) + d["build.prop"] = LoadBuildProp(read_helper) return d -def LoadBuildProp(zip): +def LoadBuildProp(read_helper): try: - data = zip.read("SYSTEM/build.prop") + data = read_helper("SYSTEM/build.prop") except KeyError: print "Warning: could not find SYSTEM/build.prop in %s" % zip data = "" + return LoadDictionaryFromLines(data.split("\n")) +def LoadDictionaryFromLines(lines): d = {} - for line in data.split("\n"): + for line in lines: line = line.strip() if not line or line.startswith("#"): continue - name, value = line.split("=", 1) - d[name] = value + if "=" in line: + name, value = line.split("=", 1) + d[name] = value return d -def LoadRecoveryFSTab(zip, fstab_version): +def LoadRecoveryFSTab(read_helper, fstab_version): class Partition(object): pass try: - data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab") + data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab") except KeyError: - print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip + print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab" data = "" if fstab_version == 1: @@ -347,9 +358,12 @@ def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir, else: print "building image from target_files %s..." % (tree_subdir,) fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt" - return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir), - os.path.join(unpack_dir, fs_config), - info_dict)) + data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir), + os.path.join(unpack_dir, fs_config), + info_dict) + if data: + return File(name, data) + return None def UnzipTemp(filename, pattern=None): @@ -490,6 +504,8 @@ def CheckSize(data, target, info_dict): if target.endswith(".img"): target = target[:-4] mount_point = "/" + target + fs_type = None + limit = None if info_dict["fstab"]: if mount_point == "/userdata": mount_point = "/data" p = info_dict["fstab"][mount_point] @@ -730,11 +746,14 @@ class PasswordManager(object): return result -def ZipWriteStr(zip, filename, data, perms=0644): +def ZipWriteStr(zip, filename, data, perms=0644, compression=None): # use a fixed timestamp so the output is repeatable. zinfo = zipfile.ZipInfo(filename=filename, date_time=(2009, 1, 1, 0, 0, 0)) - zinfo.compress_type = zip.compression + if compression is None: + zinfo.compress_type = zip.compression + else: + zinfo.compress_type = compression zinfo.external_attr = perms << 16 zip.writestr(zinfo, data) @@ -761,6 +780,7 @@ class DeviceSpecificParams(object): 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" @@ -839,8 +859,8 @@ class File(object): t.flush() return t - def AddToZip(self, z): - ZipWriteStr(z, self.name, self.data) + def AddToZip(self, z, compression=None): + ZipWriteStr(z, self.name, self.data, compression=compression) DIFF_PROGRAM_BY_EXT = { ".gz" : "imgdiff", @@ -954,7 +974,8 @@ def ComputeDifferences(diffs): # map recovery.fstab's fs_types to mount/format "partition types" PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD", - "ext4": "EMMC", "emmc": "EMMC" } + "ext4": "EMMC", "emmc": "EMMC", + "f2fs": "EMMC" } def GetTypeAndDevice(mount_point, info): fstab = info["fstab"] @@ -977,3 +998,101 @@ def ParseCertificate(data): save = True cert = "".join(cert).decode('base64') return cert + +def XDelta3(source_path, target_path, output_path): + diff_program = ["xdelta3", "-0", "-B", str(64<<20), "-e", "-f", "-s"] + diff_program.append(source_path) + diff_program.append(target_path) + diff_program.append(output_path) + p = Run(diff_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + p.communicate() + assert p.returncode == 0, "Couldn't produce patch" + +def XZ(path): + compress_program = ["xz", "-zk", "-9", "--check=crc32"] + compress_program.append(path) + p = Run(compress_program, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + p.communicate() + assert p.returncode == 0, "Couldn't compress patch" + +def MakePartitionPatch(source_file, target_file, partition): + with tempfile.NamedTemporaryFile() as output_file: + XDelta3(source_file.name, target_file.name, output_file.name) + XZ(output_file.name) + with open(output_file.name + ".xz") as patch_file: + patch_data = patch_file.read() + os.unlink(patch_file.name) + return File(partition + ".muimg.p", patch_data) + +def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img, + info_dict=None): + """Generate a binary patch that creates the recovery image starting + with the boot image. (Most of the space in these images is just the + kernel, which is identical for the two, so the resulting patch + should be efficient.) Add it to the output zip, along with a shell + script that is run from init.rc on first boot to actually do the + patching and install the new recovery image. + + recovery_img and boot_img should be File objects for the + corresponding images. info should be the dictionary returned by + common.LoadInfoDict() on the input target_files. + """ + + if info_dict is None: + info_dict = OPTIONS.info_dict + + diff_program = ["imgdiff"] + path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat") + if os.path.exists(path): + diff_program.append("-b") + diff_program.append(path) + bonus_args = "-b /system/etc/recovery-resource.dat" + else: + bonus_args = "" + + d = Difference(recovery_img, boot_img, diff_program=diff_program) + _, _, patch = d.ComputePatch() + output_sink("recovery-from-boot.p", patch) + + td_pair = GetTypeAndDevice("/boot", info_dict) + if not td_pair: + return + boot_type, boot_device = td_pair + td_pair = GetTypeAndDevice("/recovery", info_dict) + if not td_pair: + return + recovery_type, recovery_device = td_pair + + sh = """#!/system/bin/sh +if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then + applypatch %(bonus_args)s %(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 && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed" +else + log -t recovery "Recovery image already installed" +fi +""" % { 'boot_size': boot_img.size, + 'boot_sha1': boot_img.sha1, + 'recovery_size': recovery_img.size, + 'recovery_sha1': recovery_img.sha1, + 'boot_type': boot_type, + 'boot_device': boot_device, + 'recovery_type': recovery_type, + 'recovery_device': recovery_device, + 'bonus_args': bonus_args, + } + + # The install script location moved from /system/etc to /system/bin + # in the L release. Parse the init.rc file to find out where the + # target-files expects it to be, and put it there. + sh_location = "etc/install-recovery.sh" + try: + with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f: + for line in f: + m = re.match("^service flash_recovery /system/(\S+)\s*$", line) + if m: + sh_location = m.group(1) + print "putting script in", sh_location + break + except (OSError, IOError), e: + print "failed to read init.rc: %s" % (e,) + + output_sink(sh_location, sh) |