Statistics
| Branch: | Tag: | Revision:

root / image_creator / image.py @ f953c647

History | View | Annotate | Download (15.5 kB)

1 121f3bc0 Nikos Skalkotos
# -*- coding: utf-8 -*-
2 121f3bc0 Nikos Skalkotos
#
3 f5174d2c Nikos Skalkotos
# Copyright 2013 GRNET S.A. All rights reserved.
4 f5174d2c Nikos Skalkotos
#
5 f5174d2c Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
6 f5174d2c Nikos Skalkotos
# without modification, are permitted provided that the following
7 f5174d2c Nikos Skalkotos
# conditions are met:
8 f5174d2c Nikos Skalkotos
#
9 f5174d2c Nikos Skalkotos
#   1. Redistributions of source code must retain the above
10 f5174d2c Nikos Skalkotos
#      copyright notice, this list of conditions and the following
11 f5174d2c Nikos Skalkotos
#      disclaimer.
12 f5174d2c Nikos Skalkotos
#
13 f5174d2c Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
14 f5174d2c Nikos Skalkotos
#      copyright notice, this list of conditions and the following
15 f5174d2c Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
16 f5174d2c Nikos Skalkotos
#      provided with the distribution.
17 f5174d2c Nikos Skalkotos
#
18 f5174d2c Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 f5174d2c Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 f5174d2c Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 f5174d2c Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 f5174d2c Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 f5174d2c Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 f5174d2c Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 f5174d2c Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 f5174d2c Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 f5174d2c Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 f5174d2c Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 f5174d2c Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
30 f5174d2c Nikos Skalkotos
#
31 f5174d2c Nikos Skalkotos
# The views and conclusions contained in the software and
32 f5174d2c Nikos Skalkotos
# documentation are those of the authors and should not be
33 f5174d2c Nikos Skalkotos
# interpreted as representing official policies, either expressed
34 f5174d2c Nikos Skalkotos
# or implied, of GRNET S.A.
35 f5174d2c Nikos Skalkotos
36 5756c277 Nikos Skalkotos
from image_creator.util import FatalError
37 f5174d2c Nikos Skalkotos
from image_creator.gpt import GPTPartitionTable
38 f5174d2c Nikos Skalkotos
from image_creator.os_type import os_cls
39 f5174d2c Nikos Skalkotos
40 f5174d2c Nikos Skalkotos
import re
41 f5174d2c Nikos Skalkotos
import guestfs
42 f5174d2c Nikos Skalkotos
from sendfile import sendfile
43 f5174d2c Nikos Skalkotos
44 f5174d2c Nikos Skalkotos
45 f5174d2c Nikos Skalkotos
class Image(object):
46 f5174d2c Nikos Skalkotos
    """The instances of this class can create images out of block devices."""
47 f5174d2c Nikos Skalkotos
48 0eac0256 Nikos Skalkotos
    def __init__(self, device, output, **kargs):
49 88f83027 Nikos Skalkotos
        """Create a new Image instance"""
50 f5174d2c Nikos Skalkotos
51 f5174d2c Nikos Skalkotos
        self.device = device
52 f5174d2c Nikos Skalkotos
        self.out = output
53 0eac0256 Nikos Skalkotos
54 0eac0256 Nikos Skalkotos
        self.meta = kargs['meta'] if 'meta' in kargs else {}
55 0eac0256 Nikos Skalkotos
        self.sysprep_params = \
56 0eac0256 Nikos Skalkotos
            kargs['sysprep_params'] if 'sysprep_params' in kargs else {}
57 0eac0256 Nikos Skalkotos
58 f5174d2c Nikos Skalkotos
        self.progress_bar = None
59 f5174d2c Nikos Skalkotos
        self.guestfs_device = None
60 f5174d2c Nikos Skalkotos
        self.size = 0
61 f5174d2c Nikos Skalkotos
62 f5174d2c Nikos Skalkotos
        self.g = guestfs.GuestFS()
63 ec8b2a79 Nikos Skalkotos
        self.guestfs_enabled = False
64 5756c277 Nikos Skalkotos
        self.guestfs_version = self.g.version()
65 5756c277 Nikos Skalkotos
66 5756c277 Nikos Skalkotos
    def check_guestfs_version(self, major, minor, release):
