Statistics
| Branch: | Tag: | Revision:

root / image_creator / image.py @ bf3a282c

History | View | Annotate | Download (15.4 kB)

1 f5174d2c Nikos Skalkotos
# Copyright 2013 GRNET S.A. All rights reserved.
2 f5174d2c Nikos Skalkotos
#
3 f5174d2c Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
4 f5174d2c Nikos Skalkotos
# without modification, are permitted provided that the following
5 f5174d2c Nikos Skalkotos
# conditions are met:
6 f5174d2c Nikos Skalkotos
#
7 f5174d2c Nikos Skalkotos
#   1. Redistributions of source code must retain the above
8 f5174d2c Nikos Skalkotos
#      copyright notice, this list of conditions and the following
9 f5174d2c Nikos Skalkotos
#      disclaimer.
10 f5174d2c Nikos Skalkotos
#
11 f5174d2c Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
12 f5174d2c Nikos Skalkotos
#      copyright notice, this list of conditions and the following
13 f5174d2c Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
14 f5174d2c Nikos Skalkotos
#      provided with the distribution.
15 f5174d2c Nikos Skalkotos
#
16 f5174d2c Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 f5174d2c Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 f5174d2c Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 f5174d2c Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 f5174d2c Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 f5174d2c Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 f5174d2c Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 f5174d2c Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 f5174d2c Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 f5174d2c Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 f5174d2c Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 f5174d2c Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
28 f5174d2c Nikos Skalkotos
#
29 f5174d2c Nikos Skalkotos
# The views and conclusions contained in the software and
30 f5174d2c Nikos Skalkotos
# documentation are those of the authors and should not be
31 f5174d2c Nikos Skalkotos
# interpreted as representing official policies, either expressed
32 f5174d2c Nikos Skalkotos
# or implied, of GRNET S.A.
33 f5174d2c Nikos Skalkotos
34 f5174d2c Nikos Skalkotos
from image_creator.util import FatalError
35 f5174d2c Nikos Skalkotos
from image_creator.gpt import GPTPartitionTable
36 f5174d2c Nikos Skalkotos
from image_creator.os_type import os_cls
37 f5174d2c Nikos Skalkotos
38 f5174d2c Nikos Skalkotos
import re
39 f5174d2c Nikos Skalkotos
import guestfs
40 f5174d2c Nikos Skalkotos
from sendfile import sendfile
41 f5174d2c Nikos Skalkotos
42 f5174d2c Nikos Skalkotos
43 f5174d2c Nikos Skalkotos
class Image(object):
44 f5174d2c Nikos Skalkotos
    """The instances of this class can create images out of block devices."""
45 f5174d2c Nikos Skalkotos
46 f5174d2c Nikos Skalkotos
    def __init__(self, device, output, bootable=True, meta={}):
47 88f83027 Nikos Skalkotos
        """Create a new Image instance"""
48 f5174d2c Nikos Skalkotos
49 f5174d2c Nikos Skalkotos
        self.device = device
50 f5174d2c Nikos Skalkotos
        self.out = output
51 f5174d2c Nikos Skalkotos
        self.bootable = bootable
52 f5174d2c Nikos Skalkotos
        self.meta = meta
53 f5174d2c Nikos Skalkotos
        self.progress_bar = None
54 f5174d2c Nikos Skalkotos
        self.guestfs_device = None
55 f5174d2c Nikos Skalkotos
        self.size = 0
56 f5174d2c Nikos Skalkotos
        self.mounted = False
57 29fd973e Nikos Skalkotos
        self.mounted_ro = False
58 f5174d2c Nikos Skalkotos
59 f5174d2c Nikos Skalkotos
        self.g = guestfs.GuestFS()
60 f5174d2c Nikos Skalkotos
        self.g.add_drive_opts(self.device, readonly=0, format="raw")
61 f5174d2c Nikos Skalkotos
62 f5174d2c Nikos Skalkotos
        # Before version 1.17.14 the recovery process, which is a fork of the
