Statistics
| Branch: | Tag: | Revision:

root / image_creator / image.py @ 7c6a4186

History | View | Annotate | Download (12.6 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 7c6a4186 Nikos Skalkotos
from image_creator.util import FatalError, check_guestfs_version
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 f5174d2c Nikos Skalkotos
    def __init__(self, device, output, bootable=True, meta={}):
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 f5174d2c Nikos Skalkotos
        self.bootable = bootable
54 f5174d2c Nikos Skalkotos
        self.meta = meta
55 f5174d2c Nikos Skalkotos
        self.progress_bar = None
56 f5174d2c Nikos Skalkotos
        self.guestfs_device = None
57 f5174d2c Nikos Skalkotos
        self.size = 0
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 7c6a4186 Nikos Skalkotos
        if check_guestfs_version(self.g, 1, 17, 14) >= 0:
68 f5174d2c Nikos Skalkotos
            self.out.output("Enabling recovery proc")
69 7c6a4186 Nikos Skalkotos
            self.g.set_recovery_proc(1)
70 7c6a4186 Nikos Skalkotos
        else:
71 7c6a4186 Nikos Skalkotos
            self.g.set_recovery_proc(0)
72 f5174d2c Nikos Skalkotos
73 f5174d2c Nikos Skalkotos
        #self.g.set_trace(1)
74 f5174d2c Nikos Skalkotos
        #self.g.set_verbose(1)
75 f5174d2c Nikos Skalkotos
76 f5174d2c Nikos Skalkotos
        self.guestfs_enabled = False
77 f5174d2c Nikos Skalkotos
78 f5174d2c Nikos Skalkotos
    def enable(self):
79 88f83027 Nikos Skalkotos
        """Enable a newly created Image instance"""
80 f5174d2c Nikos Skalkotos
81 f5174d2c Nikos Skalkotos
        self.out.output('Launching helper VM (may take a while) ...', False)
82 f5174d2c Nikos Skalkotos
        # self.progressbar = self.out.Progress(100, "Launching helper VM",
83 f5174d2c Nikos Skalkotos
        #                                     "percent")
84 f5174d2c Nikos Skalkotos
        # eh = self.g.set_event_callback(self.progress_callback,
85 f5174d2c Nikos Skalkotos
        #                               guestfs.EVENT_PROGRESS)
86 f5174d2c Nikos Skalkotos
        self.g.launch()
87 f5174d2c Nikos Skalkotos
        self.guestfs_enabled = True
88 f5174d2c Nikos Skalkotos
        # self.g.delete_event_callback(eh)
89 f5174d2c Nikos Skalkotos
        # self.progressbar.success('done')
90 f5174d2c Nikos Skalkotos
        # self.progressbar = None
91 f5174d2c Nikos Skalkotos
        self.out.success('done')
92 f5174d2c Nikos Skalkotos
93 f5174d2c Nikos Skalkotos
        self.out.output('Inspecting Operating System ...', False)
94 f5174d2c Nikos Skalkotos
        roots = self.g.inspect_os()
95 f5174d2c Nikos Skalkotos
        if len(roots) == 0:
96 f5174d2c Nikos Skalkotos
            raise FatalError("No operating system found")
97 f5174d2c Nikos Skalkotos
        if len(roots) > 1:
98 f5174d2c Nikos Skalkotos
            raise FatalError("Multiple operating systems found."
99 f5174d2c Nikos Skalkotos
                             "We only support images with one OS.")
100 f5174d2c Nikos Skalkotos
        self.root = roots[0]
101 f5174d2c Nikos Skalkotos
        self.guestfs_device = self.g.part_to_dev(self.root)
102 f5174d2c Nikos Skalkotos
        self.size = self.g.blockdev_getsize64(self.guestfs_device)
103 f5174d2c Nikos Skalkotos
        self.meta['PARTITION_TABLE'] = \
104 f5174d2c Nikos Skalkotos
            self.g.part_get_parttype(self.guestfs_device)
105 f5174d2c Nikos Skalkotos
106 f5174d2c Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
107 f5174d2c Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
108 80411610 Nikos Skalkotos
        self.out.success(
109 80411610 Nikos Skalkotos
            'found a(n) %s system' %
110 80411610 Nikos Skalkotos
            self.ostype if self.distro == "unknown" else self.distro)
111 f5174d2c Nikos Skalkotos
112 f5174d2c Nikos Skalkotos
    def _get_os(self):
113 88f83027 Nikos Skalkotos
        """Return an OS class instance for this image"""
114 f5174d2c Nikos Skalkotos
        if hasattr(self, "_os"):
115 f5174d2c Nikos Skalkotos
            return self._os
116 f5174d2c Nikos Skalkotos
117 f5174d2c Nikos Skalkotos
        if not self.guestfs_enabled:
118 f5174d2c Nikos Skalkotos
            self.enable()
119 f5174d2c Nikos Skalkotos
120 71b0ab28 Nikos Skalkotos
        cls = os_cls(self.distro, self.ostype)
121 71b0ab28 Nikos Skalkotos
        self._os = cls(self.root, self.g, self.out)
122 f5174d2c Nikos Skalkotos
123 71b0ab28 Nikos Skalkotos
        self._os.collect_metadata()
124 f5174d2c Nikos Skalkotos
125 f5174d2c Nikos Skalkotos
        return self._os
126 f5174d2c Nikos Skalkotos
127 f5174d2c Nikos Skalkotos
    os = property(_get_os)
128 f5174d2c Nikos Skalkotos
129 f5174d2c Nikos Skalkotos
    def destroy(self):
130 88f83027 Nikos Skalkotos
        """Destroy this Image instance."""
131 f5174d2c Nikos Skalkotos
132 f5174d2c Nikos Skalkotos
        # In new guestfs versions, there is a handy shutdown method for this
133 f5174d2c Nikos Skalkotos
        try:
134 f5174d2c Nikos Skalkotos
            if self.guestfs_enabled:
135 f5174d2c Nikos Skalkotos
                self.g.umount_all()
136 f5174d2c Nikos Skalkotos
                self.g.sync()
137 f5174d2c Nikos Skalkotos
        finally:
138 f5174d2c Nikos Skalkotos
            # Close the guestfs handler if open
139 f5174d2c Nikos Skalkotos
            self.g.close()
140 f5174d2c Nikos Skalkotos
141 f5174d2c Nikos Skalkotos
#    def progress_callback(self, ev, eh, buf, array):
142 f5174d2c Nikos Skalkotos
#        position = array[2]
143 f5174d2c Nikos Skalkotos
#        total = array[3]
144 f5174d2c Nikos Skalkotos
#
145 f5174d2c Nikos Skalkotos
#        self.progressbar.goto((position * 100) // total)
146 f5174d2c Nikos Skalkotos
147 f5174d2c Nikos Skalkotos
    def _last_partition(self):
148 88f83027 Nikos Skalkotos
        """Return the last partition of the image disk"""
149 f5174d2c Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
150 f5174d2c Nikos Skalkotos
            msg = "Unsupported partition table: %s. Only msdos and gpt " \
151 f5174d2c Nikos Skalkotos
                "partition tables are supported" % self.meta['PARTITION_TABLE']
152 f5174d2c Nikos Skalkotos
            raise FatalError(msg)
153 f5174d2c Nikos Skalkotos
154 f5174d2c Nikos Skalkotos
        is_extended = lambda p: \
155 f5174d2c Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
156 f5174d2c Nikos Skalkotos
            in (0x5, 0xf)
157 f5174d2c Nikos Skalkotos
        is_logical = lambda p: \
158 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
159 f5174d2c Nikos Skalkotos
160 f5174d2c Nikos Skalkotos
        partitions = self.g.part_list(self.guestfs_device)
161 f5174d2c Nikos Skalkotos
        last_partition = partitions[-1]
162 f5174d2c Nikos Skalkotos
163 f5174d2c Nikos Skalkotos
        if is_logical(last_partition):
164 f5174d2c Nikos Skalkotos
            # The disk contains extended and logical partitions....
165 f5174d2c Nikos Skalkotos
            extended = filter(is_extended, partitions)[0]
166 f5174d2c Nikos Skalkotos
            last_primary = [p for p in partitions if p['part_num'] <= 4][-1]
167 f5174d2c Nikos Skalkotos
168 f5174d2c Nikos Skalkotos
            # check if extended is the last primary partition
169 f5174d2c Nikos Skalkotos
            if last_primary['part_num'] > extended['part_num']:
170 f5174d2c Nikos Skalkotos
                last_partition = last_primary
171 f5174d2c Nikos Skalkotos
172 f5174d2c Nikos Skalkotos
        return last_partition
173 f5174d2c Nikos Skalkotos
174 f5174d2c Nikos Skalkotos
    def shrink(self):
175 88f83027 Nikos Skalkotos
        """Shrink the image.
176 f5174d2c Nikos Skalkotos

177 88f83027 Nikos Skalkotos
        This is accomplished by shrinking the last file system of the
178 88f83027 Nikos Skalkotos
        image and then updating the partition table. The new disk size
179 f5174d2c Nikos Skalkotos
        (in bytes) is returned.
180 f5174d2c Nikos Skalkotos

181 f5174d2c Nikos Skalkotos
        ATTENTION: make sure unmount is called before shrink
182 f5174d2c Nikos Skalkotos
        """
183 f5174d2c Nikos Skalkotos
        get_fstype = lambda p: \
184 f5174d2c Nikos Skalkotos
            self.g.vfs_type("%s%d" % (self.guestfs_device, p['part_num']))
185 f5174d2c Nikos Skalkotos
        is_logical = lambda p: \
186 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
187 f5174d2c Nikos Skalkotos
        is_extended = lambda p: \
188 f5174d2c Nikos Skalkotos
            self.meta['PARTITION_TABLE'] == 'msdos' and \
189 f5174d2c Nikos Skalkotos
            self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
190 f5174d2c Nikos Skalkotos
            in (0x5, 0xf)
191 f5174d2c Nikos Skalkotos
192 f5174d2c Nikos Skalkotos
        part_add = lambda ptype, start, stop: \
193 f5174d2c Nikos Skalkotos
            self.g.part_add(self.guestfs_device, ptype, start, stop)
194 f5174d2c Nikos Skalkotos
        part_del = lambda p: self.g.part_del(self.guestfs_device, p)
195 f5174d2c Nikos Skalkotos
        part_get_id = lambda p: self.g.part_get_mbr_id(self.guestfs_device, p)
196 f5174d2c Nikos Skalkotos
        part_set_id = lambda p, id: \
197 f5174d2c Nikos Skalkotos
            self.g.part_set_mbr_id(self.guestfs_device, p, id)
198 f5174d2c Nikos Skalkotos
        part_get_bootable = lambda p: \
199 f5174d2c Nikos Skalkotos
            self.g.part_get_bootable(self.guestfs_device, p)
200 f5174d2c Nikos Skalkotos
        part_set_bootable = lambda p, bootable: \
201 f5174d2c Nikos Skalkotos
            self.g.part_set_bootable(self.guestfs_device, p, bootable)
202 f5174d2c Nikos Skalkotos
203 f5174d2c Nikos Skalkotos
        MB = 2 ** 20
204 f5174d2c Nikos Skalkotos
205 f5174d2c Nikos Skalkotos
        self.out.output("Shrinking image (this may take a while) ...", False)
206 f5174d2c Nikos Skalkotos
207 f5174d2c Nikos Skalkotos
        sector_size = self.g.blockdev_getss(self.guestfs_device)
208 f5174d2c Nikos Skalkotos
209 f5174d2c Nikos Skalkotos
        last_part = None
210 f5174d2c Nikos Skalkotos
        fstype = None
211 f5174d2c Nikos Skalkotos
        while True:
212 f5174d2c Nikos Skalkotos
            last_part = self._last_partition()
213 f5174d2c Nikos Skalkotos
            fstype = get_fstype(last_part)
214 f5174d2c Nikos Skalkotos
215 f5174d2c Nikos Skalkotos
            if fstype == 'swap':
216 f5174d2c Nikos Skalkotos
                self.meta['SWAP'] = "%d:%s" % \
217 f5174d2c Nikos Skalkotos
                    (last_part['part_num'],
218 f5174d2c Nikos Skalkotos
                     (last_part['part_size'] + MB - 1) // MB)
219 f5174d2c Nikos Skalkotos
                part_del(last_part['part_num'])
220 f5174d2c Nikos Skalkotos
                continue
221 f5174d2c Nikos Skalkotos
            elif is_extended(last_part):
222 f5174d2c Nikos Skalkotos
                part_del(last_part['part_num'])
223 f5174d2c Nikos Skalkotos
                continue
224 f5174d2c Nikos Skalkotos
225 f5174d2c Nikos Skalkotos
            # Most disk manipulation programs leave 2048 sectors after the last
226 f5174d2c Nikos Skalkotos
            # partition
227 f5174d2c Nikos Skalkotos
            new_size = last_part['part_end'] + 1 + 2048 * sector_size
228 f5174d2c Nikos Skalkotos
            self.size = min(self.size, new_size)
229 f5174d2c Nikos Skalkotos
            break
230 f5174d2c Nikos Skalkotos
231 f5174d2c Nikos Skalkotos
        if not re.match("ext[234]", fstype):
232 c71133ce Nikos Skalkotos
            self.out.warn("Don't know how to shrink %s partitions." % fstype)
233 f5174d2c Nikos Skalkotos
            return self.size
234 f5174d2c Nikos Skalkotos
235 f5174d2c Nikos Skalkotos
        part_dev = "%s%d" % (self.guestfs_device, last_part['part_num'])
236 f5174d2c Nikos Skalkotos
        self.g.e2fsck_f(part_dev)
237 f5174d2c Nikos Skalkotos
        self.g.resize2fs_M(part_dev)
238 f5174d2c Nikos Skalkotos
239 f5174d2c Nikos Skalkotos
        out = self.g.tune2fs_l(part_dev)
240 f5174d2c Nikos Skalkotos
        block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1])
241 f5174d2c Nikos Skalkotos
        block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1])
242 f5174d2c Nikos Skalkotos
243 f5174d2c Nikos Skalkotos
        start = last_part['part_start'] / sector_size
244 f5174d2c Nikos Skalkotos
        end = start + (block_size * block_cnt) / sector_size - 1
245 f5174d2c Nikos Skalkotos
246 f5174d2c Nikos Skalkotos
        if is_logical(last_part):
247 f5174d2c Nikos Skalkotos
            partitions = self.g.part_list(self.guestfs_device)
248 f5174d2c Nikos Skalkotos
249 f5174d2c Nikos Skalkotos
            logical = []  # logical partitions
250 f5174d2c Nikos Skalkotos
            for partition in partitions:
251 f5174d2c Nikos Skalkotos
                if partition['part_num'] < 4:
252 f5174d2c Nikos Skalkotos
                    continue
253 f5174d2c Nikos Skalkotos
                logical.append({
254 f5174d2c Nikos Skalkotos
                    'num': partition['part_num'],
255 f5174d2c Nikos Skalkotos
                    'start': partition['part_start'] / sector_size,
256 f5174d2c Nikos Skalkotos
                    'end': partition['part_end'] / sector_size,
257 f5174d2c Nikos Skalkotos
                    'id': part_get_id(partition['part_num']),
258 f5174d2c Nikos Skalkotos
                    'bootable': part_get_bootable(partition['part_num'])
259 f5174d2c Nikos Skalkotos
                })
260 f5174d2c Nikos Skalkotos
261 f5174d2c Nikos Skalkotos
            logical[-1]['end'] = end  # new end after resize
262 f5174d2c Nikos Skalkotos
263 f5174d2c Nikos Skalkotos
            # Recreate the extended partition
264 f5174d2c Nikos Skalkotos
            extended = filter(is_extended, partitions)[0]
265 f5174d2c Nikos Skalkotos
            part_del(extended['part_num'])
266 f5174d2c Nikos Skalkotos
            part_add('e', extended['part_start'] / sector_size, end)
267 f5174d2c Nikos Skalkotos
268 f5174d2c Nikos Skalkotos
            # Create all the logical partitions back
269 f5174d2c Nikos Skalkotos
            for l in logical:
270 f5174d2c Nikos Skalkotos
                part_add('l', l['start'], l['end'])
271 f5174d2c Nikos Skalkotos
                part_set_id(l['num'], l['id'])
272 f5174d2c Nikos Skalkotos
                part_set_bootable(l['num'], l['bootable'])
273 f5174d2c Nikos Skalkotos
        else:
274 f5174d2c Nikos Skalkotos
            # Recreate the last partition
275 f5174d2c Nikos Skalkotos
            if self.meta['PARTITION_TABLE'] == 'msdos':