67 5756c277 Nikos Skalkotos
        """Checks if the version of the used libguestfs is smaller, equal or
68 5756c277 Nikos Skalkotos
        greater than the one specified by the major, minor and release triplet
69 5756c277 Nikos Skalkotos

70 5756c277 Nikos Skalkotos
        Returns:
71 5756c277 Nikos Skalkotos
            < 0 if the installed version is smaller than the specified one
72 5756c277 Nikos Skalkotos
            = 0 if they are equal
73 5756c277 Nikos Skalkotos
            > 0 if the installed one is greater than the specified one
74 5756c277 Nikos Skalkotos
        """
75 5756c277 Nikos Skalkotos
76 5756c277 Nikos Skalkotos
        for (a, b) in (self.guestfs_version['major'], major), \
77 5756c277 Nikos Skalkotos
                (self.guestfs_version['minor'], minor), \
78 5756c277 Nikos Skalkotos
                (self.guestfs_version['release'], release):
79 5756c277 Nikos Skalkotos
            if a != b:
80 5756c277 Nikos Skalkotos
                return a - b
81 5756c277 Nikos Skalkotos
82 5756c277 Nikos Skalkotos
        return 0
83 ec8b2a79 Nikos Skalkotos
84 ec8b2a79 Nikos Skalkotos
    def enable(self):
85 ec8b2a79 Nikos Skalkotos
        """Enable a newly created Image instance"""
86 ec8b2a79 Nikos Skalkotos
87 ec8b2a79 Nikos Skalkotos
        self.enable_guestfs()
88 ec8b2a79 Nikos Skalkotos
89 ec8b2a79 Nikos Skalkotos
        self.out.output('Inspecting Operating System ...', False)
90 ec8b2a79 Nikos Skalkotos
        roots = self.g.inspect_os()
91 e482b7f9 Nikos Skalkotos
92 e482b7f9 Nikos Skalkotos
        if len(roots) == 0 or len(roots) > 1:
93 e482b7f9 Nikos Skalkotos
            self.root = None
94 e482b7f9 Nikos Skalkotos
            self.ostype = "unsupported"
95 e482b7f9 Nikos Skalkotos
            self.distro = "unsupported"
96 e482b7f9 Nikos Skalkotos
            self.guestfs_device = '/dev/sda'
97 e482b7f9 Nikos Skalkotos
            self.size = self.g.blockdev_getsize64(self.guestfs_device)
98 d0daafa2 Nikos Skalkotos
99 e482b7f9 Nikos Skalkotos
            if len(roots) > 1:
100 d0daafa2 Nikos Skalkotos
                reason = "Multiple operating systems found on the media."
101 e482b7f9 Nikos Skalkotos
            else:
102 d0daafa2 Nikos Skalkotos
                reason = "Unable to detect any operating system on the media."
103 e482b7f9 Nikos Skalkotos
104 d0daafa2 Nikos Skalkotos
            self.set_unsupported(reason)
105 e482b7f9 Nikos Skalkotos
            return
106 e482b7f9 Nikos Skalkotos
107 ec8b2a79 Nikos Skalkotos
        self.root = roots[0]
108 d0daafa2 Nikos Skalkotos
        self.meta['PARTITION_TABLE'] = self.g.part_get_parttype('/dev/sda')
109 d0daafa2 Nikos Skalkotos
        self.guestfs_device = '/dev/sda'  # self.g.part_to_dev(self.root)
110 ec8b2a79 Nikos Skalkotos
        self.size = self.g.blockdev_getsize64(self.guestfs_device)
111 ec8b2a79 Nikos Skalkotos
112 ec8b2a79 Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
113 ec8b2a79 Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
114 ec8b2a79 Nikos Skalkotos
        self.out.success(
115 ec8b2a79 Nikos Skalkotos
            'found a(n) %s system' %
116 ec8b2a79 Nikos Skalkotos
            self.ostype if self.distro == "unknown" else self.distro)
117 ec8b2a79 Nikos Skalkotos
118 5a33a51f Nikos Skalkotos
        # Inspect the OS
119 5a33a51f Nikos Skalkotos
        self.os.inspect()
120 d0daafa2 Nikos Skalkotos
121 d0daafa2 Nikos Skalkotos
    def set_unsupported(self, reason):
122 d0daafa2 Nikos Skalkotos
        """Flag this image us ansupported"""
123 d0daafa2 Nikos Skalkotos
124 d0daafa2 Nikos Skalkotos
        self._unsupported = reason
125 d0daafa2 Nikos Skalkotos
        self.meta['UNSUPPORTED'] = reason
126 d0daafa2 Nikos Skalkotos
        self.out.warn('Media is not supported. Reason: %s' % reason)
