Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ f165adc0

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

60 3b2f6619 Nikos Skalkotos
    A Disk instance never alters the source media it is created from.
61 3b2f6619 Nikos Skalkotos
    Any change is done on a snapshot created by the device-mapper of
62 3b2f6619 Nikos Skalkotos
    the Linux kernel.
63 3b2f6619 Nikos Skalkotos
    """
64 d57775d4 Nikos Skalkotos
65 d57775d4 Nikos Skalkotos
    def __init__(self, source):
66 3b2f6619 Nikos Skalkotos
        """Create a new Disk instance out of a source media. The source
67 3b2f6619 Nikos Skalkotos
        media can be an image file, a block device or a directory."""
68 d57775d4 Nikos Skalkotos
        self._cleanup_jobs = []
69 d57775d4 Nikos Skalkotos
        self._devices = []
70 d57775d4 Nikos Skalkotos
        self.source = source
71 d57775d4 Nikos Skalkotos
72 d57775d4 Nikos Skalkotos
    def _add_cleanup(self, job, *args):
73 d57775d4 Nikos Skalkotos
        self._cleanup_jobs.append((job, args))
74 d57775d4 Nikos Skalkotos
75 d57775d4 Nikos Skalkotos
    def _losetup(self, fname):
76 3ccb2618 Nikos Skalkotos
        loop = losetup('-f', '--show', fname)
77 ae48a082 Nikos Skalkotos
        loop = loop.strip()  # remove the new-line char
78 3ccb2618 Nikos Skalkotos
        self._add_cleanup(losetup, '-d', loop)
79 3ccb2618 Nikos Skalkotos
        return loop
80 d57775d4 Nikos Skalkotos
81 d57775d4 Nikos Skalkotos
    def _dir_to_disk(self):
82 d57775d4 Nikos Skalkotos
        raise NotImplementedError
83 d57775d4 Nikos Skalkotos
84 d57775d4 Nikos Skalkotos
    def cleanup(self):
85 3b2f6619 Nikos Skalkotos
        """Cleanup internal data. This needs to be called before the
86 3b2f6619 Nikos Skalkotos
        program ends.
87 3b2f6619 Nikos Skalkotos
        """
88 1377b8a7 Nikos Skalkotos
        while len(self._devices):
89 1377b8a7 Nikos Skalkotos
            device = self._devices.pop()
90 1377b8a7 Nikos Skalkotos
            device.destroy()
91 8c574358 Nikos Skalkotos
92 d57775d4 Nikos Skalkotos
        while len(self._cleanup_jobs):
93 d57775d4 Nikos Skalkotos
            job, args = self._cleanup_jobs.pop()
94 d57775d4 Nikos Skalkotos
            job(*args)
95 d57775d4 Nikos Skalkotos
96 d57775d4 Nikos Skalkotos
    def get_device(self):
97 3b2f6619 Nikos Skalkotos
        """Returns a newly created DiskDevice instance.
98 c408053f Nikos Skalkotos

99 3b2f6619 Nikos Skalkotos
        This instance is a snapshot of the original source media of
100 3b2f6619 Nikos Skalkotos
        the Disk instance.
