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