127 d0daafa2 Nikos Skalkotos
128 d0daafa2 Nikos Skalkotos
    def is_unsupported(self):
129 d0daafa2 Nikos Skalkotos
        """Returns if this image is unsupported"""
130 d0daafa2 Nikos Skalkotos
        return hasattr(self, '_unsupported')
131 d0daafa2 Nikos Skalkotos
132 ec8b2a79 Nikos Skalkotos
    def enable_guestfs(self):
133 ec8b2a79 Nikos Skalkotos
        """Enable the guestfs handler"""
134 ec8b2a79 Nikos Skalkotos
135 ec8b2a79 Nikos Skalkotos
        if self.guestfs_enabled:
136 ec8b2a79 Nikos Skalkotos
            self.out.warn("Guestfs is already enabled")
137 ec8b2a79 Nikos Skalkotos
            return
138 ec8b2a79 Nikos Skalkotos
139 bbfcaef1 Nikos Skalkotos
        # Before version 1.18.4 the behaviour of kill_subprocess was different
140 bbfcaef1 Nikos Skalkotos
        # and you need to reset the guestfs handler to relaunch a previously
141 bbfcaef1 Nikos Skalkotos
        # shut down qemu backend
142 5756c277 Nikos Skalkotos
        if self.check_guestfs_version(1, 18, 4) < 0:
143 bbfcaef1 Nikos Skalkotos
            self.g = guestfs.GuestFS()
144 bbfcaef1 Nikos Skalkotos
145 f5174d2c Nikos Skalkotos
        self.g.add_drive_opts(self.device, readonly=0, format="raw")
146 f5174d2c Nikos Skalkotos
147 f5174d2c Nikos Skalkotos
        # Before version 1.17.14 the recovery process, which is a fork of the
148 f5174d2c Nikos Skalkotos
        # original process that called libguestfs, did not close its inherited
149 f5174d2c Nikos Skalkotos
        # file descriptors. This can cause problems especially if the parent
150 f5174d2c Nikos Skalkotos
        # process has opened pipes. Since the recovery process is an optional
151 f5174d2c Nikos Skalkotos
        # feature of libguestfs, it's better to disable it.
152 5756c277 Nikos Skalkotos
        if self.check_guestfs_version(1, 17, 14) >= 0:
153 f5174d2c Nikos Skalkotos
            self.out.output("Enabling recovery proc")
154 7c6a4186 Nikos Skalkotos
            self.g.set_recovery_proc(1)
155 7c6a4186 Nikos Skalkotos
        else:
156 7c6a4186 Nikos Skalkotos
            self.g.set_recovery_proc(0)
157 f5174d2c Nikos Skalkotos
158 f5174d2c Nikos Skalkotos
        #self.g.set_trace(1)
159 f5174d2c Nikos Skalkotos
        #self.g.set_verbose(1)
160 f5174d2c Nikos Skalkotos
161 f5174d2c Nikos Skalkotos
        self.out.output('Launching helper VM (may take a while) ...', False)
162 f5174d2c Nikos Skalkotos
        # self.progressbar = self.out.Progress(100, "Launching helper VM",
163 f5174d2c Nikos Skalkotos
        #                                     "percent")
164 f5174d2c Nikos Skalkotos
        # eh = self.g.set_event_callback(self.progress_callback,
165 f5174d2c Nikos Skalkotos
        #                               guestfs.EVENT_PROGRESS)
166 f5174d2c Nikos Skalkotos
        self.g.launch()
167 f5174d2c Nikos Skalkotos
        self.guestfs_enabled = True
168 f5174d2c Nikos Skalkotos
        # self.g.delete_event_callback(eh)
169 f5174d2c Nikos Skalkotos
        # self.progressbar.success('done')
170 f5174d2c Nikos Skalkotos
        # self.progressbar = None
171 bbfcaef1 Nikos Skalkotos
172 5756c277 Nikos Skalkotos
        if self.check_guestfs_version(1, 18, 4) < 0:
173 bbfcaef1 Nikos Skalkotos
            self.g.inspect_os()  # some calls need this
174 bbfcaef1 Nikos Skalkotos
175 f5174d2c Nikos Skalkotos
        self.out.success('done')
176 f5174d2c Nikos Skalkotos
177 ec8b2a79 Nikos Skalkotos
    def disable_guestfs(self):
178 ec8b2a79 Nikos Skalkotos
        """Disable the guestfs handler"""
179 f5174d2c Nikos Skalkotos
180 ec8b2a79 Nikos Skalkotos
        if not self.guestfs_enabled:
181 ec8b2a79 Nikos Skalkotos
            self.out.warn("Guestfs is already disabled")
182 ec8b2a79 Nikos Skalkotos
            return
183 ec8b2a79 Nikos Skalkotos
184 ec8b2a79 Nikos Skalkotos
        self.out.output("Shutting down helper VM ...", False)
185 ec8b2a79 Nikos Skalkotos
        self.g.sync()
186 ec8b2a79 Nikos Skalkotos
        # guestfs_shutdown which is the prefered way to shutdown the backend
187 ec8b2a79 Nikos Skalkotos
        # process was introduced in version 1.19.16
188 5756c277 Nikos Skalkotos
        if self.check_guestfs_version(1, 19, 16) >= 0:
189 ec8b2a79 Nikos Skalkotos
            self.g.shutdown()
190 ec8b2a79 Nikos Skalkotos
        else:
191 ec8b2a79 Nikos Skalkotos
            self.g.kill_subprocess()
192 ec8b2a79 Nikos Skalkotos
193 ec8b2a79 Nikos Skalkotos
        self.guestfs_enabled = False
194 ec8b2a79 Nikos Skalkotos
        self.out.success('done')
195 f5174d2c Nikos Skalkotos
196 f5174d2c Nikos Skalkotos
    def _get_os(self):
197 88f83027 Nikos Skalkotos
        """Return an OS class instance for this image"""
198 f5174d2c Nikos Skalkotos
        if hasattr(self, "_os"):
199 f5174d2c Nikos Skalkotos
            return self._os
200 f5174d2c Nikos Skalkotos
201 f5174d2c Nikos Skalkotos
        if not self.guestfs_enabled:
202 f5174d2c Nikos Skalkotos
            self.enable()
203 f5174d2c Nikos Skalkotos
204 71b0ab28 Nikos Skalkotos
        cls = os_cls(self.distro, self.ostype)
205 0eac0256 Nikos Skalkotos
        self._os = cls(self, sysprep_params=self.sysprep_params)
206 f5174d2c Nikos Skalkotos
207 71b0ab28 Nikos Skalkotos
        self._os.collect_metadata()
208 f5174d2c Nikos Skalkotos
209 f5174d2c Nikos Skalkotos
        return self._os
210 f5174d2c Nikos Skalkotos
211 f5174d2c Nikos Skalkotos
    os = property(_get_os)
212 f5174d2c Nikos Skalkotos
213 f5174d2c Nikos Skalkotos
    def destroy(self):
214 88f83027 Nikos Skalkotos
        """Destroy this Image instance."""
215 f5174d2c Nikos Skalkotos
216 f5174d2c Nikos Skalkotos
        # In new guestfs versions, there is a handy shutdown method for this
217 f5174d2c Nikos Skalkotos
        try:
218 f5174d2c Nikos Skalkotos
            if self.guestfs_enabled:
219 f5174d2c Nikos Skalkotos
                self.g.umount_all()
220 f5174d2c Nikos Skalkotos
                self.g.sync()
221 f5174d2c Nikos Skalkotos
        finally:
222 f5174d2c Nikos Skalkotos
            # Close the guestfs handler if open
223 f5174d2c Nikos Skalkotos
            self.g.close()
224 f5174d2c Nikos Skalkotos
225 f5174d2c Nikos Skalkotos
#    def progress_callback(self, ev, eh, buf, array):
226 f5174d2c Nikos Skalkotos
#        position = array[2]
227 f5174d2c Nikos Skalkotos
#        total = array[3]
228 f5174d2c Nikos Skalkotos
#
229 f5174d2c Nikos Skalkotos
#        self.progressbar.goto((position * 100) // total)
230 f5174d2c Nikos Skalkotos
231 f5174d2c Nikos Skalkotos
    def _last_partition(self):
232 88f83027 Nikos Skalkotos
        """Return the last partition of the image disk"""
233 f5174d2c Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
234 f5174d2c Nikos Skalkotos
            msg = "Unsupported partition table: %s. Only msdos and gpt " \
235 f5174d2c Nikos Skalkotos
                "partition tables are supported" % self.meta['PARTITION_TABLE']
236 f5174d2c Nikos Skalkotos
            raise FatalError(msg)
