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