63 f5174d2c Nikos Skalkotos
        # original process that called libguestfs, did not close its inherited
64 f5174d2c Nikos Skalkotos
        # file descriptors. This can cause problems especially if the parent
65 f5174d2c Nikos Skalkotos
        # process has opened pipes. Since the recovery process is an optional
66 f5174d2c Nikos Skalkotos
        # feature of libguestfs, it's better to disable it.
67 f5174d2c Nikos Skalkotos
        self.g.set_recovery_proc(0)
68 f5174d2c Nikos Skalkotos
        version = self.g.version()
69 f5174d2c Nikos Skalkotos
        if version['major'] > 1 or \
70 f5174d2c Nikos Skalkotos
            (version['major'] == 1 and (version['minor'] >= 18 or
71 f5174d2c Nikos Skalkotos
                                        (version['minor'] == 17 and
72 f5174d2c Nikos Skalkotos
                                         version['release'] >= 14))):
73 f5174d2c Nikos Skalkotos
            self.g.set_recovery_proc(1)
74 f5174d2c Nikos Skalkotos
            self.out.output("Enabling recovery proc")
75 f5174d2c Nikos Skalkotos
76 f5174d2c Nikos Skalkotos
        #self.g.set_trace(1)
77 f5174d2c Nikos Skalkotos
        #self.g.set_verbose(1)
78 f5174d2c Nikos Skalkotos
79 f5174d2c Nikos Skalkotos
        self.guestfs_enabled = False
80 f5174d2c Nikos Skalkotos
81 f5174d2c Nikos Skalkotos
    def enable(self):
82 88f83027 Nikos Skalkotos
        """Enable a newly created Image instance"""
83 f5174d2c Nikos Skalkotos
84 f5174d2c Nikos Skalkotos
        self.out.output('Launching helper VM (may take a while) ...', False)
85 f5174d2c Nikos Skalkotos
        # self.progressbar = self.out.Progress(100, "Launching helper VM",
86 f5174d2c Nikos Skalkotos
        #                                     "percent")
87 f5174d2c Nikos Skalkotos
        # eh = self.g.set_event_callback(self.progress_callback,
88 f5174d2c Nikos Skalkotos
        #                               guestfs.EVENT_PROGRESS)
89 f5174d2c Nikos Skalkotos
        self.g.launch()
90 f5174d2c Nikos Skalkotos
        self.guestfs_enabled = True
91 f5174d2c Nikos Skalkotos
        # self.g.delete_event_callback(eh)
92 f5174d2c Nikos Skalkotos
        # self.progressbar.success('done')
93 f5174d2c Nikos Skalkotos
        # self.progressbar = None
94 f5174d2c Nikos Skalkotos
        self.out.success('done')
95 f5174d2c Nikos Skalkotos
96 f5174d2c Nikos Skalkotos
        self.out.output('Inspecting Operating System ...', False)
97 f5174d2c Nikos Skalkotos
        roots = self.g.inspect_os()
98 f5174d2c Nikos Skalkotos
        if len(roots) == 0:
99 f5174d2c Nikos Skalkotos
            raise FatalError("No operating system found")
100 f5174d2c Nikos Skalkotos
        if len(roots) > 1:
101 f5174d2c Nikos Skalkotos
            raise FatalError("Multiple operating systems found."
102 f5174d2c Nikos Skalkotos
                             "We only support images with one OS.")
103 f5174d2c Nikos Skalkotos
        self.root = roots[0]
104 f5174d2c Nikos Skalkotos
        self.guestfs_device = self.g.part_to_dev(self.root)
105 f5174d2c Nikos Skalkotos
        self.size = self.g.blockdev_getsize64(self.guestfs_device)
106 f5174d2c Nikos Skalkotos
        self.meta['PARTITION_TABLE'] = \
107 f5174d2c Nikos Skalkotos
            self.g.part_get_parttype(self.guestfs_device)