237 f5174d2c Nikos Skalkotos
238 f5174d2c Nikos Skalkotos
        is_extended = lambda p: \
239 f5174d2c Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
240 f5174d2c Nikos Skalkotos
            in (0x5, 0xf)
241 f5174d2c Nikos Skalkotos
        is_logical = lambda p: \
242 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
243 f5174d2c Nikos Skalkotos
244 f5174d2c Nikos Skalkotos
        partitions = self.g.part_list(self.guestfs_device)
245 f5174d2c Nikos Skalkotos
        last_partition = partitions[-1]
246 f5174d2c Nikos Skalkotos
247 f5174d2c Nikos Skalkotos
        if is_logical(last_partition):
248 f5174d2c Nikos Skalkotos
            # The disk contains extended and logical partitions....
249 f5174d2c Nikos Skalkotos
            extended = filter(is_extended, partitions)[0]
250 f5174d2c Nikos Skalkotos
            last_primary = [p for p in partitions if p['part_num'] <= 4][-1]
251 f5174d2c Nikos Skalkotos
252 f5174d2c Nikos Skalkotos
            # check if extended is the last primary partition
253 f5174d2c Nikos Skalkotos
            if last_primary['part_num'] > extended['part_num']:
254 f5174d2c Nikos Skalkotos
                last_partition = last_primary
255 f5174d2c Nikos Skalkotos
256 f5174d2c Nikos Skalkotos
        return last_partition
257 f5174d2c Nikos Skalkotos
258 f5174d2c Nikos Skalkotos
    def shrink(self):
259 88f83027 Nikos Skalkotos
        """Shrink the image.
260 f5174d2c Nikos Skalkotos

261 88f83027 Nikos Skalkotos
        This is accomplished by shrinking the last file system of the
262 88f83027 Nikos Skalkotos
        image and then updating the partition table. The new disk size
263 f5174d2c Nikos Skalkotos
        (in bytes) is returned.
264 f5174d2c Nikos Skalkotos

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

396 f5174d2c Nikos Skalkotos
        This method will only dump the actual payload, found by reading the
397 f5174d2c Nikos Skalkotos
        partition table. Empty space in the end of the device will be ignored.
398 f5174d2c Nikos Skalkotos
        """
399 f5174d2c Nikos Skalkotos
        MB = 2 ** 20
400 f5174d2c Nikos Skalkotos
        blocksize = 4 * MB  # 4MB
401 f5174d2c Nikos Skalkotos
        size = self.size
402 f5174d2c Nikos Skalkotos
        progr_size = (size + MB - 1) // MB  # in MB
403 f5174d2c Nikos Skalkotos
        progressbar = self.out.Progress(progr_size, "Dumping image file", 'mb')
404 f5174d2c Nikos Skalkotos
405 f5174d2c Nikos Skalkotos
        with open(self.device, 'r') as src:
406 f5174d2c Nikos Skalkotos
            with open(outfile, "w") as dst:
407 f5174d2c Nikos Skalkotos
                left = size
408 f5174d2c Nikos Skalkotos
                offset = 0
409 f5174d2c Nikos Skalkotos
                progressbar.next()
410 f5174d2c Nikos Skalkotos
                while left > 0:
411 f5174d2c Nikos Skalkotos
                    length = min(left, blocksize)
412 f5174d2c Nikos Skalkotos
                    sent = sendfile(dst.fileno(), src.fileno(), offset, length)
413 f5174d2c Nikos Skalkotos
414 f5174d2c Nikos Skalkotos
                    # Workaround for python-sendfile API change. In
415 f5174d2c Nikos Skalkotos
                    # python-sendfile 1.2.x (py-sendfile) the returning value
416 f5174d2c Nikos Skalkotos
                    # of sendfile is a tuple, where in version 2.x (pysendfile)
417 f5174d2c Nikos Skalkotos
                    # it is just a sigle integer.
418 f5174d2c Nikos Skalkotos
                    if isinstance(sent, tuple):
419 f5174d2c Nikos Skalkotos
                        sent = sent[1]
420 f5174d2c Nikos Skalkotos
421 f5174d2c Nikos Skalkotos
                    offset += sent
422 f5174d2c Nikos Skalkotos
                    left -= sent
423 f5174d2c Nikos Skalkotos
                    progressbar.goto((size - left) // MB)
424 f5174d2c Nikos Skalkotos
        progressbar.success('image file %s was successfully created' % outfile)
425 f5174d2c Nikos Skalkotos
426 f5174d2c Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :