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/base_partition.py b/client/bin/base_partition.py
deleted file mode 100644
index 82d18ee..0000000
--- a/client/bin/base_partition.py
+++ /dev/null
@@ -1,977 +0,0 @@
-"""
-APIs to write tests and control files that handle partition creation, deletion
-and formatting.
-
-@copyright: Google 2006-2008
-@author: Martin Bligh (mbligh@google.com)
-"""
-
-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)
-
-    # run the test against all the partitions
-    job.run_test(test, tag=tag, partitions=partitions, **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()
-        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='-n', 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("yes | " + fsck_cmd)
-        except:
-            if record:
-                self.job.record('FAIL', None, fsck_cmd, error.format_error())
-            raise
-        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)
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:
diff --git a/client/bin/partition_unittest.py b/client/bin/partition_unittest.py
index e843630..6fb01d7 100755
--- a/client/bin/partition_unittest.py
+++ b/client/bin/partition_unittest.py
@@ -4,10 +4,11 @@
 
 __author__ = 'gps@google.com (Gregory P. Smith)'
 
-import os, unittest
+import os, sys, unittest
 from cStringIO import StringIO
 import common
 from autotest_lib.client.common_lib.test_utils import mock
+from autotest_lib.client.bin import partition
 
 
 class FsOptions_common(object):
@@ -132,31 +133,29 @@
 # non site specific code
 class FSOptions_base_test(FsOptions_common, unittest.TestCase):
     def setUp(self):
-        global partition
-        from autotest_lib.client.bin import base_partition
-        partition = base_partition
+        sys.modules['autotest_lib.client.bin.site_partition'] = None
+        reload(partition)
 
 
 class get_partition_list_base_test(get_partition_list_common, unittest.TestCase):
     def setUp(self):
-        global partition
-        from autotest_lib.client.bin import base_partition
-        partition = base_partition
-
+        sys.modules['autotest_lib.client.bin.site_partition'] = None
+        reload(partition)
         get_partition_list_common.setUp(self)
 
 
 class FSOptions_test(FsOptions_common, unittest.TestCase):
     def setUp(self):
-        global partition
-        from autotest_lib.client.bin import partition
+        if 'autotest_lib.client.bin.site_partition' in sys.modules:
+            del sys.modules['autotest_lib.client.bin.site_partition']
+        reload(partition)
 
 
 class get_partition_list_test(get_partition_list_common, unittest.TestCase):
     def setUp(self):
-        global partition
-        from autotest_lib.client.bin import partition
-
+        if 'autotest_lib.client.bin.site_partition' in sys.modules:
+            del sys.modules['autotest_lib.client.bin.site_partition']
+        reload(partition)
         get_partition_list_common.setUp(self)