Statistics
| Branch: | Tag: | Revision:

root / image_creator / disk.py @ 42ace959

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

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

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

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