101 3b2f6619 Nikos Skalkotos
        """
102 22a6d232 Nikos Skalkotos
103 979096dd Nikos Skalkotos
        output("Examining source media `%s'..." % self.source, False)
104 3f70f242 Nikos Skalkotos
        sourcedev = self.source
105 3f70f242 Nikos Skalkotos
        mode = os.stat(self.source).st_mode
106 3f70f242 Nikos Skalkotos
        if stat.S_ISDIR(mode):
107 d603d80d Nikos Skalkotos
            success('looks like a directory')
108 3f70f242 Nikos Skalkotos
            return self._losetup(self._dir_to_disk())
109 3f70f242 Nikos Skalkotos
        elif stat.S_ISREG(mode):
110 d603d80d Nikos Skalkotos
            success('looks like an image file')
111 3f70f242 Nikos Skalkotos
            sourcedev = self._losetup(self.source)
112 3f70f242 Nikos Skalkotos
        elif not stat.S_ISBLK(mode):
113 3f70f242 Nikos Skalkotos
            raise ValueError("Invalid media source. Only block devices, "
114 3f70f242 Nikos Skalkotos
                            "regular files and directories are supported.")
115 3f70f242 Nikos Skalkotos
        else:
116 d603d80d Nikos Skalkotos
            success('looks like a block device')
117 d57775d4 Nikos Skalkotos
118 d57775d4 Nikos Skalkotos
        # Take a snapshot and return it to the user
119 979096dd Nikos Skalkotos
        output("Snapshotting media source...", False)
120 3f70f242 Nikos Skalkotos
        size = blockdev('--getsize', sourcedev)
121 3f70f242 Nikos Skalkotos
        cowfd, cow = tempfile.mkstemp()
122 3f70f242 Nikos Skalkotos
        os.close(cowfd)
123 3f70f242 Nikos Skalkotos
        self._add_cleanup(os.unlink, cow)
124 3f70f242 Nikos Skalkotos
        # Create 1G cow sparse file
125 3f70f242 Nikos Skalkotos
        dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', \
126 3f70f242 Nikos Skalkotos
                                        'seek=%d' % (1024 * 1024))
127 3f70f242 Nikos Skalkotos
        cowdev = self._losetup(cow)
128 3f70f242 Nikos Skalkotos
129 3f70f242 Nikos Skalkotos
        snapshot = uuid.uuid4().hex
130 3f70f242 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
131 3f70f242 Nikos Skalkotos
        try:
132 3f70f242 Nikos Skalkotos
            os.write(tablefd, "0 %d snapshot %s %s n 8" % \
133 3f70f242 Nikos Skalkotos
                                        (int(size), sourcedev, cowdev))
134 3f70f242 Nikos Skalkotos
            dmsetup('create', snapshot, table)
135 3f70f242 Nikos Skalkotos
            self._add_cleanup(dmsetup, 'remove', snapshot)
136 3f70f242 Nikos Skalkotos
            # Sometimes dmsetup remove fails with Device or resource busy,
137 3f70f242 Nikos Skalkotos
            # although everything is cleaned up and the snapshot is not
138 3f70f242 Nikos Skalkotos
            # used by anyone. Add a 2 seconds delay to be on the safe side.
139 3f70f242 Nikos Skalkotos
            self._add_cleanup(time.sleep, 2)
140 3f70f242 Nikos Skalkotos
141 3f70f242 Nikos Skalkotos
        finally:
142 3f70f242 Nikos Skalkotos
            os.unlink(table)
143 d603d80d Nikos Skalkotos
        success('done')
144 1377b8a7 Nikos Skalkotos
        new_device = DiskDevice("/dev/mapper/%s" % snapshot)
145 d57775d4 Nikos Skalkotos
        self._devices.append(new_device)
146 0db22eac Nikos Skalkotos
        new_device.enable()
147 d57775d4 Nikos Skalkotos
        return new_device
148 d57775d4 Nikos Skalkotos
149 1377b8a7 Nikos Skalkotos
    def destroy_device(self, device):
150 3b2f6619 Nikos Skalkotos
        """Destroys a DiskDevice instance previously created by
151 3b2f6619 Nikos Skalkotos
        get_device method.
152 3b2f6619 Nikos Skalkotos
        """
153 1377b8a7 Nikos Skalkotos
        self._devices.remove(device)
154 1377b8a7 Nikos Skalkotos
        device.destroy()
155 1377b8a7 Nikos Skalkotos
156 8c574358 Nikos Skalkotos
157 d57775d4 Nikos Skalkotos
class DiskDevice(object):
158 3b2f6619 Nikos Skalkotos
    """This class represents a block device hosting an Operating System
159 3b2f6619 Nikos Skalkotos
    as created by the device-mapper.