108 f5174d2c Nikos Skalkotos
109 f5174d2c Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
110 f5174d2c Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
111 80411610 Nikos Skalkotos
        self.out.success(
112 80411610 Nikos Skalkotos
            'found a(n) %s system' %
113 80411610 Nikos Skalkotos
            self.ostype if self.distro == "unknown" else self.distro)
114 f5174d2c Nikos Skalkotos
115 f5174d2c Nikos Skalkotos
    def _get_os(self):
116 88f83027 Nikos Skalkotos
        """Return an OS class instance for this image"""
117 f5174d2c Nikos Skalkotos
        if hasattr(self, "_os"):
118 f5174d2c Nikos Skalkotos
            return self._os
119 f5174d2c Nikos Skalkotos
120 f5174d2c Nikos Skalkotos
        if not self.guestfs_enabled:
121 f5174d2c Nikos Skalkotos
            self.enable()
122 f5174d2c Nikos Skalkotos
123 f5174d2c Nikos Skalkotos
        if not self.mounted:
124 f5174d2c Nikos Skalkotos
            do_unmount = True
125 f5174d2c Nikos Skalkotos
            self.mount(readonly=True)
126 f5174d2c Nikos Skalkotos
        else:
127 f5174d2c Nikos Skalkotos
            do_unmount = False
128 f5174d2c Nikos Skalkotos
129 f5174d2c Nikos Skalkotos
        try:
130 f5174d2c Nikos Skalkotos
            cls = os_cls(self.distro, self.ostype)
131 f5174d2c Nikos Skalkotos
            self._os = cls(self.root, self.g, self.out)
132 f5174d2c Nikos Skalkotos
133 f5174d2c Nikos Skalkotos
        finally:
134 f5174d2c Nikos Skalkotos
            if do_unmount:
135 f5174d2c Nikos Skalkotos
                self.umount()
136 f5174d2c Nikos Skalkotos
137 f5174d2c Nikos Skalkotos
        return self._os
138 f5174d2c Nikos Skalkotos
139 f5174d2c Nikos Skalkotos
    os = property(_get_os)
140 f5174d2c Nikos Skalkotos
141 f5174d2c Nikos Skalkotos
    def destroy(self):
142 88f83027 Nikos Skalkotos
        """Destroy this Image instance."""
143 f5174d2c Nikos Skalkotos
144 f5174d2c Nikos Skalkotos
        # In new guestfs versions, there is a handy shutdown method for this
145 f5174d2c Nikos Skalkotos
        try:
146 f5174d2c Nikos Skalkotos
            if self.guestfs_enabled:
147 f5174d2c Nikos Skalkotos
                self.g.umount_all()
148 f5174d2c Nikos Skalkotos
                self.g.sync()
149 f5174d2c Nikos Skalkotos
        finally:
150 f5174d2c Nikos Skalkotos
            # Close the guestfs handler if open
151 f5174d2c Nikos Skalkotos
            self.g.close()
152 f5174d2c Nikos Skalkotos
153 f5174d2c Nikos Skalkotos
#    def progress_callback(self, ev, eh, buf, array):
154 f5174d2c Nikos Skalkotos
#        position = array[2]
155 f5174d2c Nikos Skalkotos
#        total = array[3]
156 f5174d2c Nikos Skalkotos
#
157 f5174d2c Nikos Skalkotos
#        self.progressbar.goto((position * 100) // total)
158 f5174d2c Nikos Skalkotos
159 f5174d2c Nikos Skalkotos
    def mount(self, readonly=False):
160 f5174d2c Nikos Skalkotos
        """Mount all disk partitions in a correct order."""
161 f5174d2c Nikos Skalkotos
162 29fd973e Nikos Skalkotos
        msg = "Mounting the media%s ..." % (" read-only" if readonly else "")
163 29fd973e Nikos Skalkotos
        self.out.output(msg, False)
