828bbfb188
amend's symlink command isn't idempotent, so if you restart after the symlinks have been completed then the second installation attempt will fail. Work around this by deleting all symlink targets before creating symlinks. Bug: 2020011
211 lines
7.9 KiB
Python
211 lines
7.9 KiB
Python
# Copyright (C) 2009 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import os
|
|
|
|
import common
|
|
|
|
class AmendGenerator(object):
|
|
"""Class to generate scripts in the 'amend' recovery script language
|
|
used up through cupcake."""
|
|
|
|
def __init__(self):
|
|
self.script = ['assert compatible_with("0.2") == "true"']
|
|
self.included_files = set()
|
|
|
|
def MakeTemporary(self):
|
|
"""Make a temporary script object whose commands can latter be
|
|
appended to the parent script with AppendScript(). Used when the
|
|
caller wants to generate script commands out-of-order."""
|
|
x = AmendGenerator()
|
|
x.script = []
|
|
x.included_files = self.included_files
|
|
return x
|
|
|
|
@staticmethod
|
|
def _FileRoot(fn):
|
|
"""Convert a file path to the 'root' notation used by amend."""
|
|
if fn.startswith("/system/"):
|
|
return "SYSTEM:" + fn[8:]
|
|
elif fn == "/system":
|
|
return "SYSTEM:"
|
|
elif fn.startswith("/tmp/"):
|
|
return "CACHE:.." + fn
|
|
else:
|
|
raise ValueError("don't know root for \"%s\"" % (fn,))
|
|
|
|
@staticmethod
|
|
def _PartitionRoot(partition):
|
|
"""Convert a partition name to the 'root' notation used by amend."""
|
|
if partition == "userdata":
|
|
return "DATA:"
|
|
else:
|
|
return partition.upper() + ":"
|
|
|
|
def AppendScript(self, other):
|
|
"""Append the contents of another script (which should be created
|
|
with temporary=True) to this one."""
|
|
self.script.extend(other.script)
|
|
self.included_files.update(other.included_files)
|
|
|
|
def AssertSomeFingerprint(self, *fp):
|
|
"""Assert that the current fingerprint is one of *fp."""
|
|
x = [('file_contains("SYSTEM:build.prop", '
|
|
'"ro.build.fingerprint=%s") == "true"') % i for i in fp]
|
|
self.script.append("assert %s" % (" || ".join(x),))
|
|
|
|
def AssertOlderBuild(self, timestamp):
|
|
"""Assert that the build on the device is older (or the same as)
|
|
the given timestamp."""
|
|
self.script.append("run_program PACKAGE:check_prereq %s" % (timestamp,))
|
|
self.included_files.add("check_prereq")
|
|
|
|
def AssertDevice(self, device):
|
|
"""Assert that the device identifier is the given string."""
|
|
self.script.append('assert getprop("ro.product.device") == "%s" || '
|
|
'getprop("ro.build.product") == "%s"' % (device, device))
|
|
|
|
def AssertSomeBootloader(self, *bootloaders):
|
|
"""Asert that the bootloader version is one of *bootloaders."""
|
|
self.script.append("assert " +
|
|
" || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
|
|
for b in bootloaders]))
|
|
|
|
def ShowProgress(self, frac, dur):
|
|
"""Update the progress bar, advancing it over 'frac' over the next
|
|
'dur' seconds."""
|
|
self.script.append("show_progress %f %d" % (frac, int(dur)))
|
|
|
|
def PatchCheck(self, filename, *sha1):
|
|
"""Check that the given file (or MTD reference) has one of the
|
|
given *sha1 hashes."""
|
|
out = ["run_program PACKAGE:applypatch -c %s" % (filename,)]
|
|
for i in sha1:
|
|
out.append(" " + i)
|
|
self.script.append("".join(out))
|
|
self.included_files.add(("applypatch_static", "applypatch"))
|
|
|
|
def CacheFreeSpaceCheck(self, amount):
|
|
"""Check that there's at least 'amount' space that can be made
|
|
available on /cache."""
|
|
self.script.append("run_program PACKAGE:applypatch -s %d" % (amount,))
|
|
self.included_files.add(("applypatch_static", "applypatch"))
|
|
|
|
def Mount(self, kind, what, path):
|
|
# no-op; amend uses it's 'roots' system to automatically mount
|
|
# things when they're referred to
|
|
pass
|
|
|
|
def UnpackPackageDir(self, src, dst):
|
|
"""Unpack a given directory from the OTA package into the given
|
|
destination directory."""
|
|
dst = self._FileRoot(dst)
|
|
self.script.append("copy_dir PACKAGE:%s %s" % (src, dst))
|
|
|
|
def Comment(self, comment):
|
|
"""Write a comment into the update script."""
|
|
self.script.append("")
|
|
for i in comment.split("\n"):
|
|
self.script.append("# " + i)
|
|
self.script.append("")
|
|
|
|
def Print(self, message):
|
|
"""Log a message to the screen (if the logs are visible)."""
|
|
# no way to do this from amend; substitute a script comment instead
|
|
self.Comment(message)
|
|
|
|
def FormatPartition(self, partition):
|
|
"""Format the given MTD partition."""
|
|
self.script.append("format %s" % (self._PartitionRoot(partition),))
|
|
|
|
def DeleteFiles(self, file_list):
|
|
"""Delete all files in file_list."""
|
|
line = []
|
|
t = 0
|
|
for i in file_list:
|
|
i = self._FileRoot(i)
|
|
line.append(i)
|
|
t += len(i) + 1
|
|
if t > 80:
|
|
self.script.append("delete " + " ".join(line))
|
|
line = []
|
|
t = 0
|
|
if line:
|
|
self.script.append("delete " + " ".join(line))
|
|
|
|
def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
|
|
"""Apply binary patches (in *patchpairs) to the given srcfile to
|
|
produce tgtfile (which may be "-" to indicate overwriting the
|
|
source file."""
|
|
if len(patchpairs) % 2 != 0:
|
|
raise ValueError("bad patches given to ApplyPatch")
|
|
self.script.append(
|
|
("run_program PACKAGE:applypatch %s %s %s %d " %
|
|
(srcfile, tgtfile, tgtsha1, tgtsize)) +
|
|
" ".join(["%s:%s" % patchpairs[i:i+2]
|
|
for i in range(0, len(patchpairs), 2)]))
|
|
self.included_files.add(("applypatch_static", "applypatch"))
|
|
|
|
def WriteFirmwareImage(self, kind, fn):
|
|
"""Arrange to update the given firmware image (kind must be
|
|
"hboot" or "radio") when recovery finishes."""
|
|
self.script.append("write_%s_image PACKAGE:%s" % (kind, fn))
|
|
|
|
def WriteRawImage(self, partition, fn):
|
|
"""Write the given file into the given MTD partition."""
|
|
self.script.append("write_raw_image PACKAGE:%s %s" %
|
|
(fn, self._PartitionRoot(partition)))
|
|
|
|
def SetPermissions(self, fn, uid, gid, mode):
|
|
"""Set file ownership and permissions."""
|
|
fn = self._FileRoot(fn)
|
|
self.script.append("set_perm %d %d 0%o %s" % (uid, gid, mode, fn))
|
|
|
|
def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode):
|
|
"""Recursively set path ownership and permissions."""
|
|
fn = self._FileRoot(fn)
|
|
self.script.append("set_perm_recursive %d %d 0%o 0%o %s" %
|
|
(uid, gid, dmode, fmode, fn))
|
|
|
|
def MakeSymlinks(self, symlink_list):
|
|
"""Create symlinks, given a list of (dest, link) pairs."""
|
|
self.DeleteFiles([i[1] for i in symlink_list])
|
|
self.script.extend(["symlink %s %s" % (i[0], self._FileRoot(i[1]))
|
|
for i in sorted(symlink_list)])
|
|
|
|
def AppendExtra(self, extra):
|
|
"""Append text verbatim to the output script."""
|
|
self.script.append(extra)
|
|
|
|
def AddToZip(self, input_zip, output_zip, input_path=None):
|
|
"""Write the accumulated script to the output_zip file. input_zip
|
|
is used as the source for any ancillary binaries needed by the
|
|
script. If input_path is not None, it will be used as a local
|
|
path for binaries instead of input_zip."""
|
|
common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-script",
|
|
"\n".join(self.script) + "\n")
|
|
for i in self.included_files:
|
|
if isinstance(i, tuple):
|
|
sourcefn, targetfn = i
|
|
else:
|
|
sourcefn = i
|
|
targetfn = i
|
|
try:
|
|
if input_path is None:
|
|
data = input_zip.read(os.path.join("OTA/bin", sourcefn))
|
|
else:
|
|
data = open(os.path.join(input_path, sourcefn)).read()
|
|
common.ZipWriteStr(output_zip, targetfn, data, perms=0755)
|
|
except (IOError, KeyError), e:
|
|
raise ExternalError("unable to include binary %s: %s" % (i, e))
|