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 CompositeMounter.
15
16CompositeMounter implements the abstract class BaseMounter. It can add multiple
17mounters inside as sub-mounters, and operate these sub-mounters with the
18BaseMounter interface. Uses CompositeMounter.add_sub_mounter() to add
19sub-mounter.
20
21Usually, using CompositeMounter.add_by_mount_target() to add mounters is easier,
22the method uses class _MounterFactory to create a mounter and then adds it.
23
24class _MounterFactory provides a method to create a mounter by 'mounter_target'.
25'mounter_target' is a name which identify what is the file source to be
26mounted. See _MounterFactory.create_by_mount_target() for the detail.
27"""
28
29import logging
30import os
31
32from gsi_util.mounters import adb_mounter
33from gsi_util.mounters import base_mounter
34from gsi_util.mounters import folder_mounter
35from gsi_util.mounters import image_mounter
36
37SUPPORTED_PARTITIONS = ['system', 'vendor', 'odm']
38
39
40class _MounterFactory(object):
41
42  @classmethod
43  def create_by_mount_target(cls, mount_target, partition):
44    """Create a proper Mounter instance by a string of mount target.
45
46    Args:
47      partition: the partition to be mounted as
48      mount_target: 'adb', a folder name or an image file name to mount.
49        see Returns for the detail.
50
51    Returns:
52      Returns an AdbMounter if mount_target is 'adb[:SERIAL_NUM]'
53      Returns a FolderMounter if mount_target is a folder name
54      Returns an ImageMounter if mount_target is an image file name
55
56    Raises:
57      ValueError: partiton is not support or mount_target is not exist.
58    """
59    if partition not in SUPPORTED_PARTITIONS:
60      raise ValueError('Wrong partition name "{}"'.format(partition))
61
62    if mount_target == 'adb' or mount_target.startswith('adb:'):
63      (_, _, serial_num) = mount_target.partition(':')
64      return adb_mounter.AdbMounter(serial_num)
65
66    path_prefix = '/{}/'.format(partition)
67
68    if os.path.isdir(mount_target):
69      return folder_mounter.FolderMounter(mount_target, path_prefix)
70
71    if os.path.isfile(mount_target):
72      if partition == 'system':
73        path_prefix = image_mounter.ImageMounter.DETECT_SYSTEM_AS_ROOT
74      return image_mounter.ImageMounter(mount_target, path_prefix)
75
76    raise ValueError('Unknown target "{}"'.format(mount_target))
77
78
79class _CompositeFileAccessor(base_mounter.BaseFileAccessor):
80
81  def __init__(self, file_accessors):
82    super(_CompositeFileAccessor, self).__init__()
83    self._file_accessors = file_accessors
84
85  # override
86  def _handle_prepare_file(self, filename_in_storage):
87    logging.debug('_CompositeFileAccessor._handle_prepare_file(%s)',
88                  filename_in_storage)
89
90    pathfile_to_prepare = '/' + filename_in_storage
91    for (prefix_path, file_accessor) in self._file_accessors:
92      if pathfile_to_prepare.startswith(prefix_path):
93        return file_accessor.prepare_file(pathfile_to_prepare)
94
95    logging.debug('  Not found')
96    return None
97
98
99class CompositeMounter(base_mounter.BaseMounter):
100  """Implements a BaseMounter which can add multiple sub-mounters."""
101
102  def __init__(self):
103    super(CompositeMounter, self).__init__()
104    self._mounters = []
105
106  def is_empty(self):
107    return not self._mounters
108
109  # override
110  def _handle_mount(self):
111    file_accessors = [(path_prefix, mounter.mount())
112                      for (path_prefix, mounter) in self._mounters]
113    return _CompositeFileAccessor(file_accessors)
114
115  # override
116  def _handle_unmount(self):
117    for (_, mounter) in reversed(self._mounters):
118      mounter.unmount()
119
120  def add_sub_mounter(self, mount_point, mounter):
121    self._mounters.append((mount_point, mounter))
122
123  def add_by_mount_target(self, partition, mount_target):
124    logging.debug('CompositeMounter.add_by_mount_target(%s, %s)',
125                  partition, mount_target)
126    mount_point = '/{}/'.format(partition)
127    mounter = _MounterFactory.create_by_mount_target(mount_target, partition)
128    self.add_sub_mounter(mount_point, mounter)
129