Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ dbf466eb

History | View | Annotate | Download (16.4 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 dbf466eb Nikos Skalkotos
        self.size = 0
172 e8b1b48b Nikos Skalkotos
        self.meta = {}
173 1377b8a7 Nikos Skalkotos
174 1377b8a7 Nikos Skalkotos
        self.g = guestfs.GuestFS()
175 e108efd2 Nikos Skalkotos
        self.g.add_drive_opts(self.real_device, readonly=0)
176 0d5a999d Nikos Skalkotos
177 5f27b178 Nikos Skalkotos
        # Before version 1.17.14 the recovery process, which is a fork of the
178 5f27b178 Nikos Skalkotos
        # original process that called libguestfs, did not close its inherited
179 5f27b178 Nikos Skalkotos
        # file descriptors. This can cause problems especially if the parent
180 5f27b178 Nikos Skalkotos
        # process has opened pipes. Since the recovery process is an optional
181 5f27b178 Nikos Skalkotos
        # feature of libguestfs, it's better to disable it.
182 5f27b178 Nikos Skalkotos
        self.g.set_recovery_proc(0)
183 5f27b178 Nikos Skalkotos
        version = self.g.version()
184 f99fe99d Nikos Skalkotos
        if version['major'] > 1 or \
185 f99fe99d Nikos Skalkotos
            (version['major'] == 1 and (version['minor'] >= 18 or
186 f99fe99d Nikos Skalkotos
                                        (version['minor'] == 17 and
187 f99fe99d Nikos Skalkotos
                                         version['release'] >= 14))):
188 5f27b178 Nikos Skalkotos
            self.g.set_recovery_proc(1)
189 5f27b178 Nikos Skalkotos
            self.out.output("Enabling recovery proc")
190 5f27b178 Nikos Skalkotos
191 586da0a0 Nikos Skalkotos
        #self.g.set_trace(1)
192 586da0a0 Nikos Skalkotos
        #self.g.set_verbose(1)
193 0d5a999d Nikos Skalkotos
194 0db22eac Nikos Skalkotos
        self.guestfs_enabled = False
195 ae48a082 Nikos Skalkotos
196 0db22eac Nikos Skalkotos
    def enable(self):
197 0db22eac Nikos Skalkotos
        """Enable a newly created DiskDevice"""
198 96171db1 Nikos Skalkotos
        self.progressbar = self.out.Progress(100, "Launching helper VM",
199 96171db1 Nikos Skalkotos
                                             "percent")
200 3f70f242 Nikos Skalkotos
        eh = self.g.set_event_callback(self.progress_callback,
201 f99fe99d Nikos Skalkotos
                                       guestfs.EVENT_PROGRESS)
202 3f70f242 Nikos Skalkotos
        self.g.launch()
203 3f70f242 Nikos Skalkotos
        self.guestfs_enabled = True
204 3f70f242 Nikos Skalkotos
        self.g.delete_event_callback(eh)
205 e77e66a9 Nikos Skalkotos
        self.progressbar.success('done')
206 e77e66a9 Nikos Skalkotos
        self.progressbar = None
207 3f70f242 Nikos Skalkotos
208 e77e66a9 Nikos Skalkotos
        self.out.output('Inspecting Operating System...', False)
209 3f70f242 Nikos Skalkotos
        roots = self.g.inspect_os()
210 3f70f242 Nikos Skalkotos
        if len(roots) == 0:
211 3f70f242 Nikos Skalkotos
            raise FatalError("No operating system found")
212 3f70f242 Nikos Skalkotos
        if len(roots) > 1:
213 3f70f242 Nikos Skalkotos
            raise FatalError("Multiple operating systems found."
214 835171dc Nikos Skalkotos
                             "We only support images with one OS.")
215 3f70f242 Nikos Skalkotos
        self.root = roots[0]
216 e108efd2 Nikos Skalkotos
        self.guestfs_device = self.g.part_to_dev(self.root)
217 dbf466eb Nikos Skalkotos
        self.size = self.g.blockdev_getsize64(self.guestfs_device)
218 e8b1b48b Nikos Skalkotos
        self.meta['PARTITION_TABLE'] = \
219 f99fe99d Nikos Skalkotos
            self.g.part_get_parttype(self.guestfs_device)
220 331aa0ec Nikos Skalkotos
221 3f70f242 Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
222 3f70f242 Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
223 e77e66a9 Nikos Skalkotos
        self.out.success('found a(n) %s system' % self.distro)
224 8c574358 Nikos Skalkotos
225 1377b8a7 Nikos Skalkotos
    def destroy(self):
226 3b2f6619 Nikos Skalkotos
        """Destroy this DiskDevice instance."""
227 0db22eac Nikos Skalkotos
228 0db22eac Nikos Skalkotos
        if self.guestfs_enabled:
229 0db22eac Nikos Skalkotos
            self.g.umount_all()
230 0db22eac Nikos Skalkotos
            self.g.sync()
231 0db22eac Nikos Skalkotos
232 0db22eac Nikos Skalkotos
        # Close the guestfs handler if open
233 aa2062ba Nikos Skalkotos
        self.g.close()
234 8c574358 Nikos Skalkotos
235 586da0a0 Nikos Skalkotos
    def progress_callback(self, ev, eh, buf, array):
236 586da0a0 Nikos Skalkotos
        position = array[2]
237 586da0a0 Nikos Skalkotos
        total = array[3]
238 586da0a0 Nikos Skalkotos
239 b1395967 Nikos Skalkotos
        self.progressbar.goto((position * 100) // total)
240 586da0a0 Nikos Skalkotos
241 550d4a49 Nikos Skalkotos
    def mount(self, readonly=False):
242 3b2f6619 Nikos Skalkotos
        """Mount all disk partitions in a correct order."""
243 979096dd Nikos Skalkotos
244 550d4a49 Nikos Skalkotos
        mount = self.g.mount_ro if readonly else self.g.mount
245 e77e66a9 Nikos Skalkotos
        self.out.output("Mounting image...", False)
246 0d5a999d Nikos Skalkotos
        mps = self.g.inspect_get_mountpoints(self.root)
247 8c574358 Nikos Skalkotos
248 1377b8a7 Nikos Skalkotos
        # Sort the keys to mount the fs in a correct order.
249 1377b8a7 Nikos Skalkotos
        # / should be mounted befor /boot, etc
250 8c574358 Nikos Skalkotos
        def compare(a, b):
251 8c574358 Nikos Skalkotos
            if len(a[0]) > len(b[0]):
252 8c574358 Nikos Skalkotos
                return 1
253 8c574358 Nikos Skalkotos
            elif len(a[0]) == len(b[0]):
254 8c574358 Nikos Skalkotos
                return 0
255 8c574358 Nikos Skalkotos
            else:
256 8c574358 Nikos Skalkotos
                return -1
257 1377b8a7 Nikos Skalkotos
        mps.sort(compare)
258 1377b8a7 Nikos Skalkotos
        for mp, dev in mps:
259 1377b8a7 Nikos Skalkotos
            try:
260 550d4a49 Nikos Skalkotos
                mount(dev, mp)
261 1377b8a7 Nikos Skalkotos
            except RuntimeError as msg:
262 e77e66a9 Nikos Skalkotos
                self.out.warn("%s (ignored)" % msg)
263 e77e66a9 Nikos Skalkotos
        self.out.success("done")
264 d57775d4 Nikos Skalkotos
265 8c574358 Nikos Skalkotos
    def umount(self):
266 3b2f6619 Nikos Skalkotos
        """Umount all mounted filesystems."""
267 8c574358 Nikos Skalkotos
        self.g.umount_all()
268 8c574358 Nikos Skalkotos
269 e8b1b48b Nikos Skalkotos
    def _last_partition(self):
270 e8b1b48b Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
271 e8b1b48b Nikos Skalkotos
            msg = "Unsupported partition table: %s. Only msdos and gpt " \
272 f99fe99d Nikos Skalkotos
                "partition tables are supported" % self.meta['PARTITION_TABLE']
273 e8b1b48b Nikos Skalkotos
            raise FatalError(msg)
274 e8b1b48b Nikos Skalkotos
275 f99fe99d Nikos Skalkotos
        is_extended = lambda p: \
276 f99fe99d Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5
277 f99fe99d Nikos Skalkotos
        is_logical = lambda p: \
278 f99fe99d Nikos Skalkotos
            self.meta['PARTITION_TABLE'] != 'msdos' and p['part_num'] > 4
279 e8b1b48b Nikos Skalkotos
280 e8b1b48b Nikos Skalkotos
        partitions = self.g.part_list(self.guestfs_device)
281 e8b1b48b Nikos Skalkotos
        last_partition = partitions[-1]
282 e8b1b48b Nikos Skalkotos
283 e8b1b48b Nikos Skalkotos
        if is_logical(last_partition):
284 e8b1b48b Nikos Skalkotos
            # The disk contains extended and logical partitions....
285 e8b1b48b Nikos Skalkotos
            extended = [p for p in partitions if is_extended(p)][0]
286 e8b1b48b Nikos Skalkotos
            last_primary = [p for p in partitions if p['part_num'] <= 4][-1]
287 e8b1b48b Nikos Skalkotos
288 e8b1b48b Nikos Skalkotos
            # check if extended is the last primary partition
289 e8b1b48b Nikos Skalkotos
            if last_primary['part_num'] > extended['part_num']:
290 e8b1b48b Nikos Skalkotos
                last_partition = last_primary
291 e8b1b48b Nikos Skalkotos
292 e8b1b48b Nikos Skalkotos
        return last_partition
293 e8b1b48b Nikos Skalkotos
294 8c574358 Nikos Skalkotos
    def shrink(self):
295 3b2f6619 Nikos Skalkotos
        """Shrink the disk.
296 3b2f6619 Nikos Skalkotos

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

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

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