164 29fd973e Nikos Skalkotos
165 29fd973e Nikos Skalkotos
        #If something goes wrong when mounting rw, remount the filesystem ro
166 29fd973e Nikos Skalkotos
        remount_ro = False
167 29fd973e Nikos Skalkotos
        rw_mpoints = ('/', '/etc', '/root', '/home', '/var')
168 f5174d2c Nikos Skalkotos
169 f5174d2c Nikos Skalkotos
        # Sort the keys to mount the fs in a correct order.
170 f5174d2c Nikos Skalkotos
        # / should be mounted befor /boot, etc
171 f5174d2c Nikos Skalkotos
        def compare(a, b):
172 f5174d2c Nikos Skalkotos
            if len(a[0]) > len(b[0]):
173 f5174d2c Nikos Skalkotos
                return 1
174 f5174d2c Nikos Skalkotos
            elif len(a[0]) == len(b[0]):
175 f5174d2c Nikos Skalkotos
                return 0
176 f5174d2c Nikos Skalkotos
            else:
177 f5174d2c Nikos Skalkotos
                return -1
178 29fd973e Nikos Skalkotos
        mps = self.g.inspect_get_mountpoints(self.root)
179 f5174d2c Nikos Skalkotos
        mps.sort(compare)
180 29fd973e Nikos Skalkotos
181 29fd973e Nikos Skalkotos
        mopts = 'ro' if readonly else 'rw'
182 f5174d2c Nikos Skalkotos
        for mp, dev in mps:
183 29fd973e Nikos Skalkotos
            if self.ostype == 'freebsd':
184 29fd973e Nikos Skalkotos
                # libguestfs can't handle correct freebsd partitions on GUID
185 29fd973e Nikos Skalkotos
                # Partition Table. We have to do the translation to linux
186 29fd973e Nikos Skalkotos
                # device names ourselves
187 29fd973e Nikos Skalkotos
                m = re.match('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$', dev)
188 29fd973e Nikos Skalkotos
                if m:
189 29fd973e Nikos Skalkotos
                    m2 = int(m.group(2))
190 29fd973e Nikos Skalkotos
                    m3 = int(m.group(3))
191 29fd973e Nikos Skalkotos
                    dev = '/dev/sd%c%d' % (chr(ord('a') + m2), m3)
192 f5174d2c Nikos Skalkotos
            try:
193 29fd973e Nikos Skalkotos
                self.g.mount_options(mopts, dev, mp)
194 f5174d2c Nikos Skalkotos
            except RuntimeError as msg:
195 29fd973e Nikos Skalkotos
                if self.ostype == 'freebsd':
196 29fd973e Nikos Skalkotos
                    freebsd_mopts = "ufstype=ufs2,%s" % mopts
197 29fd973e Nikos Skalkotos
                    try:
198 29fd973e Nikos Skalkotos
                        self.g.mount_vfs(freebsd_mopts, 'ufs', dev, mp)
199 29fd973e Nikos Skalkotos
                    except RuntimeError as msg:
200 29fd973e Nikos Skalkotos
                        if readonly is False and mp in rw_mpoints:
201 29fd973e Nikos Skalkotos
                            remount_ro = True
202 29fd973e Nikos Skalkotos
                            break
203 29fd973e Nikos Skalkotos
                elif readonly is False and mp in rw_mpoints:
204 29fd973e Nikos Skalkotos
                    remount_ro = True
205 29fd973e Nikos Skalkotos
                    break
206 29fd973e Nikos Skalkotos
                else:
207 29fd973e Nikos Skalkotos
                    self.out.warn("%s (ignored)" % msg)
208 29fd973e Nikos Skalkotos
        if remount_ro:
209 29fd973e Nikos Skalkotos
            self.out.warn("Unable to mount %s read-write. "
210 29fd973e Nikos Skalkotos
                          "Remounting everything read-only..." % mp)
211 29fd973e Nikos Skalkotos
            self.umount()
212 29fd973e Nikos Skalkotos
            self.mount(True)
213 29fd973e Nikos Skalkotos
        else:
214 29fd973e Nikos Skalkotos
            self.mounted = True
215 29fd973e Nikos Skalkotos
            self.mounted_ro = readonly
216 29fd973e Nikos Skalkotos
            self.out.success("done")
217 f5174d2c Nikos Skalkotos
218 f5174d2c Nikos Skalkotos
    def umount(self):
219 f5174d2c Nikos Skalkotos
        """Umount all mounted filesystems."""
220 f5174d2c Nikos Skalkotos
        self.g.umount_all()
221 f5174d2c Nikos Skalkotos
        self.mounted = False
222 f5174d2c Nikos Skalkotos
223 f5174d2c Nikos Skalkotos
    def _last_partition(self):
224 88f83027 Nikos Skalkotos
        """Return the last partition of the image disk"""
225 f5174d2c Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
226 f5174d2c Nikos Skalkotos
            msg = "Unsupported partition table: %s. Only msdos and gpt " \
227 f5174d2c Nikos Skalkotos
                "partition tables are supported" % self.meta['PARTITION_TABLE']
228 f5174d2c Nikos Skalkotos
            raise FatalError(msg)
229 f5174d2c Nikos Skalkotos
230 f5174d2c Nikos Skalkotos
        is_extended = lambda p: \
231 f5174d2c Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
232 f5174d2c Nikos Skalkotos
            in (0x5, 0xf)
233 f5174d2c Nikos Skalkotos
        is_logical = lambda p: \
234 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
235 f5174d2c Nikos Skalkotos
236 f5174d2c Nikos Skalkotos
        partitions = self.g.part_list(self.guestfs_device)
237 f5174d2c Nikos Skalkotos
        last_partition = partitions[-1]
238 f5174d2c Nikos Skalkotos
239 f5174d2c Nikos Skalkotos
        if is_logical(last_partition):
240 f5174d2c Nikos Skalkotos
            # The disk contains extended and logical partitions....
241 f5174d2c Nikos Skalkotos
            extended = filter(is_extended, partitions)[0]
242 f5174d2c Nikos Skalkotos
            last_primary = [p for p in partitions if p['part_num'] <= 4][-1]
243 f5174d2c Nikos Skalkotos
244 f5174d2c Nikos Skalkotos
            # check if extended is the last primary partition
245 f5174d2c Nikos Skalkotos
            if last_primary['part_num'] > extended['part_num']:
246 f5174d2c Nikos Skalkotos
                last_partition = last_primary
247 f5174d2c Nikos Skalkotos
248 f5174d2c Nikos Skalkotos
        return last_partition
249 f5174d2c Nikos Skalkotos
250 f5174d2c Nikos Skalkotos
    def shrink(self):
251 88f83027 Nikos Skalkotos
        """Shrink the image.
252 f5174d2c Nikos Skalkotos

253 88f83027 Nikos Skalkotos
        This is accomplished by shrinking the last file system of the
254 88f83027 Nikos Skalkotos
        image and then updating the partition table. The new disk size
255 f5174d2c Nikos Skalkotos
        (in bytes) is returned.
256 f5174d2c Nikos Skalkotos

257 f5174d2c Nikos Skalkotos
        ATTENTION: make sure unmount is called before shrink
258 f5174d2c Nikos Skalkotos
        """
259 f5174d2c Nikos Skalkotos
        get_fstype = lambda p: \
260 f5174d2c Nikos Skalkotos
            self.g.vfs_type("%s%d" % (self.guestfs_device, p['part_num']))
261 f5174d2c Nikos Skalkotos
        is_logical = lambda p: \
262 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
263 f5174d2c Nikos Skalkotos
        is_extended = lambda p: \
264 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and \
265 f5174d2c Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
266 f5174d2c Nikos Skalkotos
            in (0x5, 0xf)
267 f5174d2c Nikos Skalkotos
268 f5174d2c Nikos Skalkotos
        part_add = lambda ptype, start, stop: \
269 f5174d2c Nikos Skalkotos
            self.g.part_add(self.guestfs_device, ptype, start, stop)
270 f5174d2c Nikos Skalkotos
        part_del = lambda p: self.g.part_del(self.guestfs_device, p)
271 f5174d2c Nikos Skalkotos
        part_get_id = lambda p: self.g.part_get_mbr_id(self.guestfs_device, p)
272 f5174d2c Nikos Skalkotos
        part_set_id = lambda p, id: \
273 f5174d2c Nikos Skalkotos
            self.g.part_set_mbr_id(self.guestfs_device, p, id)
274 f5174d2c Nikos Skalkotos
        part_get_bootable = lambda p: \
275 f5174d2c Nikos Skalkotos
            self.g.part_get_bootable(self.guestfs_device, p)
276 f5174d2c Nikos Skalkotos
        part_set_bootable = lambda p, bootable: \
277 f5174d2c Nikos Skalkotos
            self.g.part_set_bootable(self.guestfs_device, p, bootable)
278 f5174d2c Nikos Skalkotos
279 f5174d2c Nikos Skalkotos
        MB = 2 ** 20
280 f5174d2c Nikos Skalkotos
281 f5174d2c Nikos Skalkotos
        self.out.output("Shrinking image (this may take a while) ...", False)
282 f5174d2c Nikos Skalkotos
283 f5174d2c Nikos Skalkotos
        sector_size = self.g.blockdev_getss(self.guestfs_device)
284 f5174d2c Nikos Skalkotos
285 f5174d2c Nikos Skalkotos
        last_part = None
286 f5174d2c Nikos Skalkotos
        fstype = None
287 f5174d2c Nikos Skalkotos
        while True:
288 f5174d2c Nikos Skalkotos
            last_part = self._last_partition()
289 f5174d2c Nikos Skalkotos
            fstype = get_fstype(last_part)
290 f5174d2c Nikos Skalkotos
291 f5174d2c Nikos Skalkotos
            if fstype == 'swap':
292 f5174d2c Nikos Skalkotos
                self.meta['SWAP'] = "%d:%s" % \
293 f5174d2c Nikos Skalkotos
                    (last_part['part_num'],
294 f5174d2c Nikos Skalkotos
                     (last_part['part_size'] + MB - 1) // MB)
295 f5174d2c Nikos Skalkotos
                part_del(last_part['part_num'])
296 f5174d2c Nikos Skalkotos
                continue
297 f5174d2c Nikos Skalkotos
            elif is_extended(last_part):
298 f5174d2c Nikos Skalkotos
                part_del(last_part['part_num'])
299 f5174d2c Nikos Skalkotos
                continue
300 f5174d2c Nikos Skalkotos
301 f5174d2c Nikos Skalkotos
            # Most disk manipulation programs leave 2048 sectors after the last
302 f5174d2c Nikos Skalkotos
            # partition
303 f5174d2c Nikos Skalkotos
            new_size = last_part['part_end'] + 1 + 2048 * sector_size
304 f5174d2c Nikos Skalkotos
            self.size = min(self.size, new_size)
305 f5174d2c Nikos Skalkotos
            break
306 f5174d2c Nikos Skalkotos
307 f5174d2c Nikos Skalkotos
        if not re.match("ext[234]", fstype):
308 c71133ce Nikos Skalkotos
            self.out.warn("Don't know how to shrink %s partitions." % fstype)
309 f5174d2c Nikos Skalkotos
            return self.size
310 f5174d2c Nikos Skalkotos
311 f5174d2c Nikos Skalkotos
        part_dev = "%s%d" % (self.guestfs_device, last_part['part_num'])
312 f5174d2c Nikos Skalkotos
        self.g.e2fsck_f(part_dev)
313 f5174d2c Nikos Skalkotos
        self.g.resize2fs_M(part_dev)
314 f5174d2c Nikos Skalkotos
315 f5174d2c Nikos Skalkotos
        out = self.g.tune2fs_l(part_dev)
316 f5174d2c Nikos Skalkotos
        block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1])
317 f5174d2c Nikos Skalkotos
        block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1])
318 f5174d2c Nikos Skalkotos
319 f5174d2c Nikos Skalkotos
        start = last_part['part_start'] / sector_size
320 f5174d2c Nikos Skalkotos
        end = start + (block_size * block_cnt) / sector_size - 1
321 f5174d2c Nikos Skalkotos
322 f5174d2c Nikos Skalkotos
        if is_logical(last_part):
323 f5174d2c Nikos Skalkotos
            partitions = self.g.part_list(self.guestfs_device)
324 f5174d2c Nikos Skalkotos
325 f5174d2c Nikos Skalkotos
            logical = []  # logical partitions
326 f5174d2c Nikos Skalkotos
            for partition in partitions:
327 f5174d2c Nikos Skalkotos
                if partition['part_num'] < 4:
328 f5174d2c Nikos Skalkotos
                    continue
329 f5174d2c Nikos Skalkotos
                logical.append({
330 f5174d2c Nikos Skalkotos
                    'num': partition['part_num'],
331 f5174d2c Nikos Skalkotos
                    'start': partition['part_start'] / sector_size,
332 f5174d2c Nikos Skalkotos
                    'end': partition['part_end'] / sector_size,
333 f5174d2c Nikos Skalkotos
                    'id': part_get_id(partition['part_num']),
334 f5174d2c Nikos Skalkotos
                    'bootable': part_get_bootable(partition['part_num'])
335 f5174d2c Nikos Skalkotos
                })
336 f5174d2c Nikos Skalkotos
337 f5174d2c Nikos Skalkotos
            logical[-1]['end'] = end  # new end after resize
338 f5174d2c Nikos Skalkotos
339 f5174d2c Nikos Skalkotos
            # Recreate the extended partition
340 f5174d2c Nikos Skalkotos
            extended = filter(is_extended, partitions)[0]
341 f5174d2c Nikos Skalkotos
            part_del(extended['part_num'])
342 f5174d2c Nikos Skalkotos
            part_add('e', extended['part_start'] / sector_size, end)
343 f5174d2c Nikos Skalkotos
344 f5174d2c Nikos Skalkotos
            # Create all the logical partitions back
345 f5174d2c Nikos Skalkotos
            for l in logical:
346 f5174d2c Nikos Skalkotos
                part_add('l', l['start'], l['end'])
347 f5174d2c Nikos Skalkotos
                part_set_id(l['num'], l['id'])
348 f5174d2c Nikos Skalkotos
                part_set_bootable(l['num'], l['bootable'])
349 f5174d2c Nikos Skalkotos
        else:
350 f5174d2c Nikos Skalkotos
            # Recreate the last partition
351 f5174d2c Nikos Skalkotos
            if self.meta['PARTITION_TABLE'] == 'msdos':
352 f5174d2c Nikos Skalkotos
                last_part['id'] = part_get_id(last_part['part_num'])
353 f5174d2c Nikos Skalkotos
354 f5174d2c Nikos Skalkotos
            last_part['bootable'] = part_get_bootable(last_part['part_num'])
355 f5174d2c Nikos Skalkotos
            part_del(last_part['part_num'])
356 f5174d2c Nikos Skalkotos
            part_add('p', start, end)
357 f5174d2c Nikos Skalkotos
            part_set_bootable(last_part['part_num'], last_part['bootable'])
358 f5174d2c Nikos Skalkotos
359 f5174d2c Nikos Skalkotos
            if self.meta['PARTITION_TABLE'] == 'msdos':
360 f5174d2c Nikos Skalkotos
                part_set_id(last_part['part_num'], last_part['id'])
361 f5174d2c Nikos Skalkotos
362 f5174d2c Nikos Skalkotos
        new_size = (end + 1) * sector_size
363 f5174d2c Nikos Skalkotos
364 f5174d2c Nikos Skalkotos
        assert (new_size <= self.size)
365 f5174d2c Nikos Skalkotos
366 f5174d2c Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] == 'gpt':
367 f5174d2c Nikos Skalkotos
            ptable = GPTPartitionTable(self.device)
368 f5174d2c Nikos Skalkotos
            self.size = ptable.shrink(new_size, self.size)
369 f5174d2c Nikos Skalkotos
        else:
370 f5174d2c Nikos Skalkotos
            self.size = min(new_size + 2048 * sector_size, self.size)
371 f5174d2c Nikos Skalkotos
372 f5174d2c Nikos Skalkotos
        self.out.success("new size is %dMB" % ((self.size + MB - 1) // MB))
373 f5174d2c Nikos Skalkotos
374 f5174d2c Nikos Skalkotos
        return self.size
375 f5174d2c Nikos Skalkotos
376 f5174d2c Nikos Skalkotos
    def dump(self, outfile):
377 88f83027 Nikos Skalkotos
        """Dumps the content of the image into a file.
378 f5174d2c Nikos Skalkotos

379 f5174d2c Nikos Skalkotos
        This method will only dump the actual payload, found by reading the
380 f5174d2c Nikos Skalkotos
        partition table. Empty space in the end of the device will be ignored.
381 f5174d2c Nikos Skalkotos
        """
382 f5174d2c Nikos Skalkotos
        MB = 2 ** 20
383 f5174d2c Nikos Skalkotos
        blocksize = 4 * MB  # 4MB
384 f5174d2c Nikos Skalkotos
        size = self.size
385 f5174d2c Nikos Skalkotos
        progr_size = (size + MB - 1) // MB  # in MB
386 f5174d2c Nikos Skalkotos
        progressbar = self.out.Progress(progr_size, "Dumping image file", 'mb')
387 f5174d2c Nikos Skalkotos
388 f5174d2c Nikos Skalkotos
        with open(self.device, 'r') as src:
389 f5174d2c Nikos Skalkotos
            with open(outfile, "w") as dst:
390 f5174d2c Nikos Skalkotos
                left = size
391 f5174d2c Nikos Skalkotos
                offset = 0
392 f5174d2c Nikos Skalkotos
                progressbar.next()
393 f5174d2c Nikos Skalkotos
                while left > 0:
394 f5174d2c Nikos Skalkotos
                    length = min(left, blocksize)
395 f5174d2c Nikos Skalkotos
                    sent = sendfile(dst.fileno(), src.fileno(), offset, length)
396 f5174d2c Nikos Skalkotos
397 f5174d2c Nikos Skalkotos
                    # Workaround for python-sendfile API change. In
398 f5174d2c Nikos Skalkotos
                    # python-sendfile 1.2.x (py-sendfile) the returning value
399 f5174d2c Nikos Skalkotos
                    # of sendfile is a tuple, where in version 2.x (pysendfile)
400 f5174d2c Nikos Skalkotos
                    # it is just a sigle integer.
401 f5174d2c Nikos Skalkotos
                    if isinstance(sent, tuple):
402 f5174d2c Nikos Skalkotos
                        sent = sent[1]
403 f5174d2c Nikos Skalkotos
404 f5174d2c Nikos Skalkotos
                    offset += sent
405 f5174d2c Nikos Skalkotos
                    left -= sent
406 f5174d2c Nikos Skalkotos
                    progressbar.goto((size - left) // MB)
407 f5174d2c Nikos Skalkotos
        progressbar.success('image file %s was successfully created' % outfile)
408 f5174d2c Nikos Skalkotos
409 f5174d2c Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :