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 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 51def partname_to_device(part): 52 """ Converts a partition name to its associated device """ 53 return os.path.join(os.sep, 'dev', part) 54 55 56def 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 68def 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 75def get_iosched_path(device_name, component): 76 return '/sys/block/%s/queue/%s' % (device_name, component) 77 78 79def 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 90def 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 117def 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 178def 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 196def 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 221def 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 250def 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 276def 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 283def 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 293def 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 307def 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 330def 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 369class 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 825class 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 997try: 998 from autotest_lib.client.bin.site_partition import * 999except ImportError: 1000 pass 1001