928 lines
31 KiB
Python
928 lines
31 KiB
Python
# Lint as: python2, python3
|
|
"""
|
|
APIs to write tests and control files that handle partition creation, deletion
|
|
and formatting.
|
|
|
|
@copyright: Google 2006-2008
|
|
@author: Martin Bligh (mbligh@google.com)
|
|
"""
|
|
|
|
# pylint: disable=missing-docstring
|
|
|
|
import os, re, string, sys, fcntl, logging
|
|
from autotest_lib.client.bin import os_dep, utils
|
|
from autotest_lib.client.common_lib import error
|
|
|
|
|
|
class FsOptions(object):
|
|
"""
|
|
A class encapsulating a filesystem test's parameters.
|
|
"""
|
|
# NOTE(gps): This class could grow or be merged with something else in the
|
|
# future that actually uses the encapsulated data (say to run mkfs) rather
|
|
# than just being a container.
|
|
|
|
__slots__ = ('fstype', 'mkfs_flags', 'mount_options', 'fs_tag')
|
|
|
|
def __init__(self, fstype, fs_tag, mkfs_flags=None, mount_options=None):
|
|
"""
|
|
Fill in our properties.
|
|
|
|
@param fstype: The filesystem type ('ext2', 'ext4', 'xfs', etc.)
|
|
@param fs_tag: A short name for this filesystem test to use
|
|
in the results.
|
|
@param mkfs_flags: Optional. Additional command line options to mkfs.
|
|
@param mount_options: Optional. The options to pass to mount -o.
|
|
"""
|
|
|
|
if not fstype or not fs_tag:
|
|
raise ValueError('A filesystem and fs_tag are required.')
|
|
self.fstype = fstype
|
|
self.fs_tag = fs_tag
|
|
self.mkfs_flags = mkfs_flags or ""
|
|
self.mount_options = mount_options or ""
|
|
|
|
|
|
def __str__(self):
|
|
val = ('FsOptions(fstype=%r, mkfs_flags=%r, '
|
|
'mount_options=%r, fs_tag=%r)' %
|
|
(self.fstype, self.mkfs_flags,
|
|
self.mount_options, self.fs_tag))
|
|
return val
|
|
|
|
|
|
def partname_to_device(part):
|
|
""" Converts a partition name to its associated device """
|
|
return os.path.join(os.sep, 'dev', part)
|
|
|
|
|
|
def list_mount_devices():
|
|
devices = []
|
|
# list mounted filesystems
|
|
for line in utils.system_output('mount').splitlines():
|
|
devices.append(line.split()[0])
|
|
# list mounted swap devices
|
|
for line in utils.system_output('swapon -s').splitlines():
|
|
if line.startswith('/'): # skip header line
|
|
devices.append(line.split()[0])
|
|
return devices
|
|
|
|
|
|
def list_mount_points():
|
|
mountpoints = []
|
|
for line in utils.system_output('mount').splitlines():
|
|
mountpoints.append(line.split()[2])
|
|
return mountpoints
|
|
|
|
|
|
def get_iosched_path(device_name, component):
|
|
return '/sys/block/%s/queue/%s' % (device_name, component)
|
|
|
|
|
|
def wipe_filesystem(job, mountpoint):
|
|
wipe_cmd = 'rm -rf %s/*' % mountpoint
|
|
try:
|
|
utils.system(wipe_cmd)
|
|
except:
|
|
job.record('FAIL', None, wipe_cmd, error.format_error())
|
|
raise
|
|
else:
|
|
job.record('GOOD', None, wipe_cmd)
|
|
|
|
|
|
def is_linux_fs_type(device):
|
|
"""
|
|
Checks if specified partition is type 83
|
|
|
|
@param device: the device, e.g. /dev/sda3
|
|
|
|
@return: False if the supplied partition name is not type 83 linux, True
|
|
otherwise
|
|
"""
|
|
disk_device = device.rstrip('0123456789')
|
|
|
|
# Parse fdisk output to get partition info. Ugly but it works.
|
|
fdisk_fd = os.popen("/sbin/fdisk -l -u '%s'" % disk_device)
|
|
fdisk_lines = fdisk_fd.readlines()
|
|
fdisk_fd.close()
|
|
for line in fdisk_lines:
|
|
if not line.startswith(device):
|
|
continue
|
|
info_tuple = line.split()
|
|
# The Id will be in one of two fields depending on if the boot flag
|
|
# was set. Caveat: this assumes no boot partition will be 83 blocks.
|
|
for fsinfo in info_tuple[4:6]:
|
|
if fsinfo == '83': # hex 83 is the linux fs partition type
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_partition_list(job, min_blocks=0, filter_func=None, exclude_swap=True,
|
|
open_func=open):
|
|
"""
|
|
Get a list of partition objects for all disk partitions on the system.
|
|
|
|
Loopback devices and unnumbered (whole disk) devices are always excluded.
|
|
|
|
@param job: The job instance to pass to the partition object
|
|
constructor.
|
|
@param min_blocks: The minimum number of blocks for a partition to
|
|
be considered.
|
|
@param filter_func: A callable that returns True if a partition is
|
|
desired. It will be passed one parameter:
|
|
The partition name (hdc3, etc.).
|
|
Some useful filter functions are already defined in this module.
|
|
@param exclude_swap: If True any partition actively in use as a swap
|
|
device will be excluded.
|
|
@param __open: Reserved for unit testing.
|
|
|
|
@return: A list of L{partition} objects.
|
|
"""
|
|
active_swap_devices = set()
|
|
if exclude_swap:
|
|
for swapline in open_func('/proc/swaps'):
|
|
if swapline.startswith('/'):
|
|
active_swap_devices.add(swapline.split()[0])
|
|
|
|
partitions = []
|
|
for partline in open_func('/proc/partitions').readlines():
|
|
fields = partline.strip().split()
|
|
if len(fields) != 4 or partline.startswith('major'):
|
|
continue
|
|
(major, minor, blocks, partname) = fields
|
|
blocks = int(blocks)
|
|
|
|
# The partition name better end with a digit, else it's not a partition
|
|
if not partname[-1].isdigit():
|
|
continue
|
|
|
|
# We don't want the loopback device in the partition list
|
|
if 'loop' in partname:
|
|
continue
|
|
|
|
device = partname_to_device(partname)
|
|
if exclude_swap and device in active_swap_devices:
|
|
logging.debug('Skipping %s - Active swap.', partname)
|
|
continue
|
|
|
|
if min_blocks and blocks < min_blocks:
|
|
logging.debug('Skipping %s - Too small.', partname)
|
|
continue
|
|
|
|
if filter_func and not filter_func(partname):
|
|
logging.debug('Skipping %s - Filter func.', partname)
|
|
continue
|
|
|
|
partitions.append(partition(job, device))
|
|
|
|
return partitions
|
|
|
|
|
|
def get_mount_info(partition_list):
|
|
"""
|
|
Picks up mount point information about the machine mounts. By default, we
|
|
try to associate mount points with UUIDs, because in newer distros the
|
|
partitions are uniquely identified using them.
|
|
"""
|
|
mount_info = set()
|
|
for p in partition_list:
|
|
try:
|
|
uuid = utils.system_output('blkid -p -s UUID -o value %s' % p.device)
|
|
except error.CmdError:
|
|
# fall back to using the partition
|
|
uuid = p.device
|
|
mount_info.add((uuid, p.get_mountpoint()))
|
|
|
|
return mount_info
|
|
|
|
|
|
def filter_partition_list(partitions, devnames):
|
|
"""
|
|
Pick and choose which partition to keep.
|
|
|
|
filter_partition_list accepts a list of partition objects and a list
|
|
of strings. If a partition has the device name of the strings it
|
|
is returned in a list.
|
|
|
|
@param partitions: A list of L{partition} objects
|
|
@param devnames: A list of devnames of the form '/dev/hdc3' that
|
|
specifies which partitions to include in the returned list.
|
|
|
|
@return: A list of L{partition} objects specified by devnames, in the
|
|
order devnames specified
|
|
"""
|
|
|
|
filtered_list = []
|
|
for p in partitions:
|
|
for d in devnames:
|
|
if p.device == d and p not in filtered_list:
|
|
filtered_list.append(p)
|
|
|
|
return filtered_list
|
|
|
|
|
|
def get_unmounted_partition_list(root_part, job=None, min_blocks=0,
|
|
filter_func=None, exclude_swap=True,
|
|
open_func=open):
|
|
"""
|
|
Return a list of partition objects that are not mounted.
|
|
|
|
@param root_part: The root device name (without the '/dev/' prefix, example
|
|
'hda2') that will be filtered from the partition list.
|
|
|
|
Reasoning: in Linux /proc/mounts will never directly mention the
|
|
root partition as being mounted on / instead it will say that
|
|
/dev/root is mounted on /. Thus require this argument to filter out
|
|
the root_part from the ones checked to be mounted.
|
|
@param job, min_blocks, filter_func, exclude_swap, open_func: Forwarded
|
|
to get_partition_list().
|
|
@return List of L{partition} objects that are not mounted.
|
|
"""
|
|
partitions = get_partition_list(job=job, min_blocks=min_blocks,
|
|
filter_func=filter_func, exclude_swap=exclude_swap, open_func=open_func)
|
|
|
|
unmounted = []
|
|
for part in partitions:
|
|
if (part.device != partname_to_device(root_part) and
|
|
not part.get_mountpoint(open_func=open_func)):
|
|
unmounted.append(part)
|
|
|
|
return unmounted
|
|
|
|
|
|
def parallel(partitions, method_name, *args, **dargs):
|
|
"""
|
|
Run a partition method (with appropriate arguments) in parallel,
|
|
across a list of partition objects
|
|
"""
|
|
if not partitions:
|
|
return
|
|
job = partitions[0].job
|
|
flist = []
|
|
if (not hasattr(partition, method_name) or
|
|
not callable(getattr(partition, method_name))):
|
|
err = "partition.parallel got invalid method %s" % method_name
|
|
raise RuntimeError(err)
|
|
|
|
for p in partitions:
|
|
print_args = list(args)
|
|
print_args += ['%s=%s' % (key, dargs[key]) for key in dargs.keys()]
|
|
logging.debug('%s.%s(%s)', str(p), method_name,
|
|
', '.join(print_args))
|
|
sys.stdout.flush()
|
|
def _run_named_method(function, part=p):
|
|
getattr(part, method_name)(*args, **dargs)
|
|
flist.append((_run_named_method, ()))
|
|
job.parallel(*flist)
|
|
|
|
|
|
def filesystems():
|
|
"""
|
|
Return a list of all available filesystems
|
|
"""
|
|
return [re.sub('(nodev)?\s*', '', fs) for fs in open('/proc/filesystems')]
|
|
|
|
|
|
def unmount_partition(device):
|
|
"""
|
|
Unmount a mounted partition
|
|
|
|
@param device: e.g. /dev/sda1, /dev/hda1
|
|
"""
|
|
p = partition(job=None, device=device)
|
|
p.unmount(record=False)
|
|
|
|
|
|
def is_valid_partition(device):
|
|
"""
|
|
Checks if a partition is valid
|
|
|
|
@param device: e.g. /dev/sda1, /dev/hda1
|
|
"""
|
|
parts = get_partition_list(job=None)
|
|
p_list = [ p.device for p in parts ]
|
|
if device in p_list:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def is_valid_disk(device):
|
|
"""
|
|
Checks if a disk is valid
|
|
|
|
@param device: e.g. /dev/sda, /dev/hda
|
|
"""
|
|
partitions = []
|
|
for partline in open('/proc/partitions').readlines():
|
|
fields = partline.strip().split()
|
|
if len(fields) != 4 or partline.startswith('major'):
|
|
continue
|
|
(major, minor, blocks, partname) = fields
|
|
blocks = int(blocks)
|
|
|
|
if not partname[-1].isdigit():
|
|
# Disk name does not end in number, AFAIK
|
|
# so use it as a reference to a disk
|
|
if device.strip("/dev/") == partname:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def run_test_on_partitions(job, test, partitions, mountpoint_func,
|
|
tag, fs_opt, do_fsck=True, **dargs):
|
|
"""
|
|
Run a test that requires multiple partitions. Filesystems will be
|
|
made on the partitions and mounted, then the test will run, then the
|
|
filesystems will be unmounted and optionally fsck'd.
|
|
|
|
@param job: A job instance to run the test
|
|
@param test: A string containing the name of the test
|
|
@param partitions: A list of partition objects, these are passed to the
|
|
test as partitions=
|
|
@param mountpoint_func: A callable that returns a mountpoint given a
|
|
partition instance
|
|
@param tag: A string tag to make this test unique (Required for control
|
|
files that make multiple calls to this routine with the same value
|
|
of 'test'.)
|
|
@param fs_opt: An FsOptions instance that describes what filesystem to make
|
|
@param do_fsck: include fsck in post-test partition cleanup.
|
|
@param dargs: Dictionary of arguments to be passed to job.run_test() and
|
|
eventually the test
|
|
"""
|
|
# setup the filesystem parameters for all the partitions
|
|
for p in partitions:
|
|
p.set_fs_options(fs_opt)
|
|
|
|
# make and mount all the partitions in parallel
|
|
parallel(partitions, 'setup_before_test', mountpoint_func=mountpoint_func)
|
|
|
|
mountpoint = mountpoint_func(partitions[0])
|
|
|
|
# run the test against all the partitions
|
|
job.run_test(test, tag=tag, partitions=partitions, dir=mountpoint, **dargs)
|
|
|
|
parallel(partitions, 'unmount') # unmount all partitions in parallel
|
|
if do_fsck:
|
|
parallel(partitions, 'fsck') # fsck all partitions in parallel
|
|
# else fsck is done by caller
|
|
|
|
|
|
class partition(object):
|
|
"""
|
|
Class for handling partitions and filesystems
|
|
"""
|
|
|
|
def __init__(self, job, device, loop_size=0, mountpoint=None):
|
|
"""
|
|
@param job: A L{client.bin.job} instance.
|
|
@param device: The device in question (e.g."/dev/hda2"). If device is a
|
|
file it will be mounted as loopback.
|
|
@param loop_size: Size of loopback device (in MB). Defaults to 0.
|
|
"""
|
|
self.device = device
|
|
self.name = os.path.basename(device)
|
|
self.job = job
|
|
self.loop = loop_size
|
|
self.fstype = None
|
|
self.mountpoint = mountpoint
|
|
self.mkfs_flags = None
|
|
self.mount_options = None
|
|
self.fs_tag = None
|
|
if self.loop:
|
|
cmd = 'dd if=/dev/zero of=%s bs=1M count=%d' % (device, loop_size)
|
|
utils.system(cmd)
|
|
|
|
|
|
def __repr__(self):
|
|
return '<Partition: %s>' % self.device
|
|
|
|
|
|
def set_fs_options(self, fs_options):
|
|
"""
|
|
Set filesystem options
|
|
|
|
@param fs_options: A L{FsOptions} object
|
|
"""
|
|
|
|
self.fstype = fs_options.fstype
|
|
self.mkfs_flags = fs_options.mkfs_flags
|
|
self.mount_options = fs_options.mount_options
|
|
self.fs_tag = fs_options.fs_tag
|
|
|
|
|
|
def run_test(self, test, **dargs):
|
|
self.job.run_test(test, dir=self.get_mountpoint(), **dargs)
|
|
|
|
|
|
def setup_before_test(self, mountpoint_func):
|
|
"""
|
|
Prepare a partition for running a test. Unmounts any
|
|
filesystem that's currently mounted on the partition, makes a
|
|
new filesystem (according to this partition's filesystem
|
|
options) and mounts it where directed by mountpoint_func.
|
|
|
|
@param mountpoint_func: A callable that returns a path as a string,
|
|
given a partition instance.
|
|
"""
|
|
mountpoint = mountpoint_func(self)
|
|
if not mountpoint:
|
|
raise ValueError('Don\'t know where to put this partition')
|
|
self.unmount(ignore_status=True, record=False)
|
|
self.mkfs()
|
|
if not os.path.isdir(mountpoint):
|
|
os.makedirs(mountpoint)
|
|
self.mount(mountpoint)
|
|
|
|
|
|
def run_test_on_partition(self, test, mountpoint_func, **dargs):
|
|
"""
|
|
Executes a test fs-style (umount,mkfs,mount,test)
|
|
|
|
Here we unmarshal the args to set up tags before running the test.
|
|
Tests are also run by first umounting, mkfsing and then mounting
|
|
before executing the test.
|
|
|
|
@param test: name of test to run
|
|
@param mountpoint_func: function to return mount point string
|
|
"""
|
|
tag = dargs.get('tag')
|
|
if tag:
|
|
tag = '%s.%s' % (self.name, tag)
|
|
elif self.fs_tag:
|
|
tag = '%s.%s' % (self.name, self.fs_tag)
|
|
else:
|
|
tag = self.name
|
|
|
|
# If there's a 'suffix' argument, append it to the tag and remove it
|
|
suffix = dargs.pop('suffix', None)
|
|
if suffix:
|
|
tag = '%s.%s' % (tag, suffix)
|
|
|
|
dargs['tag'] = test + '.' + tag
|
|
|
|
def _make_partition_and_run_test(test_tag, dir=None, **dargs):
|
|
self.setup_before_test(mountpoint_func)
|
|
try:
|
|
self.job.run_test(test, tag=test_tag, dir=mountpoint, **dargs)
|
|
finally:
|
|
self.unmount()
|
|
self.fsck()
|
|
|
|
|
|
mountpoint = mountpoint_func(self)
|
|
|
|
# The tag is the tag for the group (get stripped off by run_group)
|
|
# The test_tag is the tag for the test itself
|
|
self.job.run_group(_make_partition_and_run_test,
|
|
test_tag=tag, dir=mountpoint, **dargs)
|
|
|
|
|
|
def get_mountpoint(self, open_func=open, filename=None):
|
|
"""
|
|
Find the mount point of this partition object.
|
|
|
|
@param open_func: the function to use for opening the file containing
|
|
the mounted partitions information
|
|
@param filename: where to look for the mounted partitions information
|
|
(default None which means it will search /proc/mounts and/or
|
|
/etc/mtab)
|
|
|
|
@returns a string with the mount point of the partition or None if not
|
|
mounted
|
|
"""
|
|
if filename:
|
|
for line in open_func(filename).readlines():
|
|
parts = line.split()
|
|
if parts[0] == self.device or parts[1] == self.mountpoint:
|
|
return parts[1] # The mountpoint where it's mounted
|
|
return None
|
|
|
|
# no specific file given, look in /proc/mounts
|
|
res = self.get_mountpoint(open_func=open_func, filename='/proc/mounts')
|
|
if not res:
|
|
# sometimes the root partition is reported as /dev/root in
|
|
# /proc/mounts in this case, try /etc/mtab
|
|
res = self.get_mountpoint(open_func=open_func, filename='/etc/mtab')
|
|
|
|
# trust /etc/mtab only about /
|
|
if res != '/':
|
|
res = None
|
|
|
|
return res
|
|
|
|
|
|
def mkfs_exec(self, fstype):
|
|
"""
|
|
Return the proper mkfs executable based on fs
|
|
"""
|
|
if fstype == 'ext4':
|
|
if os.path.exists('/sbin/mkfs.ext4'):
|
|
return 'mkfs'
|
|
# If ext4 supported e2fsprogs is not installed we use the
|
|
# autotest supplied one in tools dir which is statically linked"""
|
|
auto_mkfs = os.path.join(self.job.toolsdir, 'mkfs.ext4dev')
|
|
if os.path.exists(auto_mkfs):
|
|
return auto_mkfs
|
|
else:
|
|
return 'mkfs'
|
|
|
|
raise NameError('Error creating partition for filesystem type %s' %
|
|
fstype)
|
|
|
|
|
|
def mkfs(self, fstype=None, args='', record=True):
|
|
"""
|
|
Format a partition to filesystem type
|
|
|
|
@param fstype: the filesystem type, e.g.. "ext3", "ext2"
|
|
@param args: arguments to be passed to mkfs command.
|
|
@param record: if set, output result of mkfs operation to autotest
|
|
output
|
|
"""
|
|
|
|
if list_mount_devices().count(self.device):
|
|
raise NameError('Attempted to format mounted device %s' %
|
|
self.device)
|
|
|
|
if not fstype:
|
|
if self.fstype:
|
|
fstype = self.fstype
|
|
else:
|
|
fstype = 'ext2'
|
|
|
|
if self.mkfs_flags:
|
|
args += ' ' + self.mkfs_flags
|
|
if fstype == 'xfs':
|
|
args += ' -f'
|
|
|
|
if self.loop:
|
|
# BAH. Inconsistent mkfs syntax SUCKS.
|
|
if fstype.startswith('ext'):
|
|
args += ' -F'
|
|
elif fstype == 'reiserfs':
|
|
args += ' -f'
|
|
|
|
# If there isn't already a '-t <type>' argument, add one.
|
|
if not "-t" in args:
|
|
args = "-t %s %s" % (fstype, args)
|
|
|
|
args = args.strip()
|
|
|
|
mkfs_cmd = "%s %s %s" % (self.mkfs_exec(fstype), args, self.device)
|
|
|
|
sys.stdout.flush()
|
|
try:
|
|
# We throw away the output here - we only need it on error, in
|
|
# which case it's in the exception
|
|
utils.system_output("yes | %s" % mkfs_cmd)
|
|
except error.CmdError as e:
|
|
logging.error(e.result_obj)
|
|
if record:
|
|
self.job.record('FAIL', None, mkfs_cmd, error.format_error())
|
|
raise
|
|
except:
|
|
if record:
|
|
self.job.record('FAIL', None, mkfs_cmd, error.format_error())
|
|
raise
|
|
else:
|
|
if record:
|
|
self.job.record('GOOD', None, mkfs_cmd)
|
|
self.fstype = fstype
|
|
|
|
|
|
def get_fsck_exec(self):
|
|
"""
|
|
Return the proper mkfs executable based on self.fstype
|
|
"""
|
|
if self.fstype == 'ext4':
|
|
if os.path.exists('/sbin/fsck.ext4'):
|
|
return 'fsck'
|
|
# If ext4 supported e2fsprogs is not installed we use the
|
|
# autotest supplied one in tools dir which is statically linked"""
|
|
auto_fsck = os.path.join(self.job.toolsdir, 'fsck.ext4dev')
|
|
if os.path.exists(auto_fsck):
|
|
return auto_fsck
|
|
else:
|
|
return 'fsck'
|
|
|
|
raise NameError('Error creating partition for filesystem type %s' %
|
|
self.fstype)
|
|
|
|
|
|
def fsck(self, args='-fy', record=True):
|
|
"""
|
|
Run filesystem check
|
|
|
|
@param args: arguments to filesystem check tool. Default is "-n"
|
|
which works on most tools.
|
|
"""
|
|
|
|
# I hate reiserfstools.
|
|
# Requires an explit Yes for some inane reason
|
|
fsck_cmd = '%s %s %s' % (self.get_fsck_exec(), self.device, args)
|
|
if self.fstype == 'reiserfs':
|
|
fsck_cmd = 'yes "Yes" | ' + fsck_cmd
|
|
sys.stdout.flush()
|
|
try:
|
|
utils.system_output(fsck_cmd)
|
|
except:
|
|
if record:
|
|
self.job.record('FAIL', None, fsck_cmd, error.format_error())
|
|
raise error.TestError('Fsck found errors with the underlying '
|
|
'file system')
|
|
else:
|
|
if record:
|
|
self.job.record('GOOD', None, fsck_cmd)
|
|
|
|
|
|
def mount(self, mountpoint=None, fstype=None, args='', record=True):
|
|
"""
|
|
Mount this partition to a mount point
|
|
|
|
@param mountpoint: If you have not provided a mountpoint to partition
|
|
object or want to use a different one, you may specify it here.
|
|
@param fstype: Filesystem type. If not provided partition object value
|
|
will be used.
|
|
@param args: Arguments to be passed to "mount" command.
|
|
@param record: If True, output result of mount operation to autotest
|
|
output.
|
|
"""
|
|
|
|
if fstype is None:
|
|
fstype = self.fstype
|
|
else:
|
|
assert(self.fstype is None or self.fstype == fstype);
|
|
|
|
if self.mount_options:
|
|
args += ' -o ' + self.mount_options
|
|
if fstype:
|
|
args += ' -t ' + fstype
|
|
if self.loop:
|
|
args += ' -o loop'
|
|
args = args.lstrip()
|
|
|
|
if not mountpoint and not self.mountpoint:
|
|
raise ValueError("No mountpoint specified and no default "
|
|
"provided to this partition object")
|
|
if not mountpoint:
|
|
mountpoint = self.mountpoint
|
|
|
|
mount_cmd = "mount %s %s %s" % (args, self.device, mountpoint)
|
|
|
|
if list_mount_devices().count(self.device):
|
|
err = 'Attempted to mount mounted device'
|
|
self.job.record('FAIL', None, mount_cmd, err)
|
|
raise NameError(err)
|
|
if list_mount_points().count(mountpoint):
|
|
err = 'Attempted to mount busy mountpoint'
|
|
self.job.record('FAIL', None, mount_cmd, err)
|
|
raise NameError(err)
|
|
|
|
mtab = open('/etc/mtab')
|
|
# We have to get an exclusive lock here - mount/umount are racy
|
|
fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
|
|
sys.stdout.flush()
|
|
try:
|
|
utils.system(mount_cmd)
|
|
mtab.close()
|
|
except:
|
|
mtab.close()
|
|
if record:
|
|
self.job.record('FAIL', None, mount_cmd, error.format_error())
|
|
raise
|
|
else:
|
|
if record:
|
|
self.job.record('GOOD', None, mount_cmd)
|
|
self.fstype = fstype
|
|
|
|
|
|
def unmount_force(self):
|
|
"""
|
|
Kill all other jobs accessing this partition. Use fuser and ps to find
|
|
all mounts on this mountpoint and unmount them.
|
|
|
|
@return: true for success or false for any errors
|
|
"""
|
|
|
|
logging.debug("Standard umount failed, will try forcing. Users:")
|
|
try:
|
|
cmd = 'fuser ' + self.get_mountpoint()
|
|
logging.debug(cmd)
|
|
fuser = utils.system_output(cmd)
|
|
logging.debug(fuser)
|
|
users = re.sub('.*:', '', fuser).split()
|
|
for user in users:
|
|
m = re.match('(\d+)(.*)', user)
|
|
(pid, usage) = (m.group(1), m.group(2))
|
|
try:
|
|
ps = utils.system_output('ps -p %s | sed 1d' % pid)
|
|
logging.debug('%s %s %s', usage, pid, ps)
|
|
except Exception:
|
|
pass
|
|
utils.system('ls -l ' + self.device)
|
|
umount_cmd = "umount -f " + self.device
|
|
utils.system(umount_cmd)
|
|
return True
|
|
except error.CmdError:
|
|
logging.debug('Umount_force failed for %s', self.device)
|
|
return False
|
|
|
|
|
|
|
|
def unmount(self, ignore_status=False, record=True):
|
|
"""
|
|
Umount this partition.
|
|
|
|
It's easier said than done to umount a partition.
|
|
We need to lock the mtab file to make sure we don't have any
|
|
locking problems if we are umounting in paralllel.
|
|
|
|
If there turns out to be a problem with the simple umount we
|
|
end up calling umount_force to get more agressive.
|
|
|
|
@param ignore_status: should we notice the umount status
|
|
@param record: if True, output result of umount operation to
|
|
autotest output
|
|
"""
|
|
|
|
mountpoint = self.get_mountpoint()
|
|
if not mountpoint:
|
|
# It's not even mounted to start with
|
|
if record and not ignore_status:
|
|
msg = 'umount for dev %s has no mountpoint' % self.device
|
|
self.job.record('FAIL', None, msg, 'Not mounted')
|
|
return
|
|
|
|
umount_cmd = "umount " + mountpoint
|
|
mtab = open('/etc/mtab')
|
|
|
|
# We have to get an exclusive lock here - mount/umount are racy
|
|
fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
|
|
sys.stdout.flush()
|
|
try:
|
|
utils.system(umount_cmd)
|
|
mtab.close()
|
|
if record:
|
|
self.job.record('GOOD', None, umount_cmd)
|
|
except (error.CmdError, IOError):
|
|
mtab.close()
|
|
|
|
# Try the forceful umount
|
|
if self.unmount_force():
|
|
return
|
|
|
|
# If we are here we cannot umount this partition
|
|
if record and not ignore_status:
|
|
self.job.record('FAIL', None, umount_cmd, error.format_error())
|
|
raise
|
|
|
|
|
|
def wipe(self):
|
|
"""
|
|
Delete all files of a given partition filesystem.
|
|
"""
|
|
wipe_filesystem(self.job, self.get_mountpoint())
|
|
|
|
|
|
def get_io_scheduler_list(self, device_name):
|
|
names = open(self.__sched_path(device_name)).read()
|
|
return names.translate(string.maketrans('[]', ' ')).split()
|
|
|
|
|
|
def get_io_scheduler(self, device_name):
|
|
return re.split('[\[\]]',
|
|
open(self.__sched_path(device_name)).read())[1]
|
|
|
|
|
|
def set_io_scheduler(self, device_name, name):
|
|
if name not in self.get_io_scheduler_list(device_name):
|
|
raise NameError('No such IO scheduler: %s' % name)
|
|
f = open(self.__sched_path(device_name), 'w')
|
|
f.write(name)
|
|
f.close()
|
|
|
|
|
|
def __sched_path(self, device_name):
|
|
return '/sys/block/%s/queue/scheduler' % device_name
|
|
|
|
|
|
class virtual_partition:
|
|
"""
|
|
Handles block device emulation using file images of disks.
|
|
It's important to note that this API can be used only if
|
|
we have the following programs present on the client machine:
|
|
|
|
* dd
|
|
* losetup
|
|
* truncate
|
|
"""
|
|
def __init__(self, file_img, file_size):
|
|
"""
|
|
Creates a virtual partition, keeping record of the device created
|
|
under /dev/mapper (device attribute) so test writers can use it
|
|
on their filesystem tests.
|
|
|
|
@param file_img: Path to the desired disk image file.
|
|
@param file_size: Size of the desired image in Bytes.
|
|
"""
|
|
logging.debug('Quick check before attempting to create virtual '
|
|
'partition')
|
|
try:
|
|
os_dep.commands('dd', 'losetup', 'truncate')
|
|
except ValueError as e:
|
|
e_msg = 'Unable to create virtual partition: %s' % e
|
|
raise error.AutotestError(e_msg)
|
|
|
|
logging.debug('Creating virtual partition')
|
|
self.size = file_size
|
|
self.img = self._create_disk_img(file_img)
|
|
self.loop = self._attach_img_loop()
|
|
self.device = self.loop
|
|
logging.debug('Virtual partition successfuly created')
|
|
logging.debug('Image disk: %s', self.img)
|
|
logging.debug('Loopback device: %s', self.loop)
|
|
logging.debug('Device path: %s', self.device)
|
|
|
|
|
|
def destroy(self):
|
|
"""
|
|
Removes the virtual partition from /dev/mapper, detaches the image file
|
|
from the loopback device and removes the image file.
|
|
"""
|
|
logging.debug('Removing virtual partition - device %s', self.device)
|
|
self._detach_img_loop()
|
|
self._remove_disk_img()
|
|
|
|
|
|
def _create_disk_img(self, img_path):
|
|
"""
|
|
Creates a disk image using dd.
|
|
|
|
@param img_path: Path to the desired image file.
|
|
@param size: Size of the desired image in MB.
|
|
@returns: Path of the image created.
|
|
"""
|
|
logging.debug('Creating disk image %s, size = %d MB',
|
|
img_path, self.size)
|
|
try:
|
|
cmd = 'truncate %s --size %dM' % (img_path, self.size)
|
|
utils.run(cmd)
|
|
except error.CmdError as e:
|
|
e_msg = 'Error creating disk image %s: %s' % (img_path, e)
|
|
raise error.AutotestError(e_msg)
|
|
return img_path
|
|
|
|
|
|
def _attach_img_loop(self):
|
|
"""
|
|
Attaches a file image to a loopback device using losetup.
|
|
@returns: Path of the loopback device associated.
|
|
"""
|
|
logging.debug('Attaching image %s to a loop device', self.img)
|
|
try:
|
|
cmd = 'losetup -f'
|
|
loop_path = utils.system_output(cmd)
|
|
cmd = 'losetup -f %s' % self.img
|
|
utils.run(cmd)
|
|
except error.CmdError as e:
|
|
e_msg = ('Error attaching image %s to a loop device: %s' %
|
|
(self.img, e))
|
|
raise error.AutotestError(e_msg)
|
|
return loop_path
|
|
|
|
|
|
def _detach_img_loop(self):
|
|
"""
|
|
Detaches the image file from the loopback device.
|
|
"""
|
|
logging.debug('Detaching image %s from loop device %s', self.img,
|
|
self.loop)
|
|
try:
|
|
cmd = 'losetup -d %s' % self.loop
|
|
utils.run(cmd)
|
|
except error.CmdError as e:
|
|
e_msg = ('Error detaching image %s from loop device %s: %s' %
|
|
(self.img, self.loop, e))
|
|
raise error.AutotestError(e_msg)
|
|
|
|
|
|
def _remove_disk_img(self):
|
|
"""
|
|
Removes the disk image.
|
|
"""
|
|
logging.debug('Removing disk image %s', self.img)
|
|
try:
|
|
os.remove(self.img)
|
|
except:
|
|
e_msg = 'Error removing image file %s' % self.img
|
|
raise error.AutotestError(e_msg)
|
|
|
|
|
|
# import a site partition module to allow it to override functions
|
|
try:
|
|
from autotest_lib.client.bin.site_partition import *
|
|
except ImportError:
|
|
pass
|