New version of the partition refactoring, there was some method behind the
execfile madness. Instead, I got rid of base_partition and moved the
site_partition import into partition so that overrides in site_partition
actually get used by the code in base_partition; otherwise, users of partition
were getting the overridden methods but code in base_partition would continue
to only use base_partition code.
This required changing how the unit test handles just importing the "base" code.
Unfortunately the best I could come up with was some sys.modules hackery with
reload, but as lame as it is, it works reasonably well enough.
Signed-off-by: John Admanski <jadmanski@google.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@4741 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/bin/partition.py b/client/bin/partition.py
index 98c1307..61b24c0 100644
--- a/client/bin/partition.py
+++ b/client/bin/partition.py
@@ -1,9 +1,985 @@
"""
-This is a high level partition module that executes the contents of
-base_partition.py and if it exists the contents of site_partition.py.
+APIs to write tests and control files that handle partition creation, deletion
+and formatting.
+
+@copyright: Google 2006-2008
+@author: Martin Bligh (mbligh@google.com)
"""
-from autotest_lib.client.bin.base_partition import *
+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.
+ # Ex: fsdev_disks.mkfs_all_disks really should become a method.
+
+ __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 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, **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 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 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)
+
+ # fsck and then remake all the filesystems in parallel
+ parallel(partitions, 'cleanup_after_test')
+
+
+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. If you have job config
+ 'partition.partitions', e.g.,
+ job.config_set('partition.partitions', ["/dev/sda2", "/dev/sda3"])
+ you may specify a partition in the form of "partN" e.g. "part0",
+ "part1" to refer to elements of the partition list. This is
+ specially useful if you run a test in various machines and you
+ don't want to hardcode device names as those may vary.
+ @param loop_size: Size of loopback device (in MB). Defaults to 0.
+ """
+ # NOTE: This code is used by IBM / ABAT. Do not remove.
+ part = re.compile(r'^part(\d+)$')
+ m = part.match(device)
+ if m:
+ number = int(m.groups()[0])
+ partitions = job.config_get('partition.partitions')
+ try:
+ device = partitions[number]
+ except:
+ raise NameError("Partition '" + device + "' not available")
+
+ 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 cleanup_after_test(self):
+ """
+ Cleans up a partition after running a filesystem test. The
+ filesystem is unmounted, and then checked for errors.
+ """
+ self.unmount()
+ self.fsck()
+
+
+ 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.cleanup_after_test()
+
+ 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:
+ 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, 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, 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:
+
+ * sfdisk
+ * losetup
+ * kpartx
+ """
+ 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('Sanity check before attempting to create virtual '
+ 'partition')
+ try:
+ os_dep.commands('sfdisk', 'losetup', 'kpartx')
+ except ValueError, e:
+ e_msg = 'Unable to create virtual partition: %s' % e
+ raise error.AutotestError(e_msg)
+
+ logging.debug('Creating virtual partition')
+ self.img = self._create_disk_img(file_img, file_size)
+ self.loop = self._attach_img_loop(self.img)
+ self._create_single_partition(self.loop)
+ self.device = self._create_entries_partition(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._remove_entries_partition()
+ self._detach_img_loop()
+ self._remove_disk_img()
+
+
+ def _create_disk_img(self, img_path, size):
+ """
+ Creates a disk image using dd.
+
+ @param img_path: Path to the desired image file.
+ @param size: Size of the desired image in Bytes.
+ @returns: Path of the image created.
+ """
+ logging.debug('Creating disk image %s, size = %d Bytes', img_path, size)
+ try:
+ cmd = 'dd if=/dev/zero of=%s bs=1024 count=%d' % (img_path, size)
+ utils.system(cmd)
+ except error.CmdError, 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, img_path):
+ """
+ Attaches a file image to a loopback device using losetup.
+
+ @param img_path: Path of the image file that will be attached to a
+ loopback device
+ @returns: Path of the loopback device associated.
+ """
+ logging.debug('Attaching image %s to a loop device', img_path)
+ try:
+ cmd = 'losetup -f'
+ loop_path = utils.system_output(cmd)
+ cmd = 'losetup -f %s' % img_path
+ utils.system(cmd)
+ except error.CmdError, e:
+ e_msg = 'Error attaching image %s to a loop device: %s' % \
+ (img_path, e)
+ raise error.AutotestError(e_msg)
+ return loop_path
+
+
+ def _create_single_partition(self, loop_path):
+ """
+ Creates a single partition encompassing the whole 'disk' using cfdisk.
+
+ @param loop_path: Path to the loopback device.
+ """
+ logging.debug('Creating single partition on %s', loop_path)
+ try:
+ single_part_cmd = '0,,c\n'
+ sfdisk_file_path = '/tmp/create_partition.sfdisk'
+ sfdisk_cmd_file = open(sfdisk_file_path, 'w')
+ sfdisk_cmd_file.write(single_part_cmd)
+ sfdisk_cmd_file.close()
+ utils.system('sfdisk %s < %s' % (loop_path, sfdisk_file_path))
+ except error.CmdError, e:
+ e_msg = 'Error partitioning device %s: %s' % (loop_path, e)
+ raise error.AutotestError(e_msg)
+
+
+ def _create_entries_partition(self, loop_path):
+ """
+ Takes the newly created partition table on the loopback device and
+ makes all its devices available under /dev/mapper. As we previously
+ have partitioned it using a single partition, only one partition
+ will be returned.
+
+ @param loop_path: Path to the loopback device.
+ """
+ logging.debug('Creating entries under /dev/mapper for %s loop dev',
+ loop_path)
+ try:
+ cmd = 'kpartx -a %s' % loop_path
+ utils.system(cmd)
+ l_cmd = 'kpartx -l %s | cut -f1 -d " "' % loop_path
+ device = utils.system_output(l_cmd)
+ except error.CmdError, e:
+ e_msg = 'Error creating entries for %s: %s' % (loop_path, e)
+ raise error.AutotestError(e_msg)
+ return os.path.join('/dev/mapper', device)
+
+
+ def _remove_entries_partition(self):
+ """
+ Removes the entries under /dev/mapper for the partition associated
+ to the loopback device.
+ """
+ logging.debug('Removing the entry on /dev/mapper for %s loop dev',
+ self.loop)
+ try:
+ cmd = 'kpartx -d %s' % self.loop
+ utils.system(cmd)
+ except error.CmdError, e:
+ e_msg = 'Error removing entries for loop %s: %s' % (self.loop, e)
+ raise error.AutotestError(e_msg)
+
+
+ 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.system(cmd)
+ except error.CmdError, 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: