Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ fbdf1d8f

History | View | Annotate | Download (16.5 kB)

1 ae48a082 Nikos Skalkotos
# Copyright 2012 GRNET S.A. All rights reserved.
2 ae48a082 Nikos Skalkotos
#
3 ae48a082 Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
4 ae48a082 Nikos Skalkotos
# without modification, are permitted provided that the following
5 ae48a082 Nikos Skalkotos
# conditions are met:
6 ae48a082 Nikos Skalkotos
#
7 ae48a082 Nikos Skalkotos
#   1. Redistributions of source code must retain the above
8 ae48a082 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
9 ae48a082 Nikos Skalkotos
#      disclaimer.
10 ae48a082 Nikos Skalkotos
#
11 ae48a082 Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
12 ae48a082 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
13 ae48a082 Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
14 ae48a082 Nikos Skalkotos
#      provided with the distribution.
15 ae48a082 Nikos Skalkotos
#
16 ae48a082 Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 ae48a082 Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 ae48a082 Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 ae48a082 Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 ae48a082 Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 ae48a082 Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 ae48a082 Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 ae48a082 Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 ae48a082 Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 ae48a082 Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 ae48a082 Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 ae48a082 Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
28 ae48a082 Nikos Skalkotos
#
29 ae48a082 Nikos Skalkotos
# The views and conclusions contained in the software and
30 ae48a082 Nikos Skalkotos
# documentation are those of the authors and should not be
31 ae48a082 Nikos Skalkotos
# interpreted as representing official policies, either expressed
32 ae48a082 Nikos Skalkotos
# or implied, of GRNET S.A.
33 d57775d4 Nikos Skalkotos
34 979096dd Nikos Skalkotos
from image_creator.util import get_command
35 e77e66a9 Nikos Skalkotos
from image_creator.util import FatalError
36 331aa0ec Nikos Skalkotos
from image_creator.gpt import GPTPartitionTable
37 d57775d4 Nikos Skalkotos
import stat
38 d57775d4 Nikos Skalkotos
import os
39 d57775d4 Nikos Skalkotos
import tempfile
40 d57775d4 Nikos Skalkotos
import uuid
41 d57775d4 Nikos Skalkotos
import re
42 1377b8a7 Nikos Skalkotos
import sys
43 1377b8a7 Nikos Skalkotos
import guestfs
44 0db22eac Nikos Skalkotos
import time
45 d603d80d Nikos Skalkotos
from sendfile import sendfile
46 1377b8a7 Nikos Skalkotos
47 8c574358 Nikos Skalkotos
48 3ccb2618 Nikos Skalkotos
dd = get_command('dd')
49 3ccb2618 Nikos Skalkotos
dmsetup = get_command('dmsetup')
50 3ccb2618 Nikos Skalkotos
losetup = get_command('losetup')
51 3ccb2618 Nikos Skalkotos
blockdev = get_command('blockdev')
52 01a7cff3 Nikos Skalkotos
53 01a7cff3 Nikos Skalkotos
54 d57775d4 Nikos Skalkotos
class Disk(object):
55 3b2f6619 Nikos Skalkotos
    """This class represents a hard disk hosting an Operating System
56 3b2f6619 Nikos Skalkotos

57 3b2f6619 Nikos Skalkotos
    A Disk instance never alters the source media it is created from.
58 3b2f6619 Nikos Skalkotos
    Any change is done on a snapshot created by the device-mapper of
59 3b2f6619 Nikos Skalkotos
    the Linux kernel.
60 3b2f6619 Nikos Skalkotos
    """
61 d57775d4 Nikos Skalkotos
62 e77e66a9 Nikos Skalkotos
    def __init__(self, source, output):
63 3b2f6619 Nikos Skalkotos
        """Create a new Disk instance out of a source media. The source
64 3b2f6619 Nikos Skalkotos
        media can be an image file, a block device or a directory."""
65 d57775d4 Nikos Skalkotos
        self._cleanup_jobs = []
66 d57775d4 Nikos Skalkotos
        self._devices = []
67 d57775d4 Nikos Skalkotos
        self.source = source
68 e77e66a9 Nikos Skalkotos
        self.out = output
69 d57775d4 Nikos Skalkotos
70 d57775d4 Nikos Skalkotos
    def _add_cleanup(self, job, *args):
71 d57775d4 Nikos Skalkotos
        self._cleanup_jobs.append((job, args))
72 d57775d4 Nikos Skalkotos
73 d57775d4 Nikos Skalkotos
    def _losetup(self, fname):
74 3ccb2618 Nikos Skalkotos
        loop = losetup('-f', '--show', fname)
75 ae48a082 Nikos Skalkotos
        loop = loop.strip()  # remove the new-line char
76 3ccb2618 Nikos Skalkotos
        self._add_cleanup(losetup, '-d', loop)
77 3ccb2618 Nikos Skalkotos
        return loop
78 d57775d4 Nikos Skalkotos
79 d57775d4 Nikos Skalkotos
    def _dir_to_disk(self):
80 279f2c7d Nikos Skalkotos
        raise FatalError("Using a directory as media source is not supported "
81 279f2c7d Nikos Skalkotos
                         "yet!")
82 d57775d4 Nikos Skalkotos
83 d57775d4 Nikos Skalkotos
    def cleanup(self):
84 3b2f6619 Nikos Skalkotos
        """Cleanup internal data. This needs to be called before the
85 3b2f6619 Nikos Skalkotos
        program ends.
86 3b2f6619 Nikos Skalkotos
        """
87 1377b8a7 Nikos Skalkotos
        while len(self._devices):
88 1377b8a7 Nikos Skalkotos
            device = self._devices.pop()
89 1377b8a7 Nikos Skalkotos
            device.destroy()
90 8c574358 Nikos Skalkotos
91 d57775d4 Nikos Skalkotos
        while len(self._cleanup_jobs):
92 d57775d4 Nikos Skalkotos
            job, args = self._cleanup_jobs.pop()
93 d57775d4 Nikos Skalkotos
            job(*args)
94 d57775d4 Nikos Skalkotos
95 e22aa3a9 Nikos Skalkotos
    def snapshot(self):
96 e22aa3a9 Nikos Skalkotos
        """Creates a snapshot of the original source media of the Disk
97 e22aa3a9 Nikos Skalkotos
        instance.
98 3b2f6619 Nikos Skalkotos
        """
99 22a6d232 Nikos Skalkotos
100 e77e66a9 Nikos Skalkotos
        self.out.output("Examining source media `%s'..." % self.source, False)
101 3f70f242 Nikos Skalkotos
        sourcedev = self.source
102 3f70f242 Nikos Skalkotos
        mode = os.stat(self.source).st_mode
103 3f70f242 Nikos Skalkotos
        if stat.S_ISDIR(mode):
104 279f2c7d Nikos Skalkotos
            self.out.success('looks like a directory')
105 3f70f242 Nikos Skalkotos
            return self._losetup(self._dir_to_disk())
106 3f70f242 Nikos Skalkotos
        elif stat.S_ISREG(mode):
107 279f2c7d Nikos Skalkotos
            self.out.success('looks like an image file')
108 3f70f242 Nikos Skalkotos
            sourcedev = self._losetup(self.source)
109 3f70f242 Nikos Skalkotos
        elif not stat.S_ISBLK(mode):
110 3f70f242 Nikos Skalkotos
            raise ValueError("Invalid media source. Only block devices, "
111 f99fe99d Nikos Skalkotos
                             "regular files and directories are supported.")
112 3f70f242 Nikos Skalkotos
        else:
113 e77e66a9 Nikos Skalkotos
            self.out.success('looks like a block device')
114 d57775d4 Nikos Skalkotos
115 d57775d4 Nikos Skalkotos
        # Take a snapshot and return it to the user
116 e77e66a9 Nikos Skalkotos
        self.out.output("Snapshotting media source...", False)
117 3f70f242 Nikos Skalkotos
        size = blockdev('--getsize', sourcedev)
118 3f70f242 Nikos Skalkotos
        cowfd, cow = tempfile.mkstemp()
119 3f70f242 Nikos Skalkotos
        os.close(cowfd)
120 3f70f242 Nikos Skalkotos
        self._add_cleanup(os.unlink, cow)
121 3f70f242 Nikos Skalkotos
        # Create 1G cow sparse file
122 5b801534 Nikos Skalkotos
        dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', 'seek=%d' % (1024 * 1024))
123 3f70f242 Nikos Skalkotos
        cowdev = self._losetup(cow)
124 3f70f242 Nikos Skalkotos
125 3f70f242 Nikos Skalkotos
        snapshot = uuid.uuid4().hex
126 3f70f242 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
127 3f70f242 Nikos Skalkotos
        try:
128 f99fe99d Nikos Skalkotos
            os.write(tablefd, "0 %d snapshot %s %s n 8" %
129 f99fe99d Nikos Skalkotos
                              (int(size), sourcedev, cowdev))
130 3f70f242 Nikos Skalkotos
            dmsetup('create', snapshot, table)
131 3f70f242 Nikos Skalkotos
            self._add_cleanup(dmsetup, 'remove', snapshot)
132 3f70f242 Nikos Skalkotos
            # Sometimes dmsetup remove fails with Device or resource busy,
133 3f70f242 Nikos Skalkotos
            # although everything is cleaned up and the snapshot is not
134 3f70f242 Nikos Skalkotos
            # used by anyone. Add a 2 seconds delay to be on the safe side.
135 3f70f242 Nikos Skalkotos
            self._add_cleanup(time.sleep, 2)
136 3f70f242 Nikos Skalkotos
137 3f70f242 Nikos Skalkotos
        finally:
138 3f70f242 Nikos Skalkotos
            os.unlink(table)
139 e77e66a9 Nikos Skalkotos
        self.out.success('done')
140 e22aa3a9 Nikos Skalkotos
        return "/dev/mapper/%s" % snapshot
141 e22aa3a9 Nikos Skalkotos
142 e22aa3a9 Nikos Skalkotos
    def get_device(self, media):
143 e22aa3a9 Nikos Skalkotos
        """Returns a newly created DiskDevice instance."""
144 e22aa3a9 Nikos Skalkotos
145 e77e66a9 Nikos Skalkotos
        new_device = DiskDevice(media, self.out)
146 d57775d4 Nikos Skalkotos
        self._devices.append(new_device)
147 0db22eac Nikos Skalkotos
        new_device.enable()
148 d57775d4 Nikos Skalkotos
        return new_device
149 d57775d4 Nikos Skalkotos
150 1377b8a7 Nikos Skalkotos
    def destroy_device(self, device):
151 3b2f6619 Nikos Skalkotos
        """Destroys a DiskDevice instance previously created by
152 3b2f6619 Nikos Skalkotos
        get_device method.
153 3b2f6619 Nikos Skalkotos
        """
154 1377b8a7 Nikos Skalkotos
        self._devices.remove(device)
155 1377b8a7 Nikos Skalkotos
        device.destroy()
156 1377b8a7 Nikos Skalkotos
157 8c574358 Nikos Skalkotos
158 d57775d4 Nikos Skalkotos
class DiskDevice(object):
159 3b2f6619 Nikos Skalkotos
    """This class represents a block device hosting an Operating System
160 3b2f6619 Nikos Skalkotos
    as created by the device-mapper.
161 3b2f6619 Nikos Skalkotos
    """
162 d57775d4 Nikos Skalkotos
163 e77e66a9 Nikos Skalkotos
    def __init__(self, device, output, bootable=True):
164 3b2f6619 Nikos Skalkotos
        """Create a new DiskDevice."""
165 0db22eac Nikos Skalkotos
166 e108efd2 Nikos Skalkotos
        self.real_device = device
167 e77e66a9 Nikos Skalkotos
        self.out = output
168 1377b8a7 Nikos Skalkotos
        self.bootable = bootable
169 586da0a0 Nikos Skalkotos
        self.progress_bar = None
170 e108efd2 Nikos Skalkotos
        self.guestfs_device = None
171 e8b1b48b Nikos Skalkotos
        self.meta = {}
172 1377b8a7 Nikos Skalkotos
173 1377b8a7 Nikos Skalkotos
        self.g = guestfs.GuestFS()
174 e108efd2 Nikos Skalkotos
        self.g.add_drive_opts(self.real_device, readonly=0)
175 0d5a999d Nikos Skalkotos
176 5f27b178 Nikos Skalkotos
        # Before version 1.17.14 the recovery process, which is a fork of the
177 5f27b178 Nikos Skalkotos
        # original process that called libguestfs, did not close its inherited
178 5f27b178 Nikos Skalkotos
        # file descriptors. This can cause problems especially if the parent
179 5f27b178 Nikos Skalkotos
        # process has opened pipes. Since the recovery process is an optional
180 5f27b178 Nikos Skalkotos
        # feature of libguestfs, it's better to disable it.
181 5f27b178 Nikos Skalkotos
        self.g.set_recovery_proc(0)
182 5f27b178 Nikos Skalkotos
        version = self.g.version()
183 f99fe99d Nikos Skalkotos
        if version['major'] > 1 or \
184 f99fe99d Nikos Skalkotos
            (version['major'] == 1 and (version['minor'] >= 18 or
185 f99fe99d Nikos Skalkotos
                                        (version['minor'] == 17 and
186 f99fe99d Nikos Skalkotos
                                         version['release'] >= 14))):
187 5f27b178 Nikos Skalkotos
            self.g.set_recovery_proc(1)
188 5f27b178 Nikos Skalkotos
            self.out.output("Enabling recovery proc")
189 5f27b178 Nikos Skalkotos
190 586da0a0 Nikos Skalkotos
        #self.g.set_trace(1)
191 586da0a0 Nikos Skalkotos
        #self.g.set_verbose(1)
192 0d5a999d Nikos Skalkotos
193 0db22eac Nikos Skalkotos
        self.guestfs_enabled = False
194 ae48a082 Nikos Skalkotos
195 0db22eac Nikos Skalkotos
    def enable(self):
196 0db22eac Nikos Skalkotos
        """Enable a newly created DiskDevice"""
197 96171db1 Nikos Skalkotos
        self.progressbar = self.out.Progress(100, "Launching helper VM",
198 96171db1 Nikos Skalkotos
                                             "percent")
199 3f70f242 Nikos Skalkotos
        eh = self.g.set_event_callback(self.progress_callback,
200 f99fe99d Nikos Skalkotos
                                       guestfs.EVENT_PROGRESS)
201 3f70f242 Nikos Skalkotos
        self.g.launch()
202 3f70f242 Nikos Skalkotos
        self.guestfs_enabled = True
203 3f70f242 Nikos Skalkotos
        self.g.delete_event_callback(eh)
204 e77e66a9 Nikos Skalkotos
        self.progressbar.success('done')
205 e77e66a9 Nikos Skalkotos
        self.progressbar = None
206 3f70f242 Nikos Skalkotos
207 e77e66a9 Nikos Skalkotos
        self.out.output('Inspecting Operating System...', False)
208 3f70f242 Nikos Skalkotos
        roots = self.g.inspect_os()
209 3f70f242 Nikos Skalkotos
        if len(roots) == 0:
210 3f70f242 Nikos Skalkotos
            raise FatalError("No operating system found")
211 3f70f242 Nikos Skalkotos
        if len(roots) > 1:
212 3f70f242 Nikos Skalkotos
            raise FatalError("Multiple operating systems found."
213 835171dc Nikos Skalkotos
                             "We only support images with one OS.")
214 3f70f242 Nikos Skalkotos
        self.root = roots[0]
215 e108efd2 Nikos Skalkotos
        self.guestfs_device = self.g.part_to_dev(self.root)
216 e8b1b48b Nikos Skalkotos
        self.meta['SIZE'] = self.g.blockdev_getsize64(self.guestfs_device)
217 e8b1b48b Nikos Skalkotos
        self.meta['PARTITION_TABLE'] = \
218 f99fe99d Nikos Skalkotos
            self.g.part_get_parttype(self.guestfs_device)
219 331aa0ec Nikos Skalkotos
220 3f70f242 Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
221 3f70f242 Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
222 e77e66a9 Nikos Skalkotos
        self.out.success('found a(n) %s system' % self.distro)
223 8c574358 Nikos Skalkotos
224 1377b8a7 Nikos Skalkotos
    def destroy(self):
225 3b2f6619 Nikos Skalkotos
        """Destroy this DiskDevice instance."""
226 0db22eac Nikos Skalkotos
227 0db22eac Nikos Skalkotos
        if self.guestfs_enabled:
228 0db22eac Nikos Skalkotos
            self.g.umount_all()
229 0db22eac Nikos Skalkotos
            self.g.sync()
230 0db22eac Nikos Skalkotos
231 0db22eac Nikos Skalkotos
        # Close the guestfs handler if open
232 aa2062ba Nikos Skalkotos
        self.g.close()
233 8c574358 Nikos Skalkotos
234 586da0a0 Nikos Skalkotos
    def progress_callback(self, ev, eh, buf, array):
235 586da0a0 Nikos Skalkotos
        position = array[2]
236 586da0a0 Nikos Skalkotos
        total = array[3]
237 586da0a0 Nikos Skalkotos
238 b1395967 Nikos Skalkotos
        self.progressbar.goto((position * 100) // total)
239 586da0a0 Nikos Skalkotos
240 550d4a49 Nikos Skalkotos
    def mount(self, readonly=False):
241 3b2f6619 Nikos Skalkotos
        """Mount all disk partitions in a correct order."""
242 979096dd Nikos Skalkotos
243 550d4a49 Nikos Skalkotos
        mount = self.g.mount_ro if readonly else self.g.mount
244 e77e66a9 Nikos Skalkotos
        self.out.output("Mounting image...", False)
245 0d5a999d Nikos Skalkotos
        mps = self.g.inspect_get_mountpoints(self.root)
246 8c574358 Nikos Skalkotos
247 1377b8a7 Nikos Skalkotos
        # Sort the keys to mount the fs in a correct order.
248 1377b8a7 Nikos Skalkotos
        # / should be mounted befor /boot, etc
249 8c574358 Nikos Skalkotos
        def compare(a, b):
250 8c574358 Nikos Skalkotos
            if len(a[0]) > len(b[0]):
251 8c574358 Nikos Skalkotos
                return 1
252 8c574358 Nikos Skalkotos
            elif len(a[0]) == len(b[0]):
253 8c574358 Nikos Skalkotos
                return 0
254 8c574358 Nikos Skalkotos
            else:
255 8c574358 Nikos Skalkotos
                return -1
256 1377b8a7 Nikos Skalkotos
        mps.sort(compare)
257 1377b8a7 Nikos Skalkotos
        for mp, dev in mps:
258 1377b8a7 Nikos Skalkotos
            try:
259 550d4a49 Nikos Skalkotos
                mount(dev, mp)
260 1377b8a7 Nikos Skalkotos
            except RuntimeError as msg:
261 e77e66a9 Nikos Skalkotos
                self.out.warn("%s (ignored)" % msg)
262 e77e66a9 Nikos Skalkotos
        self.out.success("done")
263 d57775d4 Nikos Skalkotos
264 8c574358 Nikos Skalkotos
    def umount(self):
265 3b2f6619 Nikos Skalkotos
        """Umount all mounted filesystems."""
266 8c574358 Nikos Skalkotos
        self.g.umount_all()
267 8c574358 Nikos Skalkotos
268 e8b1b48b Nikos Skalkotos
    def _last_partition(self):
269 e8b1b48b Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
270 e8b1b48b Nikos Skalkotos
            msg = "Unsupported partition table: %s. Only msdos and gpt " \
271 f99fe99d Nikos Skalkotos
                "partition tables are supported" % self.meta['PARTITION_TABLE']
272 e8b1b48b Nikos Skalkotos
            raise FatalError(msg)
273 e8b1b48b Nikos Skalkotos
274 f99fe99d Nikos Skalkotos
        is_extended = lambda p: \
275 f99fe99d Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5
276 f99fe99d Nikos Skalkotos
        is_logical = lambda p: \
277 f99fe99d Nikos Skalkotos
            self.meta['PARTITION_TABLE'] != 'msdos' and p['part_num'] > 4
278 e8b1b48b Nikos Skalkotos
279 e8b1b48b Nikos Skalkotos
        partitions = self.g.part_list(self.guestfs_device)
280 e8b1b48b Nikos Skalkotos
        last_partition = partitions[-1]
281 e8b1b48b Nikos Skalkotos
282 e8b1b48b Nikos Skalkotos
        if is_logical(last_partition):
283 e8b1b48b Nikos Skalkotos
            # The disk contains extended and logical partitions....
284 e8b1b48b Nikos Skalkotos
            extended = [p for p in partitions if is_extended(p)][0]
285 e8b1b48b Nikos Skalkotos
            last_primary = [p for p in partitions if p['part_num'] <= 4][-1]
286 e8b1b48b Nikos Skalkotos
287 e8b1b48b Nikos Skalkotos
            # check if extended is the last primary partition
288 e8b1b48b Nikos Skalkotos
            if last_primary['part_num'] > extended['part_num']:
289 e8b1b48b Nikos Skalkotos
                last_partition = last_primary
290 e8b1b48b Nikos Skalkotos
291 e8b1b48b Nikos Skalkotos
        return last_partition
292 e8b1b48b Nikos Skalkotos
293 8c574358 Nikos Skalkotos
    def shrink(self):
294 3b2f6619 Nikos Skalkotos
        """Shrink the disk.
295 3b2f6619 Nikos Skalkotos

296 3b2f6619 Nikos Skalkotos
        This is accomplished by shrinking the last filesystem in the
297 3b2f6619 Nikos Skalkotos
        disk and then updating the partition table. The new disk size
298 3b2f6619 Nikos Skalkotos
        (in bytes) is returned.
299 e108efd2 Nikos Skalkotos

300 e108efd2 Nikos Skalkotos
        ATTENTION: make sure unmount is called before shrink
301 3b2f6619 Nikos Skalkotos
        """
302 f99fe99d Nikos Skalkotos
        get_fstype = lambda p: \
303 f99fe99d Nikos Skalkotos
            self.g.vfs_type("%s%d" % (self.guestfs_device, p['part_num']))
304 f99fe99d Nikos Skalkotos
        is_logical = lambda p: \
305 f99fe99d Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
306 f99fe99d Nikos Skalkotos
        is_extended = lambda p: \
307 f99fe99d Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and \
308 f99fe99d Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5
309 e8b1b48b Nikos Skalkotos
310 e8b1b48b Nikos Skalkotos
        part_add = lambda ptype, start, stop: \
311 f99fe99d Nikos Skalkotos
            self.g.part_add(self.guestfs_device, ptype, start, stop)
312 e8b1b48b Nikos Skalkotos
        part_del = lambda p: self.g.part_del(self.guestfs_device, p)
313 e8b1b48b Nikos Skalkotos
        part_get_id = lambda p: self.g.part_get_mbr_id(self.guestfs_device, p)
314 f99fe99d Nikos Skalkotos
        part_set_id = lambda p, id: \
315 f99fe99d Nikos Skalkotos
            self.g.part_set_mbr_id(self.guestfs_device, p, id)
316 f99fe99d Nikos Skalkotos
        part_get_bootable = lambda p: \
317 f99fe99d Nikos Skalkotos
            self.g.part_get_bootable(self.guestfs_device, p)
318 f99fe99d Nikos Skalkotos
        part_set_bootable = lambda p, bootable: \
319 f99fe99d Nikos Skalkotos
            self.g.part_set_bootable(self.guestfs_device, p, bootable)
320 e8b1b48b Nikos Skalkotos
321 e8b1b48b Nikos Skalkotos
        MB = 2 ** 20
322 8c574358 Nikos Skalkotos
323 e77e66a9 Nikos Skalkotos
        self.out.output("Shrinking image (this may take a while)...", False)
324 8c574358 Nikos Skalkotos
325 9666a511 Nikos Skalkotos
        sector_size = self.g.blockdev_getss(self.guestfs_device)
326 9666a511 Nikos Skalkotos
327 e8b1b48b Nikos Skalkotos
        last_part = None
328 e8b1b48b Nikos Skalkotos
        fstype = None
329 e8b1b48b Nikos Skalkotos
        while True:
330 e8b1b48b Nikos Skalkotos
            last_part = self._last_partition()
331 e8b1b48b Nikos Skalkotos
            fstype = get_fstype(last_part)
332 e8b1b48b Nikos Skalkotos
333 e8b1b48b Nikos Skalkotos
            if fstype == 'swap':
334 e8b1b48b Nikos Skalkotos
                self.meta['SWAP'] = "%d:%s" % \
335 f99fe99d Nikos Skalkotos
                    (last_part['part_num'],
336 f99fe99d Nikos Skalkotos
                     (last_part['part_size'] + MB - 1) // MB)
337 e8b1b48b Nikos Skalkotos
                part_del(last_part['part_num'])
338 e8b1b48b Nikos Skalkotos
                continue
339 e8b1b48b Nikos Skalkotos
            elif is_extended(last_part):
340 e8b1b48b Nikos Skalkotos
                part_del(last_part['part_num'])
341 e8b1b48b Nikos Skalkotos
                continue
342 e8b1b48b Nikos Skalkotos
343 9666a511 Nikos Skalkotos
            # Most disk manipulation programs leave 2048 sectors after the last
344 9666a511 Nikos Skalkotos
            # partition
345 9666a511 Nikos Skalkotos
            new_size = last_part['part_end'] + 1 + 2048 * sector_size
346 9666a511 Nikos Skalkotos
            self.meta['SIZE'] = min(self.meta['SIZE'], new_size)
347 e8b1b48b Nikos Skalkotos
            break
348 e8b1b48b Nikos Skalkotos
349 e8b1b48b Nikos Skalkotos
        if not re.match("ext[234]", fstype):
350 e77e66a9 Nikos Skalkotos
            self.out.warn("Don't know how to resize %s partitions." % fstype)
351 e8b1b48b Nikos Skalkotos
            return self.meta['SIZE']
352 e8b1b48b Nikos Skalkotos
353 e8b1b48b Nikos Skalkotos
        part_dev = "%s%d" % (self.guestfs_device, last_part['part_num'])
354 3f70f242 Nikos Skalkotos
        self.g.e2fsck_f(part_dev)
355 3f70f242 Nikos Skalkotos
        self.g.resize2fs_M(part_dev)
356 3f70f242 Nikos Skalkotos
357 6f319b6a Nikos Skalkotos
        out = self.g.tune2fs_l(part_dev)
358 3f70f242 Nikos Skalkotos
        block_size = int(
359 6f319b6a Nikos Skalkotos
            filter(lambda x: x[0] == 'Block size', out)[0][1])
360 3f70f242 Nikos Skalkotos
        block_cnt = int(
361 6f319b6a Nikos Skalkotos
            filter(lambda x: x[0] == 'Block count', out)[0][1])
362 22a6d232 Nikos Skalkotos
363 e8b1b48b Nikos Skalkotos
        start = last_part['part_start'] / sector_size
364 3f70f242 Nikos Skalkotos
        end = start + (block_size * block_cnt) / sector_size - 1
365 8c574358 Nikos Skalkotos
366 e8b1b48b Nikos Skalkotos
        if is_logical(last_part):
367 e8b1b48b Nikos Skalkotos
            partitions = self.g.part_list(self.guestfs_device)
368 e8b1b48b Nikos Skalkotos
369 e8b1b48b Nikos Skalkotos
            logical = []  # logical partitions
370 e8b1b48b Nikos Skalkotos
            for partition in partitions:
371 e8b1b48b Nikos Skalkotos
                if partition['part_num'] < 4:
372 e8b1b48b Nikos Skalkotos
                    continue
373 e8b1b48b Nikos Skalkotos
                logical.append({
374 e8b1b48b Nikos Skalkotos
                    'num': partition['part_num'],
375 e8b1b48b Nikos Skalkotos
                    'start': partition['part_start'] / sector_size,
376 e8b1b48b Nikos Skalkotos
                    'end': partition['part_end'] / sector_size,
377 e8b1b48b Nikos Skalkotos
                    'id': part_get_(partition['part_num']),
378 e8b1b48b Nikos Skalkotos
                    'bootable': part_get_bootable(partition['part_num'])
379 e8b1b48b Nikos Skalkotos
                })
380 e8b1b48b Nikos Skalkotos
381 e8b1b48b Nikos Skalkotos
            logical[-1]['end'] = end  # new end after resize
382 e8b1b48b Nikos Skalkotos
383 e8b1b48b Nikos Skalkotos
            # Recreate the extended partition
384 e8b1b48b Nikos Skalkotos
            extended = [p for p in partitions if self._is_extended(p)][0]
385 e8b1b48b Nikos Skalkotos
            part_del(extended['part_num'])
386 e8b1b48b Nikos Skalkotos
            part_add('e', extended['part_start'], end)
387 e8b1b48b Nikos Skalkotos
388 e8b1b48b Nikos Skalkotos
            # Create all the logical partitions back
389 e8b1b48b Nikos Skalkotos
            for l in logical:
390 e8b1b48b Nikos Skalkotos
                part_add('l', l['start'], l['end'])
391 e8b1b48b Nikos Skalkotos
                part_set_id(l['num'], l['id'])
392 e8b1b48b Nikos Skalkotos
                part_set_bootable(l['num'], l['bootable'])
393 e8b1b48b Nikos Skalkotos
        else:
394 e8b1b48b Nikos Skalkotos
            # Recreate the last partition
395 e8b1b48b Nikos Skalkotos
            if self.meta['PARTITION_TABLE'] == 'msdos':
396 e8b1b48b Nikos Skalkotos
                last_part['id'] = part_get_id(last_part['part_num'])
397 e8b1b48b Nikos Skalkotos
398 e8b1b48b Nikos Skalkotos
            last_part['bootable'] = part_get_bootable(last_part['part_num'])
399 e8b1b48b Nikos Skalkotos
            part_del(last_part['part_num'])
400 e8b1b48b Nikos Skalkotos
            part_add('p', start, end)
401 e8b1b48b Nikos Skalkotos
            part_set_bootable(last_part['part_num'], last_part['bootable'])
402 8c574358 Nikos Skalkotos
403 e8b1b48b Nikos Skalkotos
            if self.meta['PARTITION_TABLE'] == 'msdos':
404 e8b1b48b Nikos Skalkotos
                part_set_id(last_part['part_num'], last_part['id'])
405 331aa0ec Nikos Skalkotos
406 e8b1b48b Nikos Skalkotos
        new_size = (end + 1) * sector_size
407 9666a511 Nikos Skalkotos
408 9666a511 Nikos Skalkotos
        assert (new_size <= self.meta['SIZE'])
409 e8b1b48b Nikos Skalkotos
410 e8b1b48b Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] == 'gpt':
411 e108efd2 Nikos Skalkotos
            ptable = GPTPartitionTable(self.real_device)
412 9666a511 Nikos Skalkotos
            self.meta['SIZE'] = ptable.shrink(new_size, self.meta['SIZE'])
413 e8b1b48b Nikos Skalkotos
        else:
414 9666a511 Nikos Skalkotos
            self.meta['SIZE'] = min(new_size + 2048 * sector_size,
415 9666a511 Nikos Skalkotos
                                    self.meta['SIZE'])
416 9666a511 Nikos Skalkotos
417 9666a511 Nikos Skalkotos
        self.out.success("new size is %dMB" %
418 9666a511 Nikos Skalkotos
                         ((self.meta['SIZE'] + MB - 1) // MB))
419 e3aac3f9 Nikos Skalkotos
420 e8b1b48b Nikos Skalkotos
        return self.meta['SIZE']
421 8c574358 Nikos Skalkotos
422 d603d80d Nikos Skalkotos
    def dump(self, outfile):
423 d603d80d Nikos Skalkotos
        """Dumps the content of device into a file.
424 d603d80d Nikos Skalkotos

425 d603d80d Nikos Skalkotos
        This method will only dump the actual payload, found by reading the
426 d603d80d Nikos Skalkotos
        partition table. Empty space in the end of the device will be ignored.
427 d603d80d Nikos Skalkotos
        """
428 e8b1b48b Nikos Skalkotos
        MB = 2 ** 20
429 e8b1b48b Nikos Skalkotos
        blocksize = 4 * MB  # 4MB
430 e8b1b48b Nikos Skalkotos
        size = self.meta['SIZE']
431 96171db1 Nikos Skalkotos
        progr_size = (size + MB - 1) // MB  # in MB
432 96171db1 Nikos Skalkotos
        progressbar = self.out.Progress(progr_size, "Dumping image file", 'mb')
433 5b801534 Nikos Skalkotos
434 5b801534 Nikos Skalkotos
        with open(self.real_device, 'r') as src:
435 5b801534 Nikos Skalkotos
            with open(outfile, "w") as dst:
436 e8b1b48b Nikos Skalkotos
                left = size
437 d603d80d Nikos Skalkotos
                offset = 0
438 d603d80d Nikos Skalkotos
                progressbar.next()
439 d603d80d Nikos Skalkotos
                while left > 0:
440 d603d80d Nikos Skalkotos
                    length = min(left, blocksize)
441 5b801534 Nikos Skalkotos
                    sent = sendfile(dst.fileno(), src.fileno(), offset, length)
442 d603d80d Nikos Skalkotos
                    offset += sent
443 d603d80d Nikos Skalkotos
                    left -= sent
444 e8b1b48b Nikos Skalkotos
                    progressbar.goto((size - left) // MB)
445 e77e66a9 Nikos Skalkotos
        progressbar.success('image file %s was successfully created' % outfile)
446 d603d80d Nikos Skalkotos
447 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :