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