blob: 67a83e6fc52858931ea48d072df5f61f3d55ba48 [file] [log] [blame]
mbligh548e6612009-07-10 22:48:23 +00001"""
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"""
mbligh9bfef382009-05-21 01:32:27 +00008
mbligh79ceb5b2009-07-10 22:57:18 +00009import os, re, string, sys, fcntl, logging
10from autotest_lib.client.bin import os_dep, utils
mbligh9bfef382009-05-21 01:32:27 +000011from 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
mbligh548e6612009-07-10 22:48:23 +000029 @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.
mbligh9bfef382009-05-21 01:32:27 +000034 """
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
mbligh0308cc82009-07-28 23:28:20 +000052def partname_to_device(part):
53 """ Converts a partition name to its associated device """
54 return os.path.join(os.sep, 'dev', part)
55
56
mbligh9bfef382009-05-21 01:32:27 +000057def 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
mbligh0308cc82009-07-28 23:28:20 +000091
mbligh9bfef382009-05-21 01:32:27 +000092def is_linux_fs_type(device):
93 """
94 Checks if specified partition is type 83
95
mbligh548e6612009-07-10 22:48:23 +000096 @param device: the device, e.g. /dev/sda3
mbligh9bfef382009-05-21 01:32:27 +000097
mbligh548e6612009-07-10 22:48:23 +000098 @return: False if the supplied partition name is not type 83 linux, True
99 otherwise
mbligh9bfef382009-05-21 01:32:27 +0000100 """
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
mbligh9bfef382009-05-21 01:32:27 +0000119def 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
mbligh548e6612009-07-10 22:48:23 +0000126 @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.
mbligh9bfef382009-05-21 01:32:27 +0000137
mbligh548e6612009-07-10 22:48:23 +0000138 @return: A list of L{partition} objects.
mbligh9bfef382009-05-21 01:32:27 +0000139 """
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
mbligh0308cc82009-07-28 23:28:20 +0000162 device = partname_to_device(partname)
mbligh9bfef382009-05-21 01:32:27 +0000163 if exclude_swap and device in active_swap_devices:
mbligh79ceb5b2009-07-10 22:57:18 +0000164 logging.debug('Skipping %s - Active swap.' % partname)
mbligh9bfef382009-05-21 01:32:27 +0000165 continue
166
167 if min_blocks and blocks < min_blocks:
mbligh79ceb5b2009-07-10 22:57:18 +0000168 logging.debug('Skipping %s - Too small.' % partname)
mbligh9bfef382009-05-21 01:32:27 +0000169 continue
170
171 if filter_func and not filter_func(partname):
mbligh79ceb5b2009-07-10 22:57:18 +0000172 logging.debug('Skipping %s - Filter func.' % partname)
mbligh9bfef382009-05-21 01:32:27 +0000173 continue
174
175 partitions.append(partition(job, device))
176
177 return partitions
178
179
180def filter_partition_list(partitions, devnames):
181 """
182 Pick and choose which partition to keep.
183
184 filter_partition_list accepts a list of partition objects and a list
185 of strings. If a partition has the device name of the strings it
186 is returned in a list.
187
188 @param partitions: A list of L{partition} objects
189 @param devnames: A list of devnames of the form '/dev/hdc3' that
190 specifies which partitions to include in the returned list.
191
192 @return: A list of L{partition} objects specified by devnames, in the
193 order devnames specified
194 """
195
196 filtered_list = []
197 for p in partitions:
mbligh79ceb5b2009-07-10 22:57:18 +0000198 for d in devnames:
199 if p.device == d and p not in filtered_list:
200 filtered_list.append(p)
mbligh9bfef382009-05-21 01:32:27 +0000201
202 return filtered_list
203
204
205def get_unmounted_partition_list(root_part, job=None, min_blocks=0,
206 filter_func=None, exclude_swap=True,
207 open_func=open):
208 """
209 Return a list of partition objects that are not mounted.
210
mbligh548e6612009-07-10 22:48:23 +0000211 @param root_part: The root device name (without the '/dev/' prefix, example
212 'hda2') that will be filtered from the partition list.
mbligh9bfef382009-05-21 01:32:27 +0000213
mbligh548e6612009-07-10 22:48:23 +0000214 Reasoning: in Linux /proc/mounts will never directly mention the
215 root partition as being mounted on / instead it will say that
216 /dev/root is mounted on /. Thus require this argument to filter out
217 the root_part from the ones checked to be mounted.
218 @param job, min_blocks, filter_func, exclude_swap, open_func: Forwarded
219 to get_partition_list().
220 @return List of L{partition} objects that are not mounted.
mbligh9bfef382009-05-21 01:32:27 +0000221 """
222 partitions = get_partition_list(job=job, min_blocks=min_blocks,
223 filter_func=filter_func, exclude_swap=exclude_swap, open_func=open_func)
224
225 unmounted = []
226 for part in partitions:
mbligh0308cc82009-07-28 23:28:20 +0000227 if (part.device != partname_to_device(root_part) and
mbligh9bfef382009-05-21 01:32:27 +0000228 not part.get_mountpoint(open_func=open_func)):
229 unmounted.append(part)
230
231 return unmounted
232
233
234def parallel(partitions, method_name, *args, **dargs):
235 """
236 Run a partition method (with appropriate arguments) in parallel,
237 across a list of partition objects
238 """
239 if not partitions:
240 return
241 job = partitions[0].job
242 flist = []
243 if (not hasattr(partition, method_name) or
244 not callable(getattr(partition, method_name))):
245 err = "partition.parallel got invalid method %s" % method_name
246 raise RuntimeError(err)
247
248 for p in partitions:
249 print_args = list(args)
250 print_args += ['%s=%s' % (key, dargs[key]) for key in dargs.keys()]
mbligh79ceb5b2009-07-10 22:57:18 +0000251 logging.debug('%s.%s(%s)' % (str(p), method_name,
252 ', '.join(print_args)))
mbligh9bfef382009-05-21 01:32:27 +0000253 sys.stdout.flush()
254 def _run_named_method(function, part=p):
255 getattr(part, method_name)(*args, **dargs)
256 flist.append((_run_named_method, ()))
257 job.parallel(*flist)
258
259
260def filesystems():
261 """
262 Return a list of all available filesystems
263 """
264 return [re.sub('(nodev)?\s*', '', fs) for fs in open('/proc/filesystems')]
265
266
267def unmount_partition(device):
268 """
269 Unmount a mounted partition
270
mbligh548e6612009-07-10 22:48:23 +0000271 @param device: e.g. /dev/sda1, /dev/hda1
mbligh9bfef382009-05-21 01:32:27 +0000272 """
273 p = partition(job=None, device=device)
274 p.unmount(record=False)
275
276
277def is_valid_partition(device):
278 """
279 Checks if a partition is valid
280
mbligh548e6612009-07-10 22:48:23 +0000281 @param device: e.g. /dev/sda1, /dev/hda1
mbligh9bfef382009-05-21 01:32:27 +0000282 """
283 parts = get_partition_list(job=None)
284 p_list = [ p.device for p in parts ]
285 if device in p_list:
286 return True
287
288 return False
289
290
291def is_valid_disk(device):
292 """
293 Checks if a disk is valid
294
mbligh548e6612009-07-10 22:48:23 +0000295 @param device: e.g. /dev/sda, /dev/hda
mbligh9bfef382009-05-21 01:32:27 +0000296 """
297 partitions = []
298 for partline in open('/proc/partitions').readlines():
299 fields = partline.strip().split()
300 if len(fields) != 4 or partline.startswith('major'):
301 continue
302 (major, minor, blocks, partname) = fields
303 blocks = int(blocks)
304
305 if not partname[-1].isdigit():
306 # Disk name does not end in number, AFAIK
307 # so use it as a reference to a disk
308 if device.strip("/dev/") == partname:
309 return True
310
311 return False
312
313
314def run_test_on_partitions(job, test, partitions, mountpoint_func,
315 tag, fs_opt, **dargs):
mbligh548e6612009-07-10 22:48:23 +0000316 """
mbligh9bfef382009-05-21 01:32:27 +0000317 Run a test that requires multiple partitions. Filesystems will be
318 made on the partitions and mounted, then the test will run, then the
319 filesystems will be unmounted and fsck'd.
320
mbligh548e6612009-07-10 22:48:23 +0000321 @param job: A job instance to run the test
322 @param test: A string containing the name of the test
323 @param partitions: A list of partition objects, these are passed to the
324 test as partitions=
325 @param mountpoint_func: A callable that returns a mountpoint given a
326 partition instance
327 @param tag: A string tag to make this test unique (Required for control
328 files that make multiple calls to this routine with the same value
329 of 'test'.)
330 @param fs_opt: An FsOptions instance that describes what filesystem to make
331 @param dargs: Dictionary of arguments to be passed to job.run_test() and
332 eventually the test
mbligh9bfef382009-05-21 01:32:27 +0000333 """
334 # setup the filesystem parameters for all the partitions
335 for p in partitions:
336 p.set_fs_options(fs_opt)
337
338 # make and mount all the partitions in parallel
339 parallel(partitions, 'setup_before_test', mountpoint_func=mountpoint_func)
340
341 # run the test against all the partitions
342 job.run_test(test, tag=tag, partitions=partitions, **dargs)
343
344 # fsck and then remake all the filesystems in parallel
345 parallel(partitions, 'cleanup_after_test')
346
347
348class partition(object):
349 """
350 Class for handling partitions and filesystems
351 """
352
353 def __init__(self, job, device, loop_size=0, mountpoint=None):
354 """
mbligh548e6612009-07-10 22:48:23 +0000355 @param job: A L{client.bin.job} instance.
356 @param device: The device in question (e.g."/dev/hda2"). If device is a
357 file it will be mounted as loopback. If you have job config
358 'partition.partitions', e.g.,
359 job.config_set('partition.partitions', ["/dev/sda2", "/dev/sda3"])
360 you may specify a partition in the form of "partN" e.g. "part0",
361 "part1" to refer to elements of the partition list. This is
362 specially useful if you run a test in various machines and you
363 don't want to hardcode device names as those may vary.
364 @param loop_size: Size of loopback device (in MB). Defaults to 0.
mbligh9bfef382009-05-21 01:32:27 +0000365 """
366 # NOTE: This code is used by IBM / ABAT. Do not remove.
367 part = re.compile(r'^part(\d+)$')
368 m = part.match(device)
369 if m:
370 number = int(m.groups()[0])
371 partitions = job.config_get('partition.partitions')
372 try:
373 device = partitions[number]
374 except:
375 raise NameError("Partition '" + device + "' not available")
376
377 self.device = device
378 self.name = os.path.basename(device)
379 self.job = job
380 self.loop = loop_size
381 self.fstype = None
382 self.mountpoint = mountpoint
383 self.mkfs_flags = None
384 self.mount_options = None
385 self.fs_tag = None
386 if self.loop:
387 cmd = 'dd if=/dev/zero of=%s bs=1M count=%d' % (device, loop_size)
388 utils.system(cmd)
389
390
391 def __repr__(self):
392 return '<Partition: %s>' % self.device
393
394
395 def set_fs_options(self, fs_options):
396 """
397 Set filesystem options
398
399 @param fs_options: A L{FsOptions} object
400 """
401
402 self.fstype = fs_options.fstype
403 self.mkfs_flags = fs_options.mkfs_flags
404 self.mount_options = fs_options.mount_options
405 self.fs_tag = fs_options.fs_tag
406
407
408 def run_test(self, test, **dargs):
409 self.job.run_test(test, dir=self.get_mountpoint(), **dargs)
410
411
412 def setup_before_test(self, mountpoint_func):
mbligh548e6612009-07-10 22:48:23 +0000413 """
mbligh9bfef382009-05-21 01:32:27 +0000414 Prepare a partition for running a test. Unmounts any
415 filesystem that's currently mounted on the partition, makes a
416 new filesystem (according to this partition's filesystem
417 options) and mounts it where directed by mountpoint_func.
418
mbligh548e6612009-07-10 22:48:23 +0000419 @param mountpoint_func: A callable that returns a path as a string,
420 given a partition instance.
mbligh9bfef382009-05-21 01:32:27 +0000421 """
422 mountpoint = mountpoint_func(self)
423 if not mountpoint:
424 raise ValueError('Don\'t know where to put this partition')
425 self.unmount(ignore_status=True, record=False)
426 self.mkfs()
427 self.mount(mountpoint)
428
429
430 def cleanup_after_test(self):
mbligh548e6612009-07-10 22:48:23 +0000431 """
mbligh9bfef382009-05-21 01:32:27 +0000432 Cleans up a partition after running a filesystem test. The
433 filesystem is unmounted, and then checked for errors.
434 """
435 self.unmount()
436 self.fsck()
437
438
439 def run_test_on_partition(self, test, mountpoint_func, **dargs):
440 """
441 Executes a test fs-style (umount,mkfs,mount,test)
442
443 Here we unmarshal the args to set up tags before running the test.
444 Tests are also run by first umounting, mkfsing and then mounting
445 before executing the test.
446
mbligh548e6612009-07-10 22:48:23 +0000447 @param test: name of test to run
448 @param mountpoint_func: function to return mount point string
mbligh9bfef382009-05-21 01:32:27 +0000449 """
450 tag = dargs.get('tag')
451 if tag:
452 tag = '%s.%s' % (self.name, tag)
453 elif self.fs_tag:
454 tag = '%s.%s' % (self.name, self.fs_tag)
455 else:
456 tag = self.name
457
mbligh2e7749b2009-07-28 23:21:30 +0000458 # If there's a 'suffix' argument, append it to the tag and remove it
459 suffix = dargs.pop('suffix', None)
460 if suffix:
461 tag = '%s.%s' % (tag, suffix)
462
mbligh9bfef382009-05-21 01:32:27 +0000463 dargs['tag'] = test + '.' + tag
464
465 def _make_partition_and_run_test(test_tag, dir=None, **dargs):
466 self.setup_before_test(mountpoint_func)
467 try:
468 self.job.run_test(test, tag=test_tag, dir=mountpoint, **dargs)
469 finally:
470 self.cleanup_after_test()
471
472 mountpoint = mountpoint_func(self)
473
474 # The tag is the tag for the group (get stripped off by run_group)
475 # The test_tag is the tag for the test itself
476 self.job.run_group(_make_partition_and_run_test,
477 test_tag=tag, dir=mountpoint, **dargs)
478
479
mblighbb1dce32009-07-28 23:18:24 +0000480 def get_mountpoint(self, open_func=open, filename=None):
481 """
482 Find the mount point of this partition object.
483
484 @param open_func: the function to use for opening the file containing
485 the mounted partitions information
486 @param filename: where to look for the mounted partitions information
487 (default None which means it will search /proc/mounts and/or
488 /etc/mtab)
489
490 @returns a string with the mount point of the partition or None if not
491 mounted
492 """
493 if filename:
494 for line in open_func(filename).readlines():
495 parts = line.split()
496 if parts[0] == self.device:
497 return parts[1] # The mountpoint where it's mounted
498 return None
499
500 # no specific file given, look in /proc/mounts
501 res = self.get_mountpoint(open_func=open_func, filename='/proc/mounts')
502 if not res:
503 # sometimes the root partition is reported as /dev/root in
504 # /proc/mounts in this case, try /etc/mtab
505 res = self.get_mountpoint(open_func=open_func, filename='/etc/mtab')
506
507 # trust /etc/mtab only about /
508 if res != '/':
509 res = None
510
511 return res
mbligh9bfef382009-05-21 01:32:27 +0000512
513
514 def mkfs_exec(self, fstype):
515 """
516 Return the proper mkfs executable based on fs
517 """
518 if fstype == 'ext4':
519 if os.path.exists('/sbin/mkfs.ext4'):
520 return 'mkfs'
521 # If ext4 supported e2fsprogs is not installed we use the
522 # autotest supplied one in tools dir which is statically linked"""
523 auto_mkfs = os.path.join(self.job.toolsdir, 'mkfs.ext4dev')
524 if os.path.exists(auto_mkfs):
525 return auto_mkfs
526 else:
527 return 'mkfs'
528
529 raise NameError('Error creating partition for filesystem type %s' %
530 fstype)
531
532
533 def mkfs(self, fstype=None, args='', record=True):
534 """
535 Format a partition to filesystem type
536
mbligh548e6612009-07-10 22:48:23 +0000537 @param fstype: the filesystem type, e.g.. "ext3", "ext2"
538 @param args: arguments to be passed to mkfs command.
539 @param record: if set, output result of mkfs operation to autotest
540 output
mbligh9bfef382009-05-21 01:32:27 +0000541 """
542
543 if list_mount_devices().count(self.device):
544 raise NameError('Attempted to format mounted device %s' %
545 self.device)
546
547 if not fstype:
548 if self.fstype:
549 fstype = self.fstype
550 else:
551 fstype = 'ext2'
552
553 if self.mkfs_flags:
554 args += ' ' + self.mkfs_flags
555 if fstype == 'xfs':
556 args += ' -f'
557
558 if self.loop:
559 # BAH. Inconsistent mkfs syntax SUCKS.
560 if fstype.startswith('ext'):
561 args += ' -F'
562 elif fstype == 'reiserfs':
563 args += ' -f'
mblighb87c2382009-08-03 16:47:06 +0000564
565 # If there isn't already a '-t <type>' argument, add one.
566 if not "-t" in args:
567 args = "-t %s %s" % (fstype, args)
568
mbligh9bfef382009-05-21 01:32:27 +0000569 args = args.strip()
570
571 mkfs_cmd = "%s %s %s" % (self.mkfs_exec(fstype), args, self.device)
572
mbligh9bfef382009-05-21 01:32:27 +0000573 sys.stdout.flush()
574 try:
575 # We throw away the output here - we only need it on error, in
576 # which case it's in the exception
577 utils.system_output("yes | %s" % mkfs_cmd)
578 except error.CmdError, e:
mbligh79ceb5b2009-07-10 22:57:18 +0000579 logging.error(e.result_obj)
mbligh9bfef382009-05-21 01:32:27 +0000580 if record:
581 self.job.record('FAIL', None, mkfs_cmd, error.format_error())
582 raise
583 except:
584 if record:
585 self.job.record('FAIL', None, mkfs_cmd, error.format_error())
586 raise
587 else:
588 if record:
589 self.job.record('GOOD', None, mkfs_cmd)
590 self.fstype = fstype
591
592
593 def get_fsck_exec(self):
594 """
595 Return the proper mkfs executable based on self.fstype
596 """
597 if self.fstype == 'ext4':
598 if os.path.exists('/sbin/fsck.ext4'):
599 return 'fsck'
600 # If ext4 supported e2fsprogs is not installed we use the
601 # autotest supplied one in tools dir which is statically linked"""
602 auto_fsck = os.path.join(self.job.toolsdir, 'fsck.ext4dev')
603 if os.path.exists(auto_fsck):
604 return auto_fsck
605 else:
606 return 'fsck'
607
608 raise NameError('Error creating partition for filesystem type %s' %
609 self.fstype)
610
611
612 def fsck(self, args='-n', record=True):
613 """
614 Run filesystem check
615
mbligh548e6612009-07-10 22:48:23 +0000616 @param args: arguments to filesystem check tool. Default is "-n"
617 which works on most tools.
mbligh9bfef382009-05-21 01:32:27 +0000618 """
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
mbligh9bfef382009-05-21 01:32:27 +0000626 sys.stdout.flush()
627 try:
628 utils.system("yes | " + fsck_cmd)
629 except:
630 if record:
631 self.job.record('FAIL', None, fsck_cmd, error.format_error())
632 raise
633 else:
634 if record:
635 self.job.record('GOOD', None, fsck_cmd)
636
637
638 def mount(self, mountpoint, fstype=None, args='', record=True):
639 """
640 Mount this partition to a mount point
641
mbligh548e6612009-07-10 22:48:23 +0000642 @param mountpoint: If you have not provided a mountpoint to partition
643 object or want to use a different one, you may specify it here.
644 @param fstype: Filesystem type. If not provided partition object value
645 will be used.
646 @param args: Arguments to be passed to "mount" command.
647 @param record: If True, output result of mount operation to autotest
648 output.
mbligh9bfef382009-05-21 01:32:27 +0000649 """
650
651 if fstype is None:
652 fstype = self.fstype
653 else:
654 assert(self.fstype is None or self.fstype == fstype);
655
656 if self.mount_options:
657 args += ' -o ' + self.mount_options
658 if fstype:
659 args += ' -t ' + fstype
660 if self.loop:
661 args += ' -o loop'
662 args = args.lstrip()
663
664 if not mountpoint and not self.mountpoint:
665 raise ValueError("No mountpoint specified and no default "
666 "provided to this partition object")
667 if not mountpoint:
668 mountpoint = self.mountpoint
669
670 mount_cmd = "mount %s %s %s" % (args, self.device, mountpoint)
mbligh9bfef382009-05-21 01:32:27 +0000671
672 if list_mount_devices().count(self.device):
673 err = 'Attempted to mount mounted device'
674 self.job.record('FAIL', None, mount_cmd, err)
675 raise NameError(err)
676 if list_mount_points().count(mountpoint):
677 err = 'Attempted to mount busy mountpoint'
678 self.job.record('FAIL', None, mount_cmd, err)
679 raise NameError(err)
680
681 mtab = open('/etc/mtab')
682 # We have to get an exclusive lock here - mount/umount are racy
683 fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
mbligh9bfef382009-05-21 01:32:27 +0000684 sys.stdout.flush()
685 try:
686 utils.system(mount_cmd)
687 mtab.close()
688 except:
689 mtab.close()
690 if record:
691 self.job.record('FAIL', None, mount_cmd, error.format_error())
692 raise
693 else:
694 if record:
695 self.job.record('GOOD', None, mount_cmd)
696 self.fstype = fstype
697
698
699 def unmount_force(self):
mbligh79ceb5b2009-07-10 22:57:18 +0000700 """
701 Kill all other jobs accessing this partition. Use fuser and ps to find
702 all mounts on this mountpoint and unmount them.
mbligh9bfef382009-05-21 01:32:27 +0000703
mbligh79ceb5b2009-07-10 22:57:18 +0000704 @return: true for success or false for any errors
705 """
mbligh9bfef382009-05-21 01:32:27 +0000706
mbligh79ceb5b2009-07-10 22:57:18 +0000707 logging.debug("Standard umount failed, will try forcing. Users:")
708 try:
709 cmd = 'fuser ' + self.get_mountpoint()
710 logging.debug(cmd)
711 fuser = utils.system_output(cmd)
712 logging.debug(fuser)
713 users = re.sub('.*:', '', fuser).split()
714 for user in users:
715 m = re.match('(\d+)(.*)', user)
716 (pid, usage) = (m.group(1), m.group(2))
717 try:
718 ps = utils.system_output('ps -p %s | tail +2' % pid)
719 logging.debug('%s %s %s' % (usage, pid, ps))
720 except Exception:
721 pass
722 utils.system('ls -l ' + self.device)
723 umount_cmd = "umount -f " + self.device
724 utils.system(umount_cmd)
725 return True
726 except error.CmdError:
727 logging.debug('Umount_force failed for %s' % self.device)
728 return False
mbligh9bfef382009-05-21 01:32:27 +0000729
730
731
732 def unmount(self, ignore_status=False, record=True):
733 """
734 Umount this partition.
735
736 It's easier said than done to umount a partition.
737 We need to lock the mtab file to make sure we don't have any
738 locking problems if we are umounting in paralllel.
739
740 If there turns out to be a problem with the simple umount we
741 end up calling umount_force to get more agressive.
742
mbligh548e6612009-07-10 22:48:23 +0000743 @param ignore_status: should we notice the umount status
744 @param record: if True, output result of umount operation to
745 autotest output
mbligh9bfef382009-05-21 01:32:27 +0000746 """
747
748 mountpoint = self.get_mountpoint()
749 if not mountpoint:
750 # It's not even mounted to start with
751 if record and not ignore_status:
752 msg = 'umount for dev %s has no mountpoint' % self.device
753 self.job.record('FAIL', None, msg, 'Not mounted')
754 return
755
756 umount_cmd = "umount " + mountpoint
757 mtab = open('/etc/mtab')
758
759 # We have to get an exclusive lock here - mount/umount are racy
760 fcntl.flock(mtab.fileno(), fcntl.LOCK_EX)
mbligh9bfef382009-05-21 01:32:27 +0000761 sys.stdout.flush()
762 try:
763 utils.system(umount_cmd)
764 mtab.close()
765 if record:
766 self.job.record('GOOD', None, umount_cmd)
767 except (error.CmdError, IOError):
768 mtab.close()
769
770 # Try the forceful umount
771 if self.unmount_force():
772 return
773
774 # If we are here we cannot umount this partition
775 if record and not ignore_status:
mbligh79ceb5b2009-07-10 22:57:18 +0000776 self.job.record('FAIL', None, umount_cmd, error.format_error())
mbligh9bfef382009-05-21 01:32:27 +0000777 raise
778
779
780 def wipe(self):
mbligh548e6612009-07-10 22:48:23 +0000781 """
782 Delete all files of a given partition filesystem.
783 """
mbligh9bfef382009-05-21 01:32:27 +0000784 wipe_filesystem(self.job, self.get_mountpoint())
785
786
787 def get_io_scheduler_list(self, device_name):
788 names = open(self.__sched_path(device_name)).read()
789 return names.translate(string.maketrans('[]', ' ')).split()
790
791
792 def get_io_scheduler(self, device_name):
793 return re.split('[\[\]]',
794 open(self.__sched_path(device_name)).read())[1]
795
796
797 def set_io_scheduler(self, device_name, name):
798 if name not in self.get_io_scheduler_list(device_name):
799 raise NameError('No such IO scheduler: %s' % name)
800 f = open(self.__sched_path(device_name), 'w')
mbligh79ceb5b2009-07-10 22:57:18 +0000801 f.write(name)
mbligh9bfef382009-05-21 01:32:27 +0000802 f.close()
803
804
805 def __sched_path(self, device_name):
806 return '/sys/block/%s/queue/scheduler' % device_name
807
808
809class virtual_partition:
810 """
811 Handles block device emulation using file images of disks.
812 It's important to note that this API can be used only if
813 we have the following programs present on the client machine:
814
815 * sfdisk
816 * losetup
817 * kpartx
818 """
819 def __init__(self, file_img, file_size):
820 """
821 Creates a virtual partition, keeping record of the device created
822 under /dev/mapper (device attribute) so test writers can use it
823 on their filesystem tests.
824
mbligh548e6612009-07-10 22:48:23 +0000825 @param file_img: Path to the desired disk image file.
826 @param file_size: Size of the desired image in Bytes.
mbligh9bfef382009-05-21 01:32:27 +0000827 """
mbligh79ceb5b2009-07-10 22:57:18 +0000828 logging.debug('Sanity check before attempting to create virtual '
829 'partition')
mbligh9bfef382009-05-21 01:32:27 +0000830 try:
831 os_dep.commands('sfdisk', 'losetup', 'kpartx')
832 except ValueError, e:
833 e_msg = 'Unable to create virtual partition: %s' % e
834 raise error.AutotestError(e_msg)
835
mbligh79ceb5b2009-07-10 22:57:18 +0000836 logging.debug('Creating virtual partition')
mbligh9bfef382009-05-21 01:32:27 +0000837 self.img = self.__create_disk_img(file_img, file_size)
838 self.loop = self.__attach_img_loop(self.img)
839 self.__create_single_partition(self.loop)
840 self.device = self.__create_entries_partition(self.loop)
mbligh79ceb5b2009-07-10 22:57:18 +0000841 logging.debug('Virtual partition successfuly created')
842 logging.debug('Image disk: %s', self.img)
843 logging.debug('Loopback device: %s', self.loop)
844 logging.debug('Device path: %s', self.device)
mbligh9bfef382009-05-21 01:32:27 +0000845
846
847 def destroy(self):
848 """
849 Removes the virtual partition from /dev/mapper, detaches the image file
850 from the loopback device and removes the image file.
851 """
mbligh79ceb5b2009-07-10 22:57:18 +0000852 logging.debug('Removing virtual partition - device %s', self.device)
mbligh9bfef382009-05-21 01:32:27 +0000853 self.__remove_entries_partition()
854 self.__detach_img_loop()
855 self.__remove_disk_img()
856
857
858 def __create_disk_img(self, img_path, size):
859 """
860 Creates a disk image using dd.
861
mbligh548e6612009-07-10 22:48:23 +0000862 @param img_path: Path to the desired image file.
863 @param size: Size of the desired image in Bytes.
864 @returns: Path of the image created.
mbligh9bfef382009-05-21 01:32:27 +0000865 """
mbligh79ceb5b2009-07-10 22:57:18 +0000866 logging.debug('Creating disk image %s, size = %d Bytes', img_path, size)
mbligh9bfef382009-05-21 01:32:27 +0000867 try:
mbligh79ceb5b2009-07-10 22:57:18 +0000868 cmd = 'dd if=/dev/zero of=%s bs=1024 count=%d' % (img_path, size)
mbligh9bfef382009-05-21 01:32:27 +0000869 utils.system(cmd)
870 except error.CmdError, e:
871 e_msg = 'Error creating disk image %s: %s' % (img_path, e)
872 raise error.AutotestError(e_msg)
873 return img_path
874
875
876 def __attach_img_loop(self, img_path):
877 """
878 Attaches a file image to a loopback device using losetup.
879
mbligh548e6612009-07-10 22:48:23 +0000880 @param img_path: Path of the image file that will be attached to a
881 loopback device
882 @returns: Path of the loopback device associated.
mbligh9bfef382009-05-21 01:32:27 +0000883 """
mbligh79ceb5b2009-07-10 22:57:18 +0000884 logging.debug('Attaching image %s to a loop device', img_path)
mbligh9bfef382009-05-21 01:32:27 +0000885 try:
886 cmd = 'losetup -f'
887 loop_path = utils.system_output(cmd)
888 cmd = 'losetup -f %s' % img_path
889 utils.system(cmd)
890 except error.CmdError, e:
891 e_msg = 'Error attaching image %s to a loop device: %s' % \
892 (img_path, e)
893 raise error.AutotestError(e_msg)
894 return loop_path
895
896
897 def __create_single_partition(self, loop_path):
898 """
899 Creates a single partition encompassing the whole 'disk' using cfdisk.
900
mbligh548e6612009-07-10 22:48:23 +0000901 @param loop_path: Path to the loopback device.
mbligh9bfef382009-05-21 01:32:27 +0000902 """
mbligh79ceb5b2009-07-10 22:57:18 +0000903 logging.debug('Creating single partition on %s', loop_path)
mbligh9bfef382009-05-21 01:32:27 +0000904 try:
905 single_part_cmd = '0,,c\n'
906 sfdisk_file_path = '/tmp/create_partition.sfdisk'
907 sfdisk_cmd_file = open(sfdisk_file_path, 'w')
908 sfdisk_cmd_file.write(single_part_cmd)
909 sfdisk_cmd_file.close()
910 utils.system('sfdisk %s < %s' % (loop_path, sfdisk_file_path))
911 except error.CmdError, e:
912 e_msg = 'Error partitioning device %s: %s' % (loop_path, e)
913 raise error.AutotestError(e_msg)
914
915
916 def __create_entries_partition(self, loop_path):
917 """
918 Takes the newly created partition table on the loopback device and
919 makes all its devices available under /dev/mapper. As we previously
920 have partitioned it using a single partition, only one partition
921 will be returned.
922
mbligh548e6612009-07-10 22:48:23 +0000923 @param loop_path: Path to the loopback device.
mbligh9bfef382009-05-21 01:32:27 +0000924 """
mbligh79ceb5b2009-07-10 22:57:18 +0000925 logging.debug('Creating entries under /dev/mapper for %s loop dev',
926 loop_path)
mbligh9bfef382009-05-21 01:32:27 +0000927 try:
928 cmd = 'kpartx -a %s' % loop_path
929 utils.system(cmd)
930 l_cmd = 'kpartx -l %s | cut -f1 -d " "' % loop_path
931 device = utils.system_output(l_cmd)
932 except error.CmdError, e:
933 e_msg = 'Error creating entries for %s: %s' % (loop_path, e)
934 raise error.AutotestError(e_msg)
935 return os.path.join('/dev/mapper', device)
936
937
938 def __remove_entries_partition(self):
939 """
mbligh79ceb5b2009-07-10 22:57:18 +0000940 Removes the entries under /dev/mapper for the partition associated
mbligh9bfef382009-05-21 01:32:27 +0000941 to the loopback device.
942 """
mbligh79ceb5b2009-07-10 22:57:18 +0000943 logging.debug('Removing the entry on /dev/mapper for %s loop dev',
944 self.loop)
mbligh9bfef382009-05-21 01:32:27 +0000945 try:
946 cmd = 'kpartx -d %s' % self.loop
947 utils.system(cmd)
948 except error.CmdError, e:
949 e_msg = 'Error removing entries for loop %s: %s' % (self.loop, e)
950 raise error.AutotestError(e_msg)
951
952
953 def __detach_img_loop(self):
954 """
955 Detaches the image file from the loopback device.
956 """
mbligh79ceb5b2009-07-10 22:57:18 +0000957 logging.debug('Detaching image %s from loop device %s', self.img,
958 self.loop)
mbligh9bfef382009-05-21 01:32:27 +0000959 try:
960 cmd = 'losetup -d %s' % self.loop
961 utils.system(cmd)
962 except error.CmdError, e:
mbligh79ceb5b2009-07-10 22:57:18 +0000963 e_msg = ('Error detaching image %s from loop device %s: %s' %
964 (self.loop, e))
mbligh9bfef382009-05-21 01:32:27 +0000965 raise error.AutotestError(e_msg)
966
967
968 def __remove_disk_img(self):
969 """
970 Removes the disk image.
971 """
mbligh79ceb5b2009-07-10 22:57:18 +0000972 logging.debug('Removing disk image %s', self.img)
mbligh9bfef382009-05-21 01:32:27 +0000973 try:
974 os.remove(self.img)
975 except:
976 e_msg = 'Error removing image file %s' % self.img
977 raise error.AutotestError(e_msg)