blob: 7381f755bc93e4a340631866915bbffae8f20e20 [file] [log] [blame]
mbligh9bfef382009-05-21 01:32:27 +00001"""
jadmanski543dceb2010-07-30 21:42:56 +00002APIs 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)
mbligh9bfef382009-05-21 01:32:27 +00007"""
mblighd7fb4a62006-10-01 00:57:53 +00008
jadmanski543dceb2010-07-30 21:42:56 +00009import 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
Eric Lie0493a42010-11-15 13:05:43 -0800179def 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:
Eric Li861b2d52011-02-04 14:50:35 -0800188 uuid = utils.system_output('blkid -p -s UUID -o value %s' % p.device)
Eric Lie0493a42010-11-15 13:05:43 -0800189 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
jadmanski543dceb2010-07-30 21:42:56 +0000197def 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,
Eric Lie0493a42010-11-15 13:05:43 -0800332 tag, fs_opt, do_fsck=True, **dargs):
jadmanski543dceb2010-07-30 21:42:56 +0000333 """
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
Eric Lie0493a42010-11-15 13:05:43 -0800336 filesystems will be unmounted and optionally fsck'd.
jadmanski543dceb2010-07-30 21:42:56 +0000337
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
Eric Lie0493a42010-11-15 13:05:43 -0800348 @param do_fsck: include fsck in post-test partition cleanup.
jadmanski543dceb2010-07-30 21:42:56 +0000349 @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
Eric Lie0493a42010-11-15 13:05:43 -0800364 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
jadmanski543dceb2010-07-30 21:42:56 +0000368
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
jadmanski543dceb2010-07-30 21:42:56 +0000454 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:
Eric Lie0493a42010-11-15 13:05:43 -0800485 self.unmount()
486 self.fsck()
487
jadmanski543dceb2010-07-30 21:42:56 +0000488
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()
Eric Lie0493a42010-11-15 13:05:43 -0800513 if parts[0] == self.device or parts[1] == self.mountpoint:
jadmanski543dceb2010-07-30 21:42:56 +0000514 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
Eric Li6f27d4f2010-09-29 10:55:17 -0700655 def mount(self, mountpoint=None, fstype=None, args='', record=True):
jadmanski543dceb2010-07-30 21:42:56 +0000656 """
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
jadmanski69bdaac2010-07-28 16:27:20 +0000997try:
998 from autotest_lib.client.bin.site_partition import *
999except ImportError:
1000 pass