Home | History | Annotate | Download | only in bin
      1 """
      2 APIs to write tests and control files that handle partition creation, deletion
      3 and formatting.
      4 
      5 @copyright: Google 2006-2008
      6 @author: Martin Bligh (mbligh (at] google.com)
      7 """
      8 
      9 # pylint: disable=missing-docstring
     10 
     11 import os, re, string, sys, fcntl, logging
     12 from autotest_lib.client.bin import os_dep, utils
     13 from autotest_lib.client.common_lib import error
     14 
     15 
     16 class FsOptions(object):
     17     """
     18     A class encapsulating a filesystem test's parameters.
     19     """
     20     # NOTE(gps): This class could grow or be merged with something else in the
     21     # future that actually uses the encapsulated data (say to run mkfs) rather
     22     # than just being a container.
     23 
     24     __slots__ = ('fstype', 'mkfs_flags', 'mount_options', 'fs_tag')
     25 
     26     def __init__(self, fstype, fs_tag, mkfs_flags=None, mount_options=None):
     27         """
     28         Fill in our properties.
     29 
     30         @param fstype: The filesystem type ('ext2', 'ext4', 'xfs', etc.)
     31         @param fs_tag: A short name for this filesystem test to use
     32                 in the results.
     33         @param mkfs_flags: Optional. Additional command line options to mkfs.
     34         @param mount_options: Optional. The options to pass to mount -o.
     35         """
     36 
     37         if not fstype or not fs_tag:
     38             raise ValueError('A filesystem and fs_tag are required.')
     39         self.fstype = fstype
     40         self.fs_tag = fs_tag
     41         self.mkfs_flags = mkfs_flags or ""
     42         self.mount_options = mount_options or ""
     43 
     44 
     45     def __str__(self):
     46         val = ('FsOptions(fstype=%r, mkfs_flags=%r, '
     47                'mount_options=%r, fs_tag=%r)' %
     48                (self.fstype, self.mkfs_flags,
     49                 self.mount_options, self.fs_tag))
     50         return val
     51 
     52 
     53 def partname_to_device(part):
     54     """ Converts a partition name to its associated device """
     55     return os.path.join(os.sep, 'dev', part)
     56 
     57 
     58 def list_mount_devices():
     59     devices = []
     60     # list mounted filesystems
     61     for line in utils.system_output('mount').splitlines():
     62         devices.append(line.split()[0])
     63     # list mounted swap devices
     64     for line in utils.system_output('swapon -s').splitlines():
     65         if line.startswith('/'):        # skip header line
     66             devices.append(line.split()[0])
     67     return devices
     68 
     69 
     70 def list_mount_points():
     71     mountpoints = []
     72     for line in utils.system_output('mount').splitlines():
     73         mountpoints.append(line.split()[2])
     74     return mountpoints
     75 
     76 
     77 def get_iosched_path(device_name, component):
     78     return '/sys/block/%s/queue/%s' % (device_name, component)
     79 
     80 
     81 def wipe_filesystem(job, mountpoint):
     82     wipe_cmd = 'rm -rf %s/*' % mountpoint
     83     try:
     84         utils.system(wipe_cmd)
     85     except:
     86         job.record('FAIL', None, wipe_cmd, error.format_error())
     87         raise
     88     else:
     89         job.record('GOOD', None, wipe_cmd)
     90 
     91 
     92 def is_linux_fs_type(device):
     93     """
     94     Checks if specified partition is type 83
     95 
     96     @param device: the device, e.g. /dev/sda3
     97 
     98     @return: False if the supplied partition name is not type 83 linux, True
     99             otherwise
    100     """
    101     disk_device = device.rstrip('0123456789')
    102 
    103     # Parse fdisk output to get partition info.  Ugly but it works.
    104     fdisk_fd = os.popen("/sbin/fdisk -l -u '%s'" % disk_device)
    105     fdisk_lines = fdisk_fd.readlines()
    106     fdisk_fd.close()
    107     for line in fdisk_lines:
    108         if not line.startswith(device):
    109             continue
    110         info_tuple = line.split()
    111         # The Id will be in one of two fields depending on if the boot flag
    112         # was set.  Caveat: this assumes no boot partition will be 83 blocks.
    113         for fsinfo in info_tuple[4:6]:
    114             if fsinfo == '83':  # hex 83 is the linux fs partition type
    115                 return True
    116     return False
    117 
    118 
    119 def get_partition_list(job, min_blocks=0, filter_func=None, exclude_swap=True,
    120                        open_func=open):
    121     """
    122     Get a list of partition objects for all disk partitions on the system.
    123 
    124     Loopback devices and unnumbered (whole disk) devices are always excluded.
    125 
    126     @param job: The job instance to pass to the partition object
    127             constructor.
    128     @param min_blocks: The minimum number of blocks for a partition to
    129             be considered.
    130     @param filter_func: A callable that returns True if a partition is
    131             desired. It will be passed one parameter:
    132             The partition name (hdc3, etc.).
    133             Some useful filter functions are already defined in this module.
    134     @param exclude_swap: If True any partition actively in use as a swap
    135             device will be excluded.
    136     @param __open: Reserved for unit testing.
    137 
    138     @return: A list of L{partition} objects.
    139     """
    140     active_swap_devices = set()
    141     if exclude_swap:
    142         for swapline in open_func('/proc/swaps'):
    143             if swapline.startswith('/'):
    144                 active_swap_devices.add(swapline.split()[0])
    145 
    146     partitions = []
    147     for partline in open_func('/proc/partitions').readlines():
    148         fields = partline.strip().split()
    149         if len(fields) != 4 or partline.startswith('major'):
    150             continue
    151         (major, minor, blocks, partname) = fields
    152         blocks = int(blocks)
    153 
    154         # The partition name better end with a digit, else it's not a partition
    155         if not partname[-1].isdigit():
    156             continue
    157 
    158         # We don't want the loopback device in the partition list
    159         if 'loop' in partname:
    160             continue
    161 
    162         device = partname_to_device(partname)
    163         if exclude_swap and device in active_swap_devices:
    164             logging.debug('Skipping %s - Active swap.', partname)
    165             continue
    166 
    167         if min_blocks and blocks < min_blocks:
    168             logging.debug('Skipping %s - Too small.', partname)
    169             continue
    170 
    171         if filter_func and not filter_func(partname):
    172             logging.debug('Skipping %s - Filter func.', partname)
    173             continue
    174 
    175         partitions.append(partition(job, device))
    176 
    177     return partitions
    178 
    179 
    180 def get_mount_info(partition_list):
    181     """
    182     Picks up mount point information about the machine mounts. By default, we
    183     try to associate mount points with UUIDs, because in newer distros the
    184     partitions are uniquely identified using them.
    185     """
    186     mount_info = set()
    187     for p in partition_list:
    188         try:
    189             uuid = utils.system_output('blkid -p -s UUID -o value %s' % p.device)
    190         except error.CmdError:
    191             # fall back to using the partition
    192             uuid = p.device
    193         mount_info.add((uuid, p.get_mountpoint()))
    194 
    195     return mount_info
    196 
    197 
    198 def filter_partition_list(partitions, devnames):
    199     """
    200     Pick and choose which partition to keep.
    201 
    202     filter_partition_list accepts a list of partition objects and a list
    203     of strings.  If a partition has the device name of the strings it
    204     is returned in a list.
    205 
    206     @param partitions: A list of L{partition} objects
    207     @param devnames: A list of devnames of the form '/dev/hdc3' that
    208                     specifies which partitions to include in the returned list.
    209 
    210     @return: A list of L{partition} objects specified by devnames, in the
    211              order devnames specified
    212     """
    213 
    214     filtered_list = []
    215     for p in partitions:
    216         for d in devnames:
    217             if p.device == d and p not in filtered_list:
    218                 filtered_list.append(p)
    219 
    220     return filtered_list
    221 
    222 
    223 def get_unmounted_partition_list(root_part, job=None, min_blocks=0,
    224                                  filter_func=None, exclude_swap=True,
    225                                  open_func=open):
    226     """
    227     Return a list of partition objects that are not mounted.
    228 
    229     @param root_part: The root device name (without the '/dev/' prefix, example
    230             'hda2') that will be filtered from the partition list.
    231 
    232             Reasoning: in Linux /proc/mounts will never directly mention the
    233             root partition as being mounted on / instead it will say that
    234             /dev/root is mounted on /. Thus require this argument to filter out
    235             the root_part from the ones checked to be mounted.
    236     @param job, min_blocks, filter_func, exclude_swap, open_func: Forwarded
    237             to get_partition_list().
    238     @return List of L{partition} objects that are not mounted.
    239     """
    240     partitions = get_partition_list(job=job, min_blocks=min_blocks,
    241         filter_func=filter_func, exclude_swap=exclude_swap, open_func=open_func)
    242 
    243     unmounted = []
    244     for part in partitions:
    245         if (part.device != partname_to_device(root_part) and
    246             not part.get_mountpoint(open_func=open_func)):
    247             unmounted.append(part)
    248 
    249     return unmounted
    250 
    251 
    252 def parallel(partitions, method_name, *args, **dargs):
    253     """
    254     Run a partition method (with appropriate arguments) in parallel,
    255     across a list of partition objects
    256     """
    257     if not partitions:
    258         return
    259     job = partitions[0].job
    260     flist = []
    261     if (not hasattr(partition, method_name) or
    262                                not callable(getattr(partition, method_name))):
    263         err = "partition.parallel got invalid method %s" % method_name
    264         raise RuntimeError(err)
    265 
    266     for p in partitions:
    267         print_args = list(args)
    268         print_args += ['%s=%s' % (key, dargs[key]) for key in dargs.keys()]
    269         logging.debug('%s.%s(%s)', str(p), method_name,
    270                                      ', '.join(print_args))
    271         sys.stdout.flush()
    272         def _run_named_method(function, part=p):
    273             getattr(part, method_name)(*args, **dargs)
    274         flist.append((_run_named_method, ()))
    275     job.parallel(*flist)
    276 
    277 
    278 def filesystems():
    279     """
    280     Return a list of all available filesystems
    281     """
    282     return [re.sub('(nodev)?\s*', '', fs) for fs in open('/proc/filesystems')]
    283 
    284 
    285 def unmount_partition(device):
    286     """
    287     Unmount a mounted partition
    288 
    289     @param device: e.g. /dev/sda1, /dev/hda1
    290     """
    291     p = partition(job=None, device=device)
    292     p.unmount(record=False)
    293 
    294 
    295 def is_valid_partition(device):
    296     """
    297     Checks if a partition is valid
    298 
    299     @param device: e.g. /dev/sda1, /dev/hda1
    300     """
    301     parts = get_partition_list(job=None)
    302     p_list = [ p.device for p in parts ]
    303     if device in p_list:
    304         return True
    305 
    306     return False
    307 
    308 
    309 def is_valid_disk(device):
    310     """
    311     Checks if a disk is valid
    312 
    313     @param device: e.g. /dev/sda, /dev/hda
    314     """
    315     partitions = []
    316     for partline in open('/proc/partitions').readlines():
    317         fields = partline.strip().split()
    318         if len(fields) != 4 or partline.startswith('major'):
    319             continue
    320         (major, minor, blocks, partname) = fields
    321         blocks = int(blocks)
    322 
    323         if not partname[-1].isdigit():
    324             # Disk name does not end in number, AFAIK
    325             # so use it as a reference to a disk
    326             if device.strip("/dev/") == partname:
    327                 return True
    328 
    329     return False
    330 
    331 
    332 def run_test_on_partitions(job, test, partitions, mountpoint_func,
    333                            tag, fs_opt, do_fsck=True, **dargs):
    334     """
    335     Run a test that requires multiple partitions.  Filesystems will be
    336     made on the partitions and mounted, then the test will run, then the
    337     filesystems will be unmounted and optionally fsck'd.
    338 
    339     @param job: A job instance to run the test
    340     @param test: A string containing the name of the test
    341     @param partitions: A list of partition objects, these are passed to the
    342             test as partitions=
    343     @param mountpoint_func: A callable that returns a mountpoint given a
    344             partition instance
    345     @param tag: A string tag to make this test unique (Required for control
    346             files that make multiple calls to this routine with the same value
    347             of 'test'.)
    348     @param fs_opt: An FsOptions instance that describes what filesystem to make
    349     @param do_fsck: include fsck in post-test partition cleanup.
    350     @param dargs: Dictionary of arguments to be passed to job.run_test() and
    351             eventually the test
    352     """
    353     # setup the filesystem parameters for all the partitions
    354     for p in partitions:
    355         p.set_fs_options(fs_opt)
    356 
    357     # make and mount all the partitions in parallel
    358     parallel(partitions, 'setup_before_test', mountpoint_func=mountpoint_func)
    359 
    360     mountpoint = mountpoint_func(partitions[0])
    361 
    362     # run the test against all the partitions
    363     job.run_test(test, tag=tag, partitions=partitions, dir=mountpoint, **dargs)
    364 
    365     parallel(partitions, 'unmount')  # unmount all partitions in parallel
    366     if do_fsck:
    367         parallel(partitions, 'fsck')  # fsck all partitions in parallel
    368     # else fsck is done by caller
    369 
    370 
    371 class partition(object):
    372     """
    373     Class for handling partitions and filesystems
    374     """
    375 
    376     def __init__(self, job, device, loop_size=0, mountpoint=None):
    377         """
    378         @param job: A L{client.bin.job} instance.
    379         @param device: The device in question (e.g."/dev/hda2"). If device is a
    380                 file it will be mounted as loopback.
    381         @param loop_size: Size of loopback device (in MB). Defaults to 0.
    382         """
    383         self.device = device
    384         self.name = os.path.basename(device)
    385         self.job = job
    386         self.loop = loop_size
    387         self.fstype = None
    388         self.mountpoint = mountpoint
    389         self.mkfs_flags = None
    390         self.mount_options = None
    391         self.fs_tag = None
    392         if self.loop:
    393             cmd = 'dd if=/dev/zero of=%s bs=1M count=%d' % (device, loop_size)
    394             utils.system(cmd)
    395 
    396 
    397     def __repr__(self):
    398         return '<Partition: %s>' % self.device
    399 
    400 
    401     def set_fs_options(self, fs_options):
    402         """
    403         Set filesystem options
    404 
    405             @param fs_options: A L{FsOptions} object
    406         """
    407 
    408         self.fstype = fs_options.fstype
    409         self.mkfs_flags = fs_options.mkfs_flags
    410         self.mount_options = fs_options.mount_options
    411         self.fs_tag = fs_options.fs_tag
    412 
    413 
    414     def run_test(self, test, **dargs):
    415         self.job.run_test(test, dir=self.get_mountpoint(), **dargs)
    416 
    417 
    418     def setup_before_test(self, mountpoint_func):
    419         """
    420         Prepare a partition for running a test.  Unmounts any
    421         filesystem that's currently mounted on the partition, makes a
    422         new filesystem (according to this partition's filesystem
    423         options) and mounts it where directed by mountpoint_func.
    424 
    425         @param mountpoint_func: A callable that returns a path as a string,
    426                 given a partition instance.
    427         """
    428         mountpoint = mountpoint_func(self)
    429         if not mountpoint:
    430             raise ValueError('Don\'t know where to put this partition')
    431         self.unmount(ignore_status=True, record=False)
    432         self.mkfs()
    433         if not os.path.isdir(mountpoint):
    434             os.makedirs(mountpoint)
    435         self.mount(mountpoint)
    436 
    437 
    438     def run_test_on_partition(self, test, mountpoint_func, **dargs):
    439         """
    440         Executes a test fs-style (umount,mkfs,mount,test)
    441 
    442         Here we unmarshal the args to set up tags before running the test.
    443         Tests are also run by first umounting, mkfsing and then mounting
    444         before executing the test.
    445 
    446         @param test: name of test to run
    447         @param mountpoint_func: function to return mount point string
    448         """
    449         tag = dargs.get('tag')
    450         if tag:
    451             tag = '%s.%s' % (self.name, tag)
    452         elif self.fs_tag:
    453             tag = '%s.%s' % (self.name, self.fs_tag)
    454         else:
    455             tag = self.name
    456 
    457         # If there's a 'suffix' argument, append it to the tag and remove it
    458         suffix = dargs.pop('suffix', None)
    459         if suffix:
    460             tag = '%s.%s' % (tag, suffix)
    461 
    462         dargs['tag'] = test + '.' + tag
    463 
    464         def _make_partition_and_run_test(test_tag, dir=None, **dargs):
    465             self.setup_before_test(mountpoint_func)
    466             try:
    467                 self.job.run_test(test, tag=test_tag, dir=mountpoint, **dargs)
    468             finally:
    469                 self.unmount()
    470                 self.fsck()
    471 
    472 
    473         mountpoint = mountpoint_func(self)
    474 
    475         # The tag is the tag for the group (get stripped off by run_group)
    476         # The test_tag is the tag for the test itself
    477         self.job.run_group(_make_partition_and_run_test,
    478                            test_tag=tag, dir=mountpoint, **dargs)
    479 
    480 
    481     def get_mountpoint(self, open_func=open, filename=None):
    482         """
    483         Find the mount point of this partition object.
    484 
    485         @param open_func: the function to use for opening the file containing
    486                 the mounted partitions information
    487         @param filename: where to look for the mounted partitions information
    488                 (default None which means it will search /proc/mounts and/or
    489                 /etc/mtab)
    490 
    491         @returns a string with the mount point of the partition or None if not
    492                 mounted
    493         """
    494         if filename:
    495             for line in open_func(filename).readlines():
    496                 parts = line.split()
    497                 if parts[0] == self.device or parts[1] == self.mountpoint:
    498                     return parts[1] # The mountpoint where it's mounted
    499             return None
    500 
    501         # no specific file given, look in /proc/mounts
    502         res = self.get_mountpoint(open_func=open_func, filename='/proc/mounts')
    503         if not res:
    504             # sometimes the root partition is reported as /dev/root in
    505             # /proc/mounts in this case, try /etc/mtab
    506             res = self.get_mountpoint(open_func=open_func, filename='/etc/mtab')
    507 
    508             # trust /etc/mtab only about /
    509             if res != '/':
    510                 res = None
    511 
    512         return res
    513 
    514 
    515     def mkfs_exec(self, fstype):
    516         """
    517         Return the proper mkfs executable based on fs
    518         """
    519         if fstype == 'ext4':
    520             if os.path.exists('/sbin/mkfs.ext4'):
    521                 return 'mkfs'
    522             # If ext4 supported e2fsprogs is not installed we use the
    523             # autotest supplied one in tools dir which is statically linked"""
    524             auto_mkfs = os.path.join(self.job.toolsdir, 'mkfs.ext4dev')
    525             if os.path.exists(auto_mkfs):
    526                 return auto_mkfs
    527         else:
    528             return 'mkfs'
    529 
    530         raise NameError('Error creating partition for filesystem type %s' %
    531                         fstype)
    532 
    533 
    534     def mkfs(self, fstype=None, args='', record=True):
    535         """
    536         Format a partition to filesystem type
    537 
    538         @param fstype: the filesystem type, e.g.. "ext3", "ext2"
    539         @param args: arguments to be passed to mkfs command.
    540         @param record: if set, output result of mkfs operation to autotest
    541                 output
    542         """
    543 
    544         if list_mount_devices().count(self.device):
    545             raise NameError('Attempted to format mounted device %s' %
    546                              self.device)
    547 
    548         if not fstype:
    549             if self.fstype:
    550                 fstype = self.fstype
    551             else:
    552                 fstype = 'ext2'
    553 
    554         if self.mkfs_flags:
    555             args += ' ' + self.mkfs_flags
    556         if fstype == 'xfs':
    557             args += ' -f'
    558 
    559         if self.loop:
    560             # BAH. Inconsistent mkfs syntax SUCKS.
    561             if fstype.startswith('ext'):
    562                 args += ' -F'
    563             elif fstype == 'reiserfs':
    564                 args += ' -f'
    565 
    566         # If there isn't already a '-t <type>' argument, add one.
    567         if not "-t" in args:
    568             args = "-t %s %s" % (fstype, args)
    569 
    570         args = args.strip()
    571 
    572         mkfs_cmd = "%s %s %s" % (self.mkfs_exec(fstype), args, self.device)
    573 
    574         sys.stdout.flush()
    575         try:
    576             # We throw away the output here - we only need it on error, in
    577             # which case it's in the exception
    578             utils.system_output("yes | %s" % mkfs_cmd)
    579         except error.CmdError, e:
    580             logging.error(e.result_obj)
    581             if record:
    582                 self.job.record('FAIL', None, mkfs_cmd, error.format_error())
    583             raise
    584         except:
    585             if record:
    586                 self.job.record('FAIL', None, mkfs_cmd, error.format_error())
    587             raise
    588         else:
    589             if record:
    590                 self.job.record('GOOD', None, mkfs_cmd)
    591             self.fstype = fstype
    592 
    593 
    594     def get_fsck_exec(self):
    595         """
    596         Return the proper mkfs executable based on self.fstype
    597         """
    598         if self.fstype == 'ext4':
    599             if os.path.exists('/sbin/fsck.ext4'):
    600                 return 'fsck'
    601             # If ext4 supported e2fsprogs is not installed we use the
    602             # autotest supplied one in tools dir which is statically linked"""
    603             auto_fsck = os.path.join(self.job.toolsdir, 'fsck.ext4dev')
    604             if os.path.exists(auto_fsck):
    605                 return auto_fsck
    606         else:
    607             return 'fsck'
    608 
    609         raise NameError('Error creating partition for filesystem type %s' %
    610                         self.fstype)
    611 
    612 
    613     def fsck(self, args='-fy', record=True):
    614         """
    615         Run filesystem check
    616 
    617         @param args: arguments to filesystem check tool. Default is "-n"
    618                 which works on most tools.
    619         """
    620 
    621         # I hate reiserfstools.
    622         # Requires an explit Yes for some inane reason
    623         fsck_cmd = '%s %s %s' % (self.get_fsck_exec(), self.device, args)
    624         if self.fstype == 'reiserfs':
    625             fsck_cmd = 'yes "Yes" | ' + fsck_cmd
    626         sys.stdout.flush()
    627         try:
    628             utils.system_output(fsck_cmd)
    629         except:
    630             if record:
    631                 self.job.record('FAIL', None, fsck_cmd, error.format_error())
    632             raise error.TestError('Fsck found errors with the underlying '
    633                                   'file system')
    634         else:
    635             if record:
    636                 self.job.record('GOOD', None, fsck_cmd)
    637 
    638 
    639     def mount(self, mountpoint=None, fstype=None, args='', record=True):
    640         """
    641         Mount this partition to a mount point
    642 
    643         @param mountpoint: If you have not provided a mountpoint to partition
    644                 object or want to use a different one, you may specify it here.
    645         @param fstype: Filesystem type. If not provided partition object value
    646                 will be used.
    647         @param args: Arguments to be passed to "mount" command.
    648         @param record: If True, output result of mount operation to autotest
    649                 output.
    650         """
    651 
    652         if fstype is None:
    653             fstype = self.fstype
    654         else:
    655             assert(self.fstype is None or self.fstype == fstype);
    656 
    657         if self.mount_options:
    658             args += ' -o  ' + self.mount_options
    659         if fstype:
    660             args += ' -t ' + fstype
    661         if self.loop:
    662             args += ' -o loop'
    663         args = args.lstrip()
    664 
    665         if not mountpoint and not self.mountpoint:
    666             raise ValueError("No mountpoint specified and no default "
    667                              "provided to this partition object")
    668         if not mountpoint:
    669             mountpoint = self.mountpoint
    670 
    671         mount_cmd = "mount %s %s %s" % (args, self.device, mountpoint)
    672 
    673         if list_mount_devices().count(self.device):
    674             err = 'Attempted to mount mounted device'
    675             self.job.record('FAIL', None, mount_cmd, err)
    676             raise NameError(err)
    677         if list_mount_points().count(mountpoint):
    678             err = 'Attempted to mount busy mountpoint'
    679             self.job.record('FAIL', None, mount_cmd, err)
    680             raise NameError(err)
    681 
    682         mtab = open('/etc/mtab')
    683         # We have to get an exclusive lock here - mount/umount are racy
    684         fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
    685         sys.stdout.flush()
    686         try:
    687             utils.system(mount_cmd)
    688             mtab.close()
    689         except:
    690             mtab.close()
    691             if record:
    692                 self.job.record('FAIL', None, mount_cmd, error.format_error())
    693             raise
    694         else:
    695             if record:
    696                 self.job.record('GOOD', None, mount_cmd)
    697             self.fstype = fstype
    698 
    699 
    700     def unmount_force(self):
    701         """
    702         Kill all other jobs accessing this partition. Use fuser and ps to find
    703         all mounts on this mountpoint and unmount them.
    704 
    705         @return: true for success or false for any errors
    706         """
    707 
    708         logging.debug("Standard umount failed, will try forcing. Users:")
    709         try:
    710             cmd = 'fuser ' + self.get_mountpoint()
    711             logging.debug(cmd)
    712             fuser = utils.system_output(cmd)
    713             logging.debug(fuser)
    714             users = re.sub('.*:', '', fuser).split()
    715             for user in users:
    716                 m = re.match('(\d+)(.*)', user)
    717                 (pid, usage) = (m.group(1), m.group(2))
    718                 try:
    719                     ps = utils.system_output('ps -p %s | sed 1d' % pid)
    720                     logging.debug('%s %s %s', usage, pid, ps)
    721                 except Exception:
    722                     pass
    723                 utils.system('ls -l ' + self.device)
    724                 umount_cmd = "umount -f " + self.device
    725                 utils.system(umount_cmd)
    726                 return True
    727         except error.CmdError:
    728             logging.debug('Umount_force failed for %s', self.device)
    729             return False
    730 
    731 
    732 
    733     def unmount(self, ignore_status=False, record=True):
    734         """
    735         Umount this partition.
    736 
    737         It's easier said than done to umount a partition.
    738         We need to lock the mtab file to make sure we don't have any
    739         locking problems if we are umounting in paralllel.
    740 
    741         If there turns out to be a problem with the simple umount we
    742         end up calling umount_force to get more  agressive.
    743 
    744         @param ignore_status: should we notice the umount status
    745         @param record: if True, output result of umount operation to
    746                 autotest output
    747         """
    748 
    749         mountpoint = self.get_mountpoint()
    750         if not mountpoint:
    751             # It's not even mounted to start with
    752             if record and not ignore_status:
    753                 msg = 'umount for dev %s has no mountpoint' % self.device
    754                 self.job.record('FAIL', None, msg, 'Not mounted')
    755             return
    756 
    757         umount_cmd = "umount " + mountpoint
    758         mtab = open('/etc/mtab')
    759 
    760         # We have to get an exclusive lock here - mount/umount are racy
    761         fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
    762         sys.stdout.flush()
    763         try:
    764             utils.system(umount_cmd)
    765             mtab.close()
    766             if record:
    767                 self.job.record('GOOD', None, umount_cmd)
    768         except (error.CmdError, IOError):
    769             mtab.close()
    770 
    771             # Try the forceful umount
    772             if self.unmount_force():
    773                 return
    774 
    775             # If we are here we cannot umount this partition
    776             if record and not ignore_status:
    777                 self.job.record('FAIL', None, umount_cmd, error.format_error())
    778             raise
    779 
    780 
    781     def wipe(self):
    782         """
    783         Delete all files of a given partition filesystem.
    784         """
    785         wipe_filesystem(self.job, self.get_mountpoint())
    786 
    787 
    788     def get_io_scheduler_list(self, device_name):
    789         names = open(self.__sched_path(device_name)).read()
    790         return names.translate(string.maketrans('[]', '  ')).split()
    791 
    792 
    793     def get_io_scheduler(self, device_name):
    794         return re.split('[\[\]]',
    795                         open(self.__sched_path(device_name)).read())[1]
    796 
    797 
    798     def set_io_scheduler(self, device_name, name):
    799         if name not in self.get_io_scheduler_list(device_name):
    800             raise NameError('No such IO scheduler: %s' % name)
    801         f = open(self.__sched_path(device_name), 'w')
    802         f.write(name)
    803         f.close()
    804 
    805 
    806     def __sched_path(self, device_name):
    807         return '/sys/block/%s/queue/scheduler' % device_name
    808 
    809 
    810 class virtual_partition:
    811     """
    812     Handles block device emulation using file images of disks.
    813     It's important to note that this API can be used only if
    814     we have the following programs present on the client machine:
    815 
    816      * sfdisk
    817      * losetup
    818      * kpartx
    819     """
    820     def __init__(self, file_img, file_size):
    821         """
    822         Creates a virtual partition, keeping record of the device created
    823         under /dev/mapper (device attribute) so test writers can use it
    824         on their filesystem tests.
    825 
    826         @param file_img: Path to the desired disk image file.
    827         @param file_size: Size of the desired image in Bytes.
    828         """
    829         logging.debug('Sanity check before attempting to create virtual '
    830                       'partition')
    831         try:
    832             os_dep.commands('sfdisk', 'losetup', 'kpartx')
    833         except ValueError, e:
    834             e_msg = 'Unable to create virtual partition: %s' % e
    835             raise error.AutotestError(e_msg)
    836 
    837         logging.debug('Creating virtual partition')
    838         self.img = self._create_disk_img(file_img, file_size)
    839         self.loop = self._attach_img_loop(self.img)
    840         self._create_single_partition(self.loop)
    841         self.device = self._create_entries_partition(self.loop)
    842         logging.debug('Virtual partition successfuly created')
    843         logging.debug('Image disk: %s', self.img)
    844         logging.debug('Loopback device: %s', self.loop)
    845         logging.debug('Device path: %s', self.device)
    846 
    847 
    848     def destroy(self):
    849         """
    850         Removes the virtual partition from /dev/mapper, detaches the image file
    851         from the loopback device and removes the image file.
    852         """
    853         logging.debug('Removing virtual partition - device %s', self.device)
    854         self._remove_entries_partition()
    855         self._detach_img_loop()
    856         self._remove_disk_img()
    857 
    858 
    859     def _create_disk_img(self, img_path, size):
    860         """
    861         Creates a disk image using dd.
    862 
    863         @param img_path: Path to the desired image file.
    864         @param size: Size of the desired image in Bytes.
    865         @returns: Path of the image created.
    866         """
    867         logging.debug('Creating disk image %s, size = %d Bytes', img_path, size)
    868         try:
    869             cmd = 'dd if=/dev/zero of=%s bs=1024 count=%d' % (img_path, size)
    870             utils.run(cmd)
    871         except error.CmdError, e:
    872             e_msg = 'Error creating disk image %s: %s' % (img_path, e)
    873             raise error.AutotestError(e_msg)
    874         return img_path
    875 
    876 
    877     def _attach_img_loop(self, img_path):
    878         """
    879         Attaches a file image to a loopback device using losetup.
    880 
    881         @param img_path: Path of the image file that will be attached to a
    882                 loopback device
    883         @returns: Path of the loopback device associated.
    884         """
    885         logging.debug('Attaching image %s to a loop device', img_path)
    886         try:
    887             cmd = 'losetup -f'
    888             loop_path = utils.system_output(cmd)
    889             cmd = 'losetup -f %s' % img_path
    890             utils.run(cmd)
    891         except error.CmdError, e:
    892             e_msg = ('Error attaching image %s to a loop device: %s' %
    893                      (img_path, e))
    894             raise error.AutotestError(e_msg)
    895         return loop_path
    896 
    897 
    898     def _create_single_partition(self, loop_path):
    899         """
    900         Creates a single partition encompassing the whole 'disk' using cfdisk.
    901 
    902         @param loop_path: Path to the loopback device.
    903         """
    904         logging.debug('Creating single partition on %s', loop_path)
    905         try:
    906             single_part_cmd = '0,,c\n'
    907             sfdisk_file_path = '/tmp/create_partition.sfdisk'
    908             sfdisk_cmd_file = open(sfdisk_file_path, 'w')
    909             sfdisk_cmd_file.write(single_part_cmd)
    910             sfdisk_cmd_file.close()
    911             utils.run('sfdisk %s < %s' % (loop_path, sfdisk_file_path))
    912         except error.CmdError, e:
    913             e_msg = 'Error partitioning device %s: %s' % (loop_path, e)
    914             raise error.AutotestError(e_msg)
    915 
    916 
    917     def _create_entries_partition(self, loop_path):
    918         """
    919         Takes the newly created partition table on the loopback device and
    920         makes all its devices available under /dev/mapper. As we previously
    921         have partitioned it using a single partition, only one partition
    922         will be returned.
    923 
    924         @param loop_path: Path to the loopback device.
    925         """
    926         logging.debug('Creating entries under /dev/mapper for %s loop dev',
    927                       loop_path)
    928         try:
    929             cmd = 'kpartx -a %s' % loop_path
    930             utils.run(cmd)
    931             l_cmd = 'kpartx -l %s | cut -f1 -d " "' % loop_path
    932             device = utils.system_output(l_cmd)
    933         except error.CmdError, e:
    934             e_msg = 'Error creating entries for %s: %s' % (loop_path, e)
    935             raise error.AutotestError(e_msg)
    936         return os.path.join('/dev/mapper', device)
    937 
    938 
    939     def _remove_entries_partition(self):
    940         """
    941         Removes the entries under /dev/mapper for the partition associated
    942         to the loopback device.
    943         """
    944         logging.debug('Removing the entry on /dev/mapper for %s loop dev',
    945                       self.loop)
    946         try:
    947             cmd = 'kpartx -d %s' % self.loop
    948             utils.run(cmd)
    949         except error.CmdError, e:
    950             e_msg = 'Error removing entries for loop %s: %s' % (self.loop, e)
    951             raise error.AutotestError(e_msg)
    952 
    953 
    954     def _detach_img_loop(self):
    955         """
    956         Detaches the image file from the loopback device.
    957         """
    958         logging.debug('Detaching image %s from loop device %s', self.img,
    959                       self.loop)
    960         try:
    961             cmd = 'losetup -d %s' % self.loop
    962             utils.run(cmd)
    963         except error.CmdError, e:
    964             e_msg = ('Error detaching image %s from loop device %s: %s' %
    965                     (self.img, self.loop, e))
    966             raise error.AutotestError(e_msg)
    967 
    968 
    969     def _remove_disk_img(self):
    970         """
    971         Removes the disk image.
    972         """
    973         logging.debug('Removing disk image %s', self.img)
    974         try:
    975             os.remove(self.img)
    976         except:
    977             e_msg = 'Error removing image file %s' % self.img
    978             raise error.AutotestError(e_msg)
    979 
    980 
    981 # import a site partition module to allow it to override functions
    982 try:
    983     from autotest_lib.client.bin.site_partition import *
    984 except ImportError:
    985     pass
    986