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