1# Copyright 2017 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Provides class ImageMounter. 15 16The ImageMounter implements the abstract class BaseMounter, 17It can get files from an image file. e.g., system.img or vendor.img. 18""" 19 20import errno 21import logging 22import os 23import shutil 24import tempfile 25 26from gsi_util.mounters import base_mounter 27from gsi_util.utils import debugfs 28from gsi_util.utils import image_utils 29 30 31class _ImageFileAccessor(base_mounter.BaseFileAccessor): 32 33 @staticmethod 34 def _make_parent_dirs(filename): 35 """Make parent directories as needed, no error if it exists.""" 36 dir_path = os.path.dirname(filename) 37 try: 38 os.makedirs(dir_path) 39 except OSError as exc: 40 if exc.errno != errno.EEXIST: 41 raise 42 43 def __init__(self, path_prefix, raw_image_file, temp_dir): 44 super(_ImageFileAccessor, self).__init__(path_prefix) 45 self._raw_image_file = raw_image_file 46 self._temp_dir = temp_dir 47 48 # override 49 def _handle_prepare_file(self, filename_in_storage): 50 filespec = os.path.join('/', filename_in_storage) 51 out_file = os.path.join(self._temp_dir, filename_in_storage) 52 logging.info('_ImageFileAccessor: Prepare file %s -> %s', 53 filename_in_storage, out_file) 54 55 self._make_parent_dirs(out_file) 56 57 if not debugfs.dump(self._raw_image_file, filespec, out_file): 58 logging.info(' File does not exist: %s', filename_in_storage) 59 return None 60 61 return base_mounter.MounterFile(out_file) 62 63 64class ImageMounter(base_mounter.BaseMounter): 65 """Provides a file accessor which can access files in the given image file.""" 66 67 DETECT_SYSTEM_AS_ROOT = 'detect-system-as-root' 68 _SYSTEM_FILES = ['compatibility_matrix.xml', 'build.prop'] 69 70 def __init__(self, image_filename, path_prefix): 71 super(ImageMounter, self).__init__() 72 self._image_filename = image_filename 73 self._path_prefix = path_prefix 74 75 @classmethod 76 def _detect_system_as_root(cls, raw_image_file): 77 """Returns True if the image layout of raw_image_file is system-as-root.""" 78 logging.debug('Checking system-as-root in %s...', raw_image_file) 79 80 system_without_root = True 81 for filename in cls._SYSTEM_FILES: 82 file_spec = os.path.join('/', filename) 83 if debugfs.get_type(raw_image_file, file_spec) != 'regular': 84 system_without_root = False 85 break 86 87 system_as_root = True 88 for filename in cls._SYSTEM_FILES: 89 file_spec = os.path.join('/system', filename) 90 if debugfs.get_type(raw_image_file, file_spec) != 'regular': 91 system_as_root = False 92 break 93 94 ret = system_as_root and not system_without_root 95 logging.debug( 96 'Checked system-as-root=%s system_without_root=%s result=%s', 97 system_as_root, 98 system_without_root, 99 ret) 100 return ret 101 102 # override 103 def _handle_mount(self): 104 # Unsparse the image to a temp file 105 unsparsed_suffix = '_system.img.raw' 106 unsparsed_file = tempfile.NamedTemporaryFile(suffix=unsparsed_suffix) 107 unsparsed_filename = unsparsed_file.name 108 image_utils.unsparse(unsparsed_filename, self._image_filename) 109 110 # detect system-as-root if need 111 path_prefix = self._path_prefix 112 if path_prefix == self.DETECT_SYSTEM_AS_ROOT: 113 path_prefix = '/' if self._detect_system_as_root( 114 unsparsed_filename) else '/system/' 115 116 # Create a temp dir for the target of copying file from image 117 temp_dir = tempfile.mkdtemp() 118 logging.debug('Created temp dir: %s', temp_dir) 119 120 # Keep data to be removed on __exit__ 121 self._unsparsed_file = unsparsed_file 122 self._temp_dir = tempfile.mkdtemp() 123 124 return _ImageFileAccessor(path_prefix, unsparsed_filename, temp_dir) 125 126 # override 127 def _handle_unmount(self): 128 if hasattr(self, '_temp_dir'): 129 logging.debug('Removing temp dir: %s', self._temp_dir) 130 shutil.rmtree(self._temp_dir) 131 del self._temp_dir 132 133 if hasattr(self, '_unsparsed_file'): 134 # will also delete the temp file implicitly 135 del self._unsparsed_file 136