160 3b2f6619 Nikos Skalkotos
    """
161 d57775d4 Nikos Skalkotos
162 8c574358 Nikos Skalkotos
    def __init__(self, device, bootable=True):
163 3b2f6619 Nikos Skalkotos
        """Create a new DiskDevice."""
164 0db22eac Nikos Skalkotos
165 333ff548 Nikos Skalkotos
        self.device = device
166 1377b8a7 Nikos Skalkotos
        self.bootable = bootable
167 586da0a0 Nikos Skalkotos
        self.progress_bar = None
168 1377b8a7 Nikos Skalkotos
169 1377b8a7 Nikos Skalkotos
        self.g = guestfs.GuestFS()
170 0db22eac Nikos Skalkotos
        self.g.add_drive_opts(self.device, readonly=0)
171 0d5a999d Nikos Skalkotos
172 586da0a0 Nikos Skalkotos
        #self.g.set_trace(1)
173 586da0a0 Nikos Skalkotos
        #self.g.set_verbose(1)
174 0d5a999d Nikos Skalkotos
175 0db22eac Nikos Skalkotos
        self.guestfs_enabled = False
176 ae48a082 Nikos Skalkotos
177 0db22eac Nikos Skalkotos
    def enable(self):
178 0db22eac Nikos Skalkotos
        """Enable a newly created DiskDevice"""
179 979096dd Nikos Skalkotos
        self.progressbar = progress("Launching helper VM: ")
180 3f70f242 Nikos Skalkotos
        self.progressbar.next()
181 3f70f242 Nikos Skalkotos
        eh = self.g.set_event_callback(self.progress_callback,
182 3f70f242 Nikos Skalkotos
                                                    guestfs.EVENT_PROGRESS)
183 3f70f242 Nikos Skalkotos
        self.g.launch()
184 3f70f242 Nikos Skalkotos
        self.guestfs_enabled = True
185 3f70f242 Nikos Skalkotos
        self.g.delete_event_callback(eh)
186 3f70f242 Nikos Skalkotos
        if self.progressbar is not None:
187 3f70f242 Nikos Skalkotos
            self.progressbar.send(100)
188 3f70f242 Nikos Skalkotos
            self.progressbar = None
189 3f70f242 Nikos Skalkotos
190 979096dd Nikos Skalkotos
        output('Inspecting Operating System...', False)
191 3f70f242 Nikos Skalkotos
        roots = self.g.inspect_os()
192 3f70f242 Nikos Skalkotos
        if len(roots) == 0:
193 3f70f242 Nikos Skalkotos
            raise FatalError("No operating system found")
194 3f70f242 Nikos Skalkotos
        if len(roots) > 1:
195 3f70f242 Nikos Skalkotos
            raise FatalError("Multiple operating systems found."
196 3f70f242 Nikos Skalkotos
                            "We only support images with one filesystem.")
197 3f70f242 Nikos Skalkotos
        self.root = roots[0]
198 3f70f242 Nikos Skalkotos
        self.ostype = self.g.inspect_get_type(self.root)
199 3f70f242 Nikos Skalkotos
        self.distro = self.g.inspect_get_distro(self.root)
200 d603d80d Nikos Skalkotos
        success('found a %s system' % self.distro)
201 8c574358 Nikos Skalkotos
202 1377b8a7 Nikos Skalkotos
    def destroy(self):
203 3b2f6619 Nikos Skalkotos
        """Destroy this DiskDevice instance."""
204 0db22eac Nikos Skalkotos
205 0db22eac Nikos Skalkotos
        if self.guestfs_enabled:
206 0db22eac Nikos Skalkotos
            self.g.umount_all()
207 0db22eac Nikos Skalkotos
            self.g.sync()
208 0db22eac Nikos Skalkotos
209 0db22eac Nikos Skalkotos
        # Close the guestfs handler if open
210 aa2062ba Nikos Skalkotos
        self.g.close()
211 8c574358 Nikos Skalkotos
212 586da0a0 Nikos Skalkotos
    def progress_callback(self, ev, eh, buf, array):
213 586da0a0 Nikos Skalkotos
        position = array[2]
214 586da0a0 Nikos Skalkotos
        total = array[3]
215 586da0a0 Nikos Skalkotos
216 ae48a082 Nikos Skalkotos
        self.progressbar.send((position * 100) // total)
217 586da0a0 Nikos Skalkotos
218 586da0a0 Nikos Skalkotos
        if position == total:
219 c54fc0e8 Nikos Skalkotos
            self.progressbar = None
220 586da0a0 Nikos Skalkotos
221 1377b8a7 Nikos Skalkotos
    def mount(self):
222 3b2f6619 Nikos Skalkotos
        """Mount all disk partitions in a correct order."""
223 979096dd Nikos Skalkotos
224 979096dd Nikos Skalkotos
        output("Mounting image...", False)
225 0d5a999d Nikos Skalkotos
        mps = self.g.inspect_get_mountpoints(self.root)
226 8c574358 Nikos Skalkotos
227 1377b8a7 Nikos Skalkotos
        # Sort the keys to mount the fs in a correct order.
228 1377b8a7 Nikos Skalkotos
        # / should be mounted befor /boot, etc
229 8c574358 Nikos Skalkotos
        def compare(a, b):
230 8c574358 Nikos Skalkotos
            if len(a[0]) > len(b[0]):
231 8c574358 Nikos Skalkotos
                return 1
232 8c574358 Nikos Skalkotos
            elif len(a[0]) == len(b[0]):
233 8c574358 Nikos Skalkotos
                return 0
234 8c574358 Nikos Skalkotos
            else:
235 8c574358 Nikos Skalkotos
                return -1
236 1377b8a7 Nikos Skalkotos
        mps.sort(compare)
237 1377b8a7 Nikos Skalkotos
        for mp, dev in mps:
238 1377b8a7 Nikos Skalkotos
            try:
239 1377b8a7 Nikos Skalkotos
                self.g.mount(dev, mp)
240 1377b8a7 Nikos Skalkotos
            except RuntimeError as msg:
241 979096dd Nikos Skalkotos
                warn("%s (ignored)" % msg)
242 979096dd Nikos Skalkotos
        success("done")
243 d57775d4 Nikos Skalkotos
244 8c574358 Nikos Skalkotos
    def umount(self):
245 3b2f6619 Nikos Skalkotos
        """Umount all mounted filesystems."""
246 8c574358 Nikos Skalkotos
        self.g.umount_all()
247 8c574358 Nikos Skalkotos
248 8c574358 Nikos Skalkotos
    def shrink(self):
249 3b2f6619 Nikos Skalkotos
        """Shrink the disk.
250 3b2f6619 Nikos Skalkotos

251 3b2f6619 Nikos Skalkotos
        This is accomplished by shrinking the last filesystem in the
252 3b2f6619 Nikos Skalkotos
        disk and then updating the partition table. The new disk size
253 3b2f6619 Nikos Skalkotos
        (in bytes) is returned.
254 3b2f6619 Nikos Skalkotos
        """
255 979096dd Nikos Skalkotos
        output("Shrinking image (this may take a while)...", False)
256 3f70f242 Nikos Skalkotos
257 8c574358 Nikos Skalkotos
        dev = self.g.part_to_dev(self.root)
258 8c574358 Nikos Skalkotos
        parttype = self.g.part_get_parttype(dev)
259 8c574358 Nikos Skalkotos
        if parttype != 'msdos':
260 0ae01e26 Nikos Skalkotos
            raise FatalError("You have a %s partition table. "
261 8c574358 Nikos Skalkotos
                "Only msdos partitions are supported" % parttype)
262 8c574358 Nikos Skalkotos
263 8c574358 Nikos Skalkotos
        last_partition = self.g.part_list(dev)[-1]
264 8c574358 Nikos Skalkotos
265 8c574358 Nikos Skalkotos
        if last_partition['part_num'] > 4:
266 0ae01e26 Nikos Skalkotos
            raise FatalError("This disk contains logical partitions. "
267 8c574358 Nikos Skalkotos
                "Only primary partitions are supported.")
268 8c574358 Nikos Skalkotos
269 8c574358 Nikos Skalkotos
        part_dev = "%s%d" % (dev, last_partition['part_num'])
270 8c574358 Nikos Skalkotos
        fs_type = self.g.vfs_type(part_dev)
271 8c574358 Nikos Skalkotos
        if not re.match("ext[234]", fs_type):
272 22a6d232 Nikos Skalkotos
            warn("Don't know how to resize %s partitions." % vfs_type)
273 8c574358 Nikos Skalkotos
            return
274 8c574358 Nikos Skalkotos
275 3f70f242 Nikos Skalkotos
        self.g.e2fsck_f(part_dev)
276 3f70f242 Nikos Skalkotos
        self.g.resize2fs_M(part_dev)
277 3f70f242 Nikos Skalkotos
278 3f70f242 Nikos Skalkotos
        output = self.g.tune2fs_l(part_dev)
279 3f70f242 Nikos Skalkotos
        block_size = int(
280 3f70f242 Nikos Skalkotos
            filter(lambda x: x[0] == 'Block size', output)[0][1])
281 3f70f242 Nikos Skalkotos
        block_cnt = int(
282 3f70f242 Nikos Skalkotos
            filter(lambda x: x[0] == 'Block count', output)[0][1])
283 22a6d232 Nikos Skalkotos
284 3f70f242 Nikos Skalkotos
        sector_size = self.g.blockdev_getss(dev)
285 8c574358 Nikos Skalkotos
286 3f70f242 Nikos Skalkotos
        start = last_partition['part_start'] / sector_size
287 3f70f242 Nikos Skalkotos
        end = start + (block_size * block_cnt) / sector_size - 1
288 8c574358 Nikos Skalkotos
289 3f70f242 Nikos Skalkotos
        self.g.part_del(dev, last_partition['part_num'])
290 3f70f242 Nikos Skalkotos
        self.g.part_add(dev, 'p', start, end)
291 8c574358 Nikos Skalkotos
292 3f70f242 Nikos Skalkotos
        new_size = (end + 1) * sector_size
293 d603d80d Nikos Skalkotos
        success("new image size is %dMB" %
294 d603d80d Nikos Skalkotos
                            ((new_size + 2 ** 20 - 1) // 2 ** 20))
295 22a6d232 Nikos Skalkotos
        return new_size
296 e3aac3f9 Nikos Skalkotos
297 c408053f Nikos Skalkotos
    def size(self):
298 c408053f Nikos Skalkotos
        """Returns the "payload" size of the device.
299 c408053f Nikos Skalkotos

300 c408053f Nikos Skalkotos
        The size returned by this method is the size of the space occupied by
301 c408053f Nikos Skalkotos
        the partitions (including the space before the first partition).
302 c408053f Nikos Skalkotos
        """
303 c408053f Nikos Skalkotos
        dev = self.g.part_to_dev(self.root)
304 c408053f Nikos Skalkotos
        last = self.g.part_list(dev)[-1]
305 c408053f Nikos Skalkotos
306 13675429 Nikos Skalkotos
        return last['part_end'] + 1
307 8c574358 Nikos Skalkotos
308 d603d80d Nikos Skalkotos
    def dump(self, outfile):
309 d603d80d Nikos Skalkotos
        """Dumps the content of device into a file.
310 d603d80d Nikos Skalkotos

311 d603d80d Nikos Skalkotos
        This method will only dump the actual payload, found by reading the
312 d603d80d Nikos Skalkotos
        partition table. Empty space in the end of the device will be ignored.
313 d603d80d Nikos Skalkotos
        """
314 d603d80d Nikos Skalkotos
        blocksize = 2 ** 22  # 4MB
315 d603d80d Nikos Skalkotos
        size = self.size()
316 d603d80d Nikos Skalkotos
        progress_size = (size + 2 ** 20 - 1) // 2 ** 20  # in MB
317 979096dd Nikos Skalkotos
        progressbar = progress("Dumping image file: ", progress_size)
318 d603d80d Nikos Skalkotos
319 d603d80d Nikos Skalkotos
        source = open(self.device, "r")
320 d603d80d Nikos Skalkotos
        try:
321 d603d80d Nikos Skalkotos
            dest = open(outfile, "w")
322 d603d80d Nikos Skalkotos
            try:
323 d603d80d Nikos Skalkotos
                left = size
324 d603d80d Nikos Skalkotos
                offset = 0
325 d603d80d Nikos Skalkotos
                progressbar.next()
326 d603d80d Nikos Skalkotos
                while left > 0:
327 d603d80d Nikos Skalkotos
                    length = min(left, blocksize)
328 d603d80d Nikos Skalkotos
                    sent = sendfile(dest.fileno(), source.fileno(), offset,
329 d603d80d Nikos Skalkotos
                                                                        length)
330 d603d80d Nikos Skalkotos
                    offset += sent
331 d603d80d Nikos Skalkotos
                    left -= sent
332 d603d80d Nikos Skalkotos
                    for i in range((length + 2 ** 20 - 1) // 2 ** 20):
333 d603d80d Nikos Skalkotos
                        progressbar.next()
334 d603d80d Nikos Skalkotos
            finally:
335 d603d80d Nikos Skalkotos
                dest.close()
336 d603d80d Nikos Skalkotos
        finally:
337 d603d80d Nikos Skalkotos
            source.close()
338 d603d80d Nikos Skalkotos
339 d603d80d Nikos Skalkotos
        success('Image file %s was successfully created' % outfile)
340 d603d80d Nikos Skalkotos
341 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :