| #!/usr/bin/python |
| |
| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """This module is responsible for generate a stateful update payload.""" |
| |
| import logging |
| import optparse |
| import os |
| import sys |
| import subprocess |
| import tempfile |
| |
| STATEFUL_FILE = 'stateful.tgz' |
| |
| |
| def GenerateStatefulPayload(image_path, output_directory, logger): |
| """Generates a stateful update payload given a full path to an image. |
| |
| Args: |
| image_path: Full path to the image. |
| output_directory: Path to the directory to leave the resulting output. |
| logger: logging instance. |
| """ |
| logger.info('Generating stateful update file.') |
| from_dir = os.path.dirname(image_path) |
| image = os.path.basename(image_path) |
| output_gz = os.path.join(output_directory, STATEFUL_FILE) |
| # TODO(zbehan): This is only used for mount_gpt_image.sh. That script also |
| # needs to move away from src/scripts. |
| try: |
| crosutils_dir = '%s/src/scripts' % os.environ['CROS_WORKON_SRCROOT'] |
| except KeyError: |
| # Wow, we're outside the chroot. There's no clean way to do this, but we |
| # need to find out where sourceroot is. |
| # The two places where we may be running is platform/dev/, or /usr/bin/. |
| # Coincidentally, both are just a ../../../ away from sourceroot. Other |
| # locations for this script will just not work, and long term, we want |
| # exactly one anyway. (/usr/bin/) |
| self_path = os.path.dirname(sys.argv[0]) |
| crosutils_dir = os.path.realpath( |
| os.path.join(self_path, '../../..', 'src/scripts')) |
| |
| # Temporary directories for this function. |
| rootfs_dir = tempfile.mkdtemp(suffix='rootfs', prefix='tmp') |
| stateful_dir = tempfile.mkdtemp(suffix='stateful', prefix='tmp') |
| |
| # Mount the image to pull out the important directories. |
| try: |
| # Only need stateful partition, but this saves us having to manage our |
| # own loopback device. |
| subprocess.check_call(['%s/mount_gpt_image.sh' % crosutils_dir, |
| '--from=%s' % from_dir, |
| '--image=%s' % image, |
| '--read_only', |
| '--rootfs_mountpt=%s' % rootfs_dir, |
| '--stateful_mountpt=%s' % stateful_dir, |
| ]) |
| logger.info('Tarring up /usr/local and /var!') |
| subprocess.check_call(['sudo', |
| 'tar', |
| '-czf', |
| output_gz, |
| '--directory=%s' % stateful_dir, |
| '--hard-dereference', |
| '--transform=s,^dev_image,dev_image_new,', |
| '--transform=s,^var_overlay,var_new,', |
| 'dev_image', |
| 'var_overlay', |
| ]) |
| except: |
| logger.error('Failed to create stateful update file') |
| raise |
| finally: |
| # Unmount best effort regardless. |
| subprocess.call(['%s/mount_gpt_image.sh' % crosutils_dir, |
| '--unmount', |
| '--rootfs_mountpt=%s' % rootfs_dir, |
| '--stateful_mountpt=%s' % stateful_dir, |
| ]) |
| # Clean up our directories. |
| os.rmdir(rootfs_dir) |
| os.rmdir(stateful_dir) |
| |
| logger.info('Successfully generated %s' % output_gz) |
| |
| |
| def main(): |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(os.path.basename(__file__)) |
| parser = optparse.OptionParser() |
| parser.add_option('-i', '--image_path', |
| help='The image to generate the stateful update for.') |
| parser.add_option('-o', '--output_dir', |
| help='The path to the directory to output the update file.') |
| options, unused_args = parser.parse_args() |
| if not options.image_path: |
| parser.error('Missing image for stateful payload generator') |
| if not options.output_dir: |
| parser.error('Missing output directory for the payload generator') |
| |
| GenerateStatefulPayload(os.path.abspath(options.image_path), |
| options.output_dir, logger) |
| |
| |
| if __name__ == '__main__': |
| main() |