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