276 f5174d2c Nikos Skalkotos
                last_part['id'] = part_get_id(last_part['part_num'])
277 f5174d2c Nikos Skalkotos
278 f5174d2c Nikos Skalkotos
            last_part['bootable'] = part_get_bootable(last_part['part_num'])
279 f5174d2c Nikos Skalkotos
            part_del(last_part['part_num'])
280 f5174d2c Nikos Skalkotos
            part_add('p', start, end)
281 f5174d2c Nikos Skalkotos
            part_set_bootable(last_part['part_num'], last_part['bootable'])
282 f5174d2c Nikos Skalkotos
283 f5174d2c Nikos Skalkotos
            if self.meta['PARTITION_TABLE'] == 'msdos':
284 f5174d2c Nikos Skalkotos
                part_set_id(last_part['part_num'], last_part['id'])
285 f5174d2c Nikos Skalkotos
286 f5174d2c Nikos Skalkotos
        new_size = (end + 1) * sector_size
287 f5174d2c Nikos Skalkotos
288 f5174d2c Nikos Skalkotos
        assert (new_size <= self.size)
289 f5174d2c Nikos Skalkotos
290 f5174d2c Nikos Skalkotos
        if self.meta['PARTITION_TABLE'] == 'gpt':
291 f5174d2c Nikos Skalkotos
            ptable = GPTPartitionTable(self.device)
292 f5174d2c Nikos Skalkotos
            self.size = ptable.shrink(new_size, self.size)
293 f5174d2c Nikos Skalkotos
        else:
294 f5174d2c Nikos Skalkotos
            self.size = min(new_size + 2048 * sector_size, self.size)
295 f5174d2c Nikos Skalkotos
296 f5174d2c Nikos Skalkotos
        self.out.success("new size is %dMB" % ((self.size + MB - 1) // MB))
297 f5174d2c Nikos Skalkotos
298 f5174d2c Nikos Skalkotos
        return self.size
299 f5174d2c Nikos Skalkotos
300 f5174d2c Nikos Skalkotos
    def dump(self, outfile):
301 88f83027 Nikos Skalkotos
        """Dumps the content of the image into a file.
302 f5174d2c Nikos Skalkotos

303 f5174d2c Nikos Skalkotos
        This method will only dump the actual payload, found by reading the
304 f5174d2c Nikos Skalkotos
        partition table. Empty space in the end of the device will be ignored.
305 f5174d2c Nikos Skalkotos
        """
306 f5174d2c Nikos Skalkotos
        MB = 2 ** 20
307 f5174d2c Nikos Skalkotos
        blocksize = 4 * MB  # 4MB
308 f5174d2c Nikos Skalkotos
        size = self.size
309 f5174d2c Nikos Skalkotos
        progr_size = (size + MB - 1) // MB  # in MB
310 f5174d2c Nikos Skalkotos
        progressbar = self.out.Progress(progr_size, "Dumping image file", 'mb')
311 f5174d2c Nikos Skalkotos
312 f5174d2c Nikos Skalkotos
        with open(self.device, 'r') as src:
313 f5174d2c Nikos Skalkotos
            with open(outfile, "w") as dst:
314 f5174d2c Nikos Skalkotos
                left = size
315 f5174d2c Nikos Skalkotos
                offset = 0
316 f5174d2c Nikos Skalkotos
                progressbar.next()
317 f5174d2c Nikos Skalkotos
                while left > 0:
318 f5174d2c Nikos Skalkotos
                    length = min(left, blocksize)
319 f5174d2c Nikos Skalkotos
                    sent = sendfile(dst.fileno(), src.fileno(), offset, length)
320 f5174d2c Nikos Skalkotos
321 f5174d2c Nikos Skalkotos
                    # Workaround for python-sendfile API change. In
322 f5174d2c Nikos Skalkotos
                    # python-sendfile 1.2.x (py-sendfile) the returning value
323 f5174d2c Nikos Skalkotos
                    # of sendfile is a tuple, where in version 2.x (pysendfile)
324 f5174d2c Nikos Skalkotos
                    # it is just a sigle integer.
325 f5174d2c Nikos Skalkotos
                    if isinstance(sent, tuple):
326 f5174d2c Nikos Skalkotos
                        sent = sent[1]
327 f5174d2c Nikos Skalkotos
328 f5174d2c Nikos Skalkotos
                    offset += sent
329 f5174d2c Nikos Skalkotos
                    left -= sent
330 f5174d2c Nikos Skalkotos
                    progressbar.goto((size - left) // MB)
331 f5174d2c Nikos Skalkotos
        progressbar.success('image file %s was successfully created' % outfile)
332 f5174d2c Nikos Skalkotos
333 f5174d2c Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :