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