1""" 2APIs to write tests and control files that handle partition creation, deletion 3and formatting. 4 5@copyright: Google 2006-2008 6@author: Martin Bligh (mbligh@google.com) 7""" 8 9# pylint: disable=missing-docstring 10 11import os, re, string, sys, fcntl, logging 12from autotest_lib.client.bin import os_dep, utils 13from autotest_lib.client.common_lib import error 14 15 16class 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 53def partname_to_device(part): 54 """ Converts a partition name to its associated device """ 55 return os.path.join(os.sep, 'dev', part) 56 57 58def 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 70def 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 77def get_iosched_path(device_name, component): 78 return '/sys/block/%s/queue/%s' % (device_name, component) 79 80 81def 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 92def 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 119def 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 180def 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 198def 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 223def 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 252def 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 278def 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 285def 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 295def 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 309def 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 332def 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 371class 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 810class 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 982try: 983 from autotest_lib.client.bin.site_partition import * 984except ImportError: 985 pass 986