diff --git a/gsi/gsi_util/Android.bp b/gsi/gsi_util/Android.bp index 2eb80fdf8..2ab04c80b 100644 --- a/gsi/gsi_util/Android.bp +++ b/gsi/gsi_util/Android.bp @@ -18,10 +18,13 @@ python_binary_host { "gsi_util.py", "gsi_util/*.py", "gsi_util/commands/*.py", + "gsi_util/mounters/*.py", "gsi_util/utils/*.py", ], required: [ + "adb", "avbtool", + "simg2img", ], version: { py2: { diff --git a/gsi/gsi_util/gsi_util.py b/gsi/gsi_util/gsi_util.py index 2b535a85f..a226628e6 100755 --- a/gsi/gsi_util/gsi_util.py +++ b/gsi/gsi_util/gsi_util.py @@ -28,7 +28,7 @@ class GsiUtil(object): # Adds gsi_util COMMAND here. # TODO(bowgotsai): auto collect from gsi_util/commands/*.py - _COMMANDS = ['flash_gsi', 'hello'] + _COMMANDS = ['flash_gsi', 'pull', 'hello'] _LOGGING_FORMAT = '%(message)s' _LOGGING_LEVEL = logging.WARNING diff --git a/gsi/gsi_util/gsi_util/commands/pull.py b/gsi/gsi_util/gsi_util/commands/pull.py new file mode 100644 index 000000000..0b030110b --- /dev/null +++ b/gsi/gsi_util/gsi_util/commands/pull.py @@ -0,0 +1,98 @@ +# Copyright 2017 - 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. +"""Implementation of gsi_util command 'pull'.""" + +import argparse +import logging +import shutil +import sys + +from gsi_util.mounters.composite_mounter import CompositeMounter + + +def do_pull(args): + logging.info('==== PULL ====') + logging.info(' system=%s vendor=%s', args.system, args.vendor) + + if not args.system and not args.vendor: + sys.exit('Without system nor vendor.') + + source, dest = args.SOURCE, args.DEST + + mounter = CompositeMounter() + if args.system: + mounter.add_by_mount_target('system', args.system) + if args.vendor: + mounter.add_by_mount_target('vendor', args.vendor) + + with mounter as file_accessor: + with file_accessor.prepare_file(source) as filename: + if not filename: + print >> sys.stderr, 'Can not dump file: {}'.format(source) + else: + logging.debug('Copy %s -> %s', filename, dest) + shutil.copy(filename, dest) + + logging.info('==== DONE ====') + + +DUMP_DESCRIPTION = """'pull' command pulls a file from the give image. + +You must assign at least one image source by SYSTEM and/or VENDOR. +Image source could be: + + adb[:SERIAL_NUM]: pull the file form the device which be connected with adb + image file name: pull the file from the given image file, e.g. the file name + of a GSI. + If a image file is assigned to be the source of system + image, gsu_util will detect system-as-root automatically. + folder name: pull the file from the given folder, e.g. the system/vendor + folder in a Android build out folder. + +SOURCE is the full path file name to pull, which must start with '/' and +includes the mount point. ex. + + /system/build.prop + /vendor/compatibility_matrix.xml + +Some usage examples: + + $ ./gsi_util.py pull --system adb:AB0123456789 /system/manifest.xml + $ ./gsi_util.py pull --vendor adb /vendor/compatibility_matrix.xml + $ ./gsi_util.py pull --system system.img /system/build.prop + $ ./gsi_util.py pull --system my/out/folder/system /system/build.prop""" + + +def setup_command_args(parser): + # command 'pull' + dump_parser = parser.add_parser( + 'pull', + help='pull a file from the given image', + description=DUMP_DESCRIPTION, + formatter_class=argparse.RawTextHelpFormatter) + dump_parser.add_argument( + '--system', type=str, help='system image file name, folder name or "adb"') + dump_parser.add_argument( + '--vendor', type=str, help='vendor image file name, folder name or "adb"') + dump_parser.add_argument( + 'SOURCE', + type=str, + help='the full path file name in given image to be pull') + dump_parser.add_argument( + 'DEST', + nargs='?', + default='.', + type=str, + help='the file name or directory to save the pulled file (default: .)') + dump_parser.set_defaults(func=do_pull) diff --git a/gsi/gsi_util/gsi_util/mounters/__init__.py b/gsi/gsi_util/gsi_util/mounters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gsi/gsi_util/gsi_util/mounters/adb_mounter.py b/gsi/gsi_util/gsi_util/mounters/adb_mounter.py new file mode 100644 index 000000000..17d16eee2 --- /dev/null +++ b/gsi/gsi_util/gsi_util/mounters/adb_mounter.py @@ -0,0 +1,82 @@ +# Copyright 2017 - 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. + +"""Provides class AdbMounter. + +The AdbMounter implements the abstract class BaseMounter. It can get files from +a device which is connected by adb. +""" + +import errno +import logging +import os +import shutil +import tempfile + +import gsi_util.mounters.base_mounter as base_mounter +import gsi_util.utils.adb_utils as adb_utils + + +class _AdbFileAccessor(base_mounter.BaseFileAccessor): + + def __init__(self, temp_dir, serial_num): + super(_AdbFileAccessor, self).__init__() + self._temp_dir = temp_dir + self._serial_num = serial_num + + @staticmethod + def _make_parent_dirs(filename): + """Make parent directories as needed, no error if it exists.""" + dir_path = os.path.dirname(filename) + try: + os.makedirs(dir_path) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + + # override + def _handle_prepare_file(self, filename_in_storage): + filename = os.path.join(self._temp_dir, filename_in_storage) + logging.info('Prepare file %s -> %s', filename_in_storage, filename) + + self._make_parent_dirs(filename) + if not adb_utils.pull(filename, filename_in_storage, self._serial_num): + logging.error('Fail to prepare file: %s', filename_in_storage) + return None + + return base_mounter.MounterFile(filename) + + +class AdbMounter(base_mounter.BaseMounter): + """Provides a file accessor which can access files by adb.""" + + def __init__(self, serial_num=None): + super(AdbMounter, self).__init__() + self._serial_num = serial_num + + # override + def _handle_mount(self): + adb_utils.root(self._serial_num) + + self._temp_dir = tempfile.mkdtemp() + logging.debug('Created temp dir: %s', self._temp_dir) + + return _AdbFileAccessor(self._temp_dir, self._serial_num) + + # override + def _handle_unmount(self): + if hasattr(self, '_temp_dir'): + logging.debug('Remove temp dir: %s', self._temp_dir) + shutil.rmtree(self._temp_dir) + del self._temp_dir diff --git a/gsi/gsi_util/gsi_util/mounters/base_mounter.py b/gsi/gsi_util/gsi_util/mounters/base_mounter.py new file mode 100644 index 000000000..c0402c72d --- /dev/null +++ b/gsi/gsi_util/gsi_util/mounters/base_mounter.py @@ -0,0 +1,180 @@ +# Copyright 2017 - 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. +"""Base classes to implement Mounter classes.""" + +import abc +import logging + + +class MounterFile(object): + + def __init__(self, filename, cleanup_func=None): + self._filename = filename + self._clean_up_func = cleanup_func + + def _handle_get_filename(self): + return self._filename + + def _handle_clean_up(self): + if self._clean_up_func: + self._clean_up_func() + + def __enter__(self): + return self._handle_get_filename() + + def __exit__(self, exc_type, exc_val, exc_tb): + self._handle_clean_up() + + def get_filename(self): + return self._handle_get_filename() + + def clean_up(self): + self._handle_clean_up() + + +class MounterFileList(object): + + def __init__(self, file_list): + self._file_list = file_list + + def _handle_get_filenames(self): + return [x.get_filename() for x in self._file_list] + + def _handle_clean_up(self): + for x in reversed(self._file_list): + x.clean_up() + + def __enter__(self): + return self._handle_get_filenames() + + def __exit__(self, exc_type, exc_val, exc_tb): + self._handle_clean_up() + + def get_filenames(self): + return self._handle_get_filenames() + + def clean_up(self): + self._handle_clean_up() + + +class BaseFileAccessor(object): + """An abstract class to implement the file accessors. + + A mounter returns a file accessor when it is mounted. A file accessor must + override the method _handle_prepare_file() to return the file name of + the requested file in the storage. However, files in some mounter storages + couldn't be access directly, e.g. the file accessor of AdbMounter, which + accesses the file in a device by adb. In this case, file accessor could + return a temp file which contains the content. A file accessor could give the + cleanup_func when creating MounterFile to cleanup the temp file. + """ + + __metaclass__ = abc.ABCMeta + + def __init__(self, path_prefix='/'): + logging.debug('BaseFileAccessor(path_prefix=%s)', path_prefix) + self._path_prefix = path_prefix + + def _get_pathfile_to_access(self, file_to_map): + path_prefix = self._path_prefix + + if not file_to_map.startswith(path_prefix): + raise RuntimeError('"%s" does not start with "%s"', file_to_map, + path_prefix) + + return file_to_map[len(path_prefix):] + + @abc.abstractmethod + def _handle_prepare_file(self, filename_in_storage): + """Override this method to prepare the given file in the storage. + + Args: + filename_in_storage: the file in the storage to be prepared + + Returns: + Return an MounterFile instance. Return None if the request file is not + in the mount. + """ + + def prepare_file(self, filename_in_mount): + """Return the accessable file name in the storage. + + The function prepares a accessable file which contains the content of the + filename_in_mount. + + See BaseFileAccessor for the detail. + + Args: + filename_in_mount: the file to map. + filename_in_mount should be a full path file as the path in a real + device, and must start with a '/'. For example: '/system/build.prop', + '/vendor/default.prop', '/init.rc', etc. + + Returns: + A MounterFile instance. Return None if the file is not exit in the + storage. + """ + filename_in_storage = self._get_pathfile_to_access(filename_in_mount) + ret = self._handle_prepare_file(filename_in_storage) + return ret if ret else MounterFile(None) + + def prepare_multi_files(self, filenames_in_mount): + file_list = [self.prepare_file(x) for x in filenames_in_mount] + return MounterFileList(file_list) + + +class BaseMounter(object): + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def _handle_mount(self): + """Override this method to handle mounting and return a file accessor. + + File accessor must inherit from BaseFileAccessor. + """ + + def _handle_unmount(self): + """Override this method to handle cleanup this mounting.""" + # default is do nothing + return + + def _process_mount(self): + if self._mounted: + raise RuntimeError('The mounter had been mounted.') + + file_accessor = self._handle_mount() + self._mounted = True + + return file_accessor + + def _process_unmount(self): + if self._mounted: + self._handle_unmount() + self._mounted = False + + def __init__(self): + self._mounted = False + + def __enter__(self): + return self._process_mount() + + def __exit__(self, exc_type, exc_val, exc_tb): + self._process_unmount() + + def mount(self): + return self._process_mount() + + def unmount(self): + self._process_unmount() diff --git a/gsi/gsi_util/gsi_util/mounters/composite_mounter.py b/gsi/gsi_util/gsi_util/mounters/composite_mounter.py new file mode 100644 index 000000000..0e6742061 --- /dev/null +++ b/gsi/gsi_util/gsi_util/mounters/composite_mounter.py @@ -0,0 +1,125 @@ +# Copyright 2017 - 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. +"""Provides class CompositeMounter. + +CompositeMounter implements the abstract class BaseMounter. It can add multiple +mounters inside as sub-mounters, and operate these sub-mounters with the +BaseMounter interface. Uses CompositeMounter.add_sub_mounter() to add +sub-mounter. + +Usually, using CompositeMounter.add_by_mount_target() to add mounters is easier, +the method uses class _MounterFactory to create a mounter and then adds it. + +class _MounterFactory provides a method to create a mounter by 'mounter_target'. +'mounter_target' is a name which identify what is the file source to be +mounted. See _MounterFactory.create_by_mount_target() for the detail. +""" + +import logging +import os + +from adb_mounter import AdbMounter +import base_mounter +from folder_mounter import FolderMounter +from image_mounter import ImageMounter + + +class _MounterFactory(object): + + _SUPPORTED_PARTITIONS = ['system', 'vendor'] + + @classmethod + def create_by_mount_target(cls, mount_target, partition): + """Create a proper Mounter instance by a string of mount target. + + Args: + partition: the partition to be mounted as + mount_target: 'adb', a folder name or an image file name to mount. + see Returns for the detail. + + Returns: + Returns an AdbMounter if mount_target is 'adb[:SERIAL_NUM]' + Returns a FolderMounter if mount_target is a folder name + Returns an ImageMounter if mount_target is an image file name + + Raises: + ValueError: partiton is not support or mount_target is not exist. + """ + if partition not in cls._SUPPORTED_PARTITIONS: + raise ValueError('Wrong partition name "{}"'.format(partition)) + + if mount_target == 'adb' or mount_target.startswith('adb:'): + (_, _, serial_num) = mount_target.partition(':') + return AdbMounter(serial_num) + + path_prefix = '/{}/'.format(partition) + + if os.path.isdir(mount_target): + return FolderMounter(mount_target, path_prefix) + + if os.path.isfile(mount_target): + if partition == 'system': + path_prefix = ImageMounter.DETECT_SYSTEM_AS_ROOT + return ImageMounter(mount_target, path_prefix) + + raise ValueError('Unknown target "{}"'.format(mount_target)) + + +class _CompositeFileAccessor(base_mounter.BaseFileAccessor): + + def __init__(self, file_accessors): + super(_CompositeFileAccessor, self).__init__() + self._file_accessors = file_accessors + + # override + def _handle_prepare_file(self, filename_in_storage): + logging.debug('_CompositeFileAccessor._handle_prepare_file(%s)', + filename_in_storage) + + pathfile_to_prepare = '/' + filename_in_storage + for (prefix_path, file_accessor) in self._file_accessors: + if pathfile_to_prepare.startswith(prefix_path): + return file_accessor.prepare_file(pathfile_to_prepare) + + logging.debug(' Not found') + return None + + +class CompositeMounter(base_mounter.BaseMounter): + """Implements a BaseMounter which can add multiple sub-mounters.""" + + def __init__(self): + super(CompositeMounter, self).__init__() + self._mounters = [] + + # override + def _handle_mount(self): + file_accessors = [(path_prefix, mounter.mount()) + for (path_prefix, mounter) in self._mounters] + return _CompositeFileAccessor(file_accessors) + + # override + def _handle_unmount(self): + for (_, mounter) in reversed(self._mounters): + mounter.unmount() + + def add_sub_mounter(self, mount_point, mounter): + self._mounters.append((mount_point, mounter)) + + def add_by_mount_target(self, partition, mount_target): + logging.debug('CompositeMounter.add_by_mount_target(%s, %s)', + partition, mount_target) + mount_point = '/{}/'.format(partition) + mounter = _MounterFactory.create_by_mount_target(mount_target, partition) + self.add_sub_mounter(mount_point, mounter) diff --git a/gsi/gsi_util/gsi_util/mounters/folder_mounter.py b/gsi/gsi_util/gsi_util/mounters/folder_mounter.py new file mode 100644 index 000000000..83ba6410b --- /dev/null +++ b/gsi/gsi_util/gsi_util/mounters/folder_mounter.py @@ -0,0 +1,54 @@ +# Copyright 2017 - 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. + +"""Provides class FolderMounter. + +The FolderMounter implements the abstract class BaseMounter. It can +get files from a given folder. The folder is usually the system/vendor folder +of $OUT folder in an Android build environment. +""" + +import logging +import os + +import base_mounter + + +class _FolderFileAccessor(base_mounter.BaseFileAccessor): + + def __init__(self, folder_dir, path_prefix): + super(_FolderFileAccessor, self).__init__(path_prefix) + self._folder_dir = folder_dir + + # override + def _handle_prepare_file(self, filename_in_storage): + filename = os.path.join(self._folder_dir, filename_in_storage) + logging.debug('Prepare file %s -> %s', filename_in_storage, filename) + if not os.path.isfile(filename): + logging.error('File is not exist: %s', filename_in_storage) + return None + return base_mounter.MounterFile(filename) + + +class FolderMounter(base_mounter.BaseMounter): + """Provides a file accessor which can access files in the given folder.""" + + def __init__(self, folder_dir, path_prefix): + super(FolderMounter, self).__init__() + self._folder_dir = folder_dir + self._path_prefix = path_prefix + + # override + def _handle_mount(self): + return _FolderFileAccessor(self._folder_dir, self._path_prefix) diff --git a/gsi/gsi_util/gsi_util/mounters/image_mounter.py b/gsi/gsi_util/gsi_util/mounters/image_mounter.py new file mode 100644 index 000000000..22a32e5b5 --- /dev/null +++ b/gsi/gsi_util/gsi_util/mounters/image_mounter.py @@ -0,0 +1,138 @@ +# Copyright 2017 - 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. + +"""Provides class ImageMounter. + +The ImageMounter implements the abstract calss BaseMounter, +It can get files from an image file. e.g., system.img or vendor.img. +""" + +import logging +import os +import shutil +import tempfile + +import gsi_util.mounters.base_mounter as base_mounter +import gsi_util.utils.image_utils as image_utils + + +class _ImageFileAccessor(base_mounter.BaseFileAccessor): + + @staticmethod + def _make_parent_dirs(filename): + """Make parent directories as needed, no error if it exists.""" + dir_path = os.path.dirname(filename) + try: + os.makedirs(dir_path) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + + def __init__(self, path_prefix, mount_point, temp_dir): + super(_ImageFileAccessor, self).__init__(path_prefix) + self._mount_point = mount_point + self._temp_dir = temp_dir + + # override + def _handle_prepare_file(self, filename_in_storage): + mount_filename = os.path.join(self._mount_point, filename_in_storage) + filename = os.path.join(self._temp_dir, filename_in_storage) + logging.info('Prepare file %s -> %s', filename_in_storage, filename) + if not os.path.isfile(mount_filename): + logging.error('File does not exist: %s', filename_in_storage) + return None + + self._make_parent_dirs(filename) + image_utils.copy_file(filename, mount_filename) + + return base_mounter.MounterFile(filename) + + +class ImageMounter(base_mounter.BaseMounter): + """Provides a file accessor which can access files in the given image file.""" + + DETECT_SYSTEM_AS_ROOT = 'detect-system-as-root' + _SYSTEM_FILES = ['compatibility_matrix.xml', 'build.prop', 'manifest.xml'] + + def __init__(self, image_filename, path_prefix): + super(ImageMounter, self).__init__() + self._image_filename = image_filename + self._path_prefix = path_prefix + + @classmethod + def _detect_system_as_root(cls, mount_point): + """Returns True if the image layout on mount_point is system-as-root.""" + logging.debug('Checking system-as-root on mount point %s...', mount_point) + + system_without_root = True + for filename in cls._SYSTEM_FILES: + if not os.path.isfile(os.path.join(mount_point, filename)): + system_without_root = False + break + + system_as_root = True + for filename in cls._SYSTEM_FILES: + if not os.path.isfile(os.path.join(mount_point, 'system', filename)): + system_as_root = False + break + + ret = system_as_root and not system_without_root + logging.debug(' Result=%s', ret) + return ret + + # override + def _handle_mount(self): + # Unsparse the image to a temp file + unsparsed_suffix = '_system.img.raw' + unsparsed_file = tempfile.NamedTemporaryFile(suffix=unsparsed_suffix) + unsparsed_filename = unsparsed_file.name + image_utils.unsparse(unsparsed_filename, self._image_filename) + + # Mount it + mount_point = tempfile.mkdtemp() + logging.debug('Create a temp mount point %s', mount_point) + image_utils.mount(mount_point, unsparsed_filename) + + # detect system-as-root if need + path_prefix = self._path_prefix + if path_prefix == self.DETECT_SYSTEM_AS_ROOT: + path_prefix = '/' if self._detect_system_as_root( + mount_point) else '/system/' + + # Create a temp dir for the target of copying file from image + temp_dir = tempfile.mkdtemp() + logging.debug('Created temp dir: %s', temp_dir) + + # Keep data to be removed on __exit__ + self._unsparsed_file = unsparsed_file + self._mount_point = mount_point + self._temp_dir = tempfile.mkdtemp() + + return _ImageFileAccessor(path_prefix, mount_point, temp_dir) + + # override + def _handle_unmount(self): + if hasattr(self, '_temp_dir'): + logging.debug('Removing temp dir: %s', self._temp_dir) + shutil.rmtree(self._temp_dir) + del self._temp_dir + + if hasattr(self, '_mount_point'): + image_utils.unmount(self._mount_point) + shutil.rmtree(self._mount_point) + del self._mount_point + + if hasattr(self, '_unsparsed_file'): + # will also delete the temp file implicitly + del self._unsparsed_file diff --git a/gsi/gsi_util/gsi_util/utils/adb_utils.py b/gsi/gsi_util/gsi_util/utils/adb_utils.py new file mode 100644 index 000000000..3a7bfb30c --- /dev/null +++ b/gsi/gsi_util/gsi_util/utils/adb_utils.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# +# Copyright 2017 - 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. +"""ADB-related utilities.""" + +import logging +import subprocess + +from gsi_util.utils.cmd_utils import run_command + + +def root(serial_num=None): + command = ['adb'] + if serial_num: + command += ['-s', serial_num] + command += ['root'] + + # 'read_stdout=True' to disable output + run_command(command, raise_on_error=False, read_stdout=True, log_stderr=True) + + +def pull(local_filename, remote_filename, serial_num=None): + command = ['adb'] + if serial_num: + command += ['-s', serial_num] + command += ['pull', remote_filename, local_filename] + + # 'read_stdout=True' to disable output + (returncode, _, _) = run_command( + command, raise_on_error=False, read_stdout=True, log_stdout=True) + + return returncode == 0 diff --git a/gsi/gsi_util/gsi_util/utils/image_utils.py b/gsi/gsi_util/gsi_util/utils/image_utils.py new file mode 100644 index 000000000..513c60e21 --- /dev/null +++ b/gsi/gsi_util/gsi_util/utils/image_utils.py @@ -0,0 +1,41 @@ +# Copyright 2017 - 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. +"""Image-related utilities.""" + +import logging + +from gsi_util.utils.cmd_utils import run_command + + +def unsparse(output_filename, input_filename): + logging.debug('Unsparsing %s...', input_filename) + run_command(['simg2img', input_filename, output_filename]) + + +def mount(mount_point, image_filename): + logging.debug('Mounting...') + run_command( + ['mount', '-t', 'ext4', '-o', 'loop', image_filename, mount_point], + sudo=True) + + +def unmount(mount_point): + logging.debug('Unmounting...') + run_command(['umount', '-l', mount_point], sudo=True, raise_on_error=False) + + +def copy_file(dest, src): + run_command(['cp', src, dest], sudo=True) + # This is a hack to give access permission without root + run_command(['chmod', '+444', dest], sudo=True)