blob: 2cfb73a564dd853285106d543a8322730495db74 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (c) 2012 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.
"""Tools to mount partition in an image or a block device."""
import argparse
import logging
import os
import stat
import struct
import tempfile
import time
from contextlib import contextmanager
import factory_common # pylint: disable=W0611
from cros.factory.utils.process_utils import Spawn
class MountPartitionException(Exception):
"""Exception for MountPartition."""
pass
def MountPartition(source_path, index=None, mount_point=None, rw=False,
is_omaha_channel=False, options=None):
'''Mounts a partition in an image or a block device.
Args:
source_path: The image file or a block device.
index: The index of the partition, or None to mount as a single
partition. If source_path is a block device, index must be None.
Note that if is_omaha_channel is set, it is ignored.
mount_point: The mount point. If None, a temporary directory is used.
rw: Whether to mount as read/write.
is_omaha_channel: if it is True and source_path is a file, treats
source_path as a mini-Omaha channel file (kernel+rootfs) and mounts the
rootfs. rootfs offset bytes: 8 + BigEndian(first-8-bytes).
options: A list of options to add to the -o argument when mounting, e.g.,
['offset=8192', 'sizelimit=1048576'].
Raises:
OSError: if image file or mount point doesn't exist.
subprocess.CalledProcessError: if mount fails.
MountPartitionException: if index is given while source_path is a block
device.
'''
if not mount_point:
mount_point = tempfile.mkdtemp(prefix='mount_partition.')
remove_mount_point = True
else:
remove_mount_point = False
if not os.path.exists(source_path):
raise OSError('Image file %s does not exist' % source_path)
if not os.path.isdir(mount_point):
raise OSError('Mount point %s does not exist', mount_point)
for line in open('/etc/mtab').readlines():
if line.split()[1] == mount_point:
raise OSError('Mount point %s is already mounted' % mount_point)
all_options = ['rw' if rw else 'ro']
# source_path is a block device.
if stat.S_ISBLK(os.stat(source_path).st_mode):
if index:
raise MountPartitionException('index must be None for a block device.')
if is_omaha_channel:
raise MountPartitionException(
'is_omaha_channel must be False for a block device.')
else:
# Use loop option on image file.
all_options.append('loop')
if is_omaha_channel:
with open(source_path, 'rb') as f:
first_8_bytes = f.read(8)
offset = struct.unpack('>Q', first_8_bytes)[0] + 8
all_options.append('offset=%d' % offset)
elif index:
def RunCGPT(option):
'''Runs cgpt and returns the integer result.'''
return int(
Spawn(['cgpt', 'show', '-i', str(index),
option, source_path],
read_stdout=True, check_call=True).stdout_data)
offset = RunCGPT('-b') * 512
all_options.append('offset=%d' % offset)
sizelimit = RunCGPT('-s') * 512
all_options.append('sizelimit=%d' % sizelimit)
if options:
all_options.extend(options)
Spawn(['mount', '-o', ','.join(all_options), source_path, mount_point],
log=True, check_call=True, sudo=True)
@contextmanager
def Unmounter():
try:
yield mount_point
finally:
logging.info('Unmounting %s', mount_point)
for _ in range(5):
if Spawn(['umount', mount_point], call=True, sudo=True,
ignore_stderr=True).returncode == 0:
break
time.sleep(1) # And retry
else:
logging.warn('Unable to umount %s', mount_point)
if remove_mount_point:
try:
os.rmdir(mount_point)
except OSError:
pass
return Unmounter()
def main():
logging.basicConfig(level=logging.INFO)
parser = argparse.ArgumentParser(
description="Mount a partition in an image file.")
parser.add_argument('-rw', '--rw', action='store_true',
help='mount partition read/write')
parser.add_argument('source_path', help='an image file or a block device')
parser.add_argument('index', type=int, help='partition index')
parser.add_argument('mount_point', help='mount point')
args = parser.parse_args()
MountPartition(args.source_path, args.index, args.mount_point, args.rw)
if __name__ == '__main__':
main()