Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ 25b4d858

History | View | Annotate | Download (16.9 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 a939e3b8 Nikos Skalkotos
from image_creator.bundle_volume import BundleVolume
38 8eea5572 Nikos Skalkotos
39 d57775d4 Nikos Skalkotos
import stat
40 d57775d4 Nikos Skalkotos
import os
41 d57775d4 Nikos Skalkotos
import tempfile
42 d57775d4 Nikos Skalkotos
import uuid
43 d57775d4 Nikos Skalkotos
import re
44 1377b8a7 Nikos Skalkotos
import sys
45 1377b8a7 Nikos Skalkotos
import guestfs
46 0db22eac Nikos Skalkotos
import time
47 d603d80d Nikos Skalkotos
from sendfile import sendfile
48 1377b8a7 Nikos Skalkotos
49 8c574358 Nikos Skalkotos
50 3ccb2618 Nikos Skalkotos
dd = get_command('dd')
51 3ccb2618 Nikos Skalkotos
dmsetup = get_command('dmsetup')
52 3ccb2618 Nikos Skalkotos
losetup = get_command('losetup')
53 3ccb2618 Nikos Skalkotos
blockdev = get_command('blockdev')
54 01a7cff3 Nikos Skalkotos
55 01a7cff3 Nikos Skalkotos
56 d57775d4 Nikos Skalkotos
class Disk(object):
57 3b2f6619 Nikos Skalkotos
    """This class represents a hard disk hosting an Operating System
58 3b2f6619 Nikos Skalkotos

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

308 3b2f6619 Nikos Skalkotos
        This is accomplished by shrinking the last filesystem in the
309 3b2f6619 Nikos Skalkotos
        disk and then updating the partition table. The new disk size
310 3b2f6619 Nikos Skalkotos
        (in bytes) is returned.
311 e108efd2 Nikos Skalkotos

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

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