Statistics
| Branch: | Tag: | Revision:

root / image_creator / bundle_volume.py @ d15368cc

History | View | Annotate | Download (14.9 kB)

1 9e4b4de2 Nikos Skalkotos
# Copyright 2012 GRNET S.A. All rights reserved.
2 9e4b4de2 Nikos Skalkotos
#
3 9e4b4de2 Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
4 9e4b4de2 Nikos Skalkotos
# without modification, are permitted provided that the following
5 9e4b4de2 Nikos Skalkotos
# conditions are met:
6 9e4b4de2 Nikos Skalkotos
#
7 9e4b4de2 Nikos Skalkotos
#   1. Redistributions of source code must retain the above
8 9e4b4de2 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
9 9e4b4de2 Nikos Skalkotos
#      disclaimer.
10 9e4b4de2 Nikos Skalkotos
#
11 9e4b4de2 Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
12 9e4b4de2 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
13 9e4b4de2 Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
14 9e4b4de2 Nikos Skalkotos
#      provided with the distribution.
15 9e4b4de2 Nikos Skalkotos
#
16 9e4b4de2 Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 9e4b4de2 Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 9e4b4de2 Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 9e4b4de2 Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 9e4b4de2 Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 9e4b4de2 Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 9e4b4de2 Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 9e4b4de2 Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 9e4b4de2 Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 9e4b4de2 Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 9e4b4de2 Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 9e4b4de2 Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
28 9e4b4de2 Nikos Skalkotos
#
29 9e4b4de2 Nikos Skalkotos
# The views and conclusions contained in the software and
30 9e4b4de2 Nikos Skalkotos
# documentation are those of the authors and should not be
31 9e4b4de2 Nikos Skalkotos
# interpreted as representing official policies, either expressed
32 9e4b4de2 Nikos Skalkotos
# or implied, of GRNET S.A.
33 9e4b4de2 Nikos Skalkotos
34 27a4229d Nikos Skalkotos
import os
35 27a4229d Nikos Skalkotos
import re
36 8eea5572 Nikos Skalkotos
import tempfile
37 9517bf29 Nikos Skalkotos
from collections import namedtuple
38 27a4229d Nikos Skalkotos
39 8eea5572 Nikos Skalkotos
import parted
40 8eea5572 Nikos Skalkotos
41 74149d07 Nikos Skalkotos
from image_creator.rsync import Rsync
42 27a4229d Nikos Skalkotos
from image_creator.util import get_command
43 9e4b4de2 Nikos Skalkotos
from image_creator.util import FatalError
44 f3845095 Nikos Skalkotos
from image_creator.util import try_fail_repeat
45 9e4b4de2 Nikos Skalkotos
46 27a4229d Nikos Skalkotos
findfs = get_command('findfs')
47 27a4229d Nikos Skalkotos
dd = get_command('dd')
48 8eea5572 Nikos Skalkotos
dmsetup = get_command('dmsetup')
49 25b4d858 Nikos Skalkotos
losetup = get_command('losetup')
50 25b4d858 Nikos Skalkotos
mount = get_command('mount')
51 25b4d858 Nikos Skalkotos
umount = get_command('umount')
52 bf15a033 Nikos Skalkotos
blkid = get_command('blkid')
53 25b4d858 Nikos Skalkotos
54 25b4d858 Nikos Skalkotos
MKFS_OPTS = {
55 25b4d858 Nikos Skalkotos
    'ext2': ['-F'],
56 25b4d858 Nikos Skalkotos
    'ext3': ['-F'],
57 25b4d858 Nikos Skalkotos
    'ext4': ['-F'],
58 25b4d858 Nikos Skalkotos
    'reiserfs': ['-ff'],
59 25b4d858 Nikos Skalkotos
    'btrfs': [],
60 25b4d858 Nikos Skalkotos
    'minix': [],
61 25b4d858 Nikos Skalkotos
    'xfs': ['-f'],
62 25b4d858 Nikos Skalkotos
    'jfs': ['-f'],
63 25b4d858 Nikos Skalkotos
    'ntfs': ['-F'],
64 25b4d858 Nikos Skalkotos
    'msdos': [],
65 25b4d858 Nikos Skalkotos
    'vfat': []
66 25b4d858 Nikos Skalkotos
    }
67 9517bf29 Nikos Skalkotos
68 9517bf29 Nikos Skalkotos
69 f3845095 Nikos Skalkotos
class BundleVolume(object):
70 f3845095 Nikos Skalkotos
    """This class can be used to create an image out of the running system"""
71 27a4229d Nikos Skalkotos
72 8eea5572 Nikos Skalkotos
    def __init__(self, out, meta):
73 f3845095 Nikos Skalkotos
        """Create an instance of the BundleVolume class."""
74 8eea5572 Nikos Skalkotos
        self.out = out
75 8eea5572 Nikos Skalkotos
        self.meta = meta
76 27a4229d Nikos Skalkotos
77 a939e3b8 Nikos Skalkotos
        self.out.output('Searching for root device ...', False)
78 8eea5572 Nikos Skalkotos
        root = self._get_root_partition()
79 27a4229d Nikos Skalkotos
80 8eea5572 Nikos Skalkotos
        if root.startswith("UUID=") or root.startswith("LABEL="):
81 25b4d858 Nikos Skalkotos
            root = findfs(root).stdout.strip()
82 27a4229d Nikos Skalkotos
83 25b4d858 Nikos Skalkotos
        if not re.match('/dev/[hsv]d[a-z][1-9]*$', root):
84 25b4d858 Nikos Skalkotos
            raise FatalError("Don't know how to handle root device: %s" % root)
85 9517bf29 Nikos Skalkotos
86 25b4d858 Nikos Skalkotos
        out.success(root)
87 9517bf29 Nikos Skalkotos
88 25b4d858 Nikos Skalkotos
        disk_file = re.split('[0-9]', root)[0]
89 25b4d858 Nikos Skalkotos
        device = parted.Device(disk_file)
90 25b4d858 Nikos Skalkotos
        self.disk = parted.Disk(device)
91 9517bf29 Nikos Skalkotos
92 a939e3b8 Nikos Skalkotos
    def _read_fstable(self, f):
93 25b4d858 Nikos Skalkotos
94 8eea5572 Nikos Skalkotos
        if not os.path.isfile(f):
95 8eea5572 Nikos Skalkotos
            raise FatalError("Unable to open: `%s'. File is missing." % f)
96 27a4229d Nikos Skalkotos
97 74149d07 Nikos Skalkotos
        FileSystemTableEntry = namedtuple('FileSystemTableEntry',
98 25b4d858 Nikos Skalkotos
                                     'dev mpoint fs opts freq passno')
99 8eea5572 Nikos Skalkotos
        with open(f) as table:
100 8eea5572 Nikos Skalkotos
            for line in iter(table):
101 8eea5572 Nikos Skalkotos
                entry = line.split('#')[0].strip().split()
102 8eea5572 Nikos Skalkotos
                if len(entry) != 6:
103 8eea5572 Nikos Skalkotos
                    continue
104 74149d07 Nikos Skalkotos
                yield FileSystemTableEntry(*entry)
105 27a4229d Nikos Skalkotos
106 a939e3b8 Nikos Skalkotos
    def _get_root_partition(self):
107 8eea5572 Nikos Skalkotos
        for entry in self._read_fstable('/etc/fstab'):
108 8eea5572 Nikos Skalkotos
            if entry.mpoint == '/':
109 8eea5572 Nikos Skalkotos
                return entry.dev
110 27a4229d Nikos Skalkotos
111 8eea5572 Nikos Skalkotos
        raise FatalError("Unable to find root device in /etc/fstab")
112 27a4229d Nikos Skalkotos
113 a939e3b8 Nikos Skalkotos
    def _is_mpoint(self, path):
114 a939e3b8 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
115 8eea5572 Nikos Skalkotos
            if entry.mpoint == path:
116 8eea5572 Nikos Skalkotos
                return True
117 8eea5572 Nikos Skalkotos
        return False
118 27a4229d Nikos Skalkotos
119 25b4d858 Nikos Skalkotos
    def _get_mount_options(self, device):
120 a939e3b8 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
121 8eea5572 Nikos Skalkotos
            if not entry.dev.startswith('/'):
122 8eea5572 Nikos Skalkotos
                continue
123 9517bf29 Nikos Skalkotos
124 8eea5572 Nikos Skalkotos
            if os.path.realpath(entry.dev) == os.path.realpath(device):
125 8eea5572 Nikos Skalkotos
                return entry
126 9517bf29 Nikos Skalkotos
127 25b4d858 Nikos Skalkotos
        return None
128 9517bf29 Nikos Skalkotos
129 25b4d858 Nikos Skalkotos
    def _create_partition_table(self, image):
130 8eea5572 Nikos Skalkotos
131 25b4d858 Nikos Skalkotos
        if self.disk.type != 'msdos':
132 8eea5572 Nikos Skalkotos
            raise FatalError('Only msdos partition tables are supported')
133 8eea5572 Nikos Skalkotos
134 8eea5572 Nikos Skalkotos
        # Copy the MBR and the space between the MBR and the first partition.
135 8eea5572 Nikos Skalkotos
        # In Grub version 1 Stage 1.5 is located there.
136 25b4d858 Nikos Skalkotos
        first_sector = self.disk.getPrimaryPartitions()[0].geometry.start
137 8eea5572 Nikos Skalkotos
138 25b4d858 Nikos Skalkotos
        dd('if=%s' % self.disk.device.path, 'of=%s' % image,
139 25b4d858 Nikos Skalkotos
           'bs=%d' % self.disk.device.sectorSize,
140 8eea5572 Nikos Skalkotos
           'count=%d' % first_sector, 'conv=notrunc')
141 8eea5572 Nikos Skalkotos
142 8eea5572 Nikos Skalkotos
        # Create the Extended boot records (EBRs) in the image
143 25b4d858 Nikos Skalkotos
        extended = self.disk.getExtendedPartition()
144 8eea5572 Nikos Skalkotos
        if not extended:
145 8eea5572 Nikos Skalkotos
            return
146 8eea5572 Nikos Skalkotos
147 8eea5572 Nikos Skalkotos
        # Extended boot records precede the logical partitions they describe
148 25b4d858 Nikos Skalkotos
        logical = self.disk.getLogicalPartitions()
149 8eea5572 Nikos Skalkotos
        start = extended.geometry.start
150 8eea5572 Nikos Skalkotos
        for i in range(len(logical)):
151 8eea5572 Nikos Skalkotos
            end = logical[i].geometry.start - 1
152 25b4d858 Nikos Skalkotos
            dd('if=%s' % self.disk.device.path, 'of=%s' % image,
153 8eea5572 Nikos Skalkotos
               'count=%d' % (end - start + 1), 'conv=notrunc',
154 8eea5572 Nikos Skalkotos
               'seek=%d' % start, 'skip=%d' % start)
155 8eea5572 Nikos Skalkotos
            start = logical[i].geometry.end + 1
156 8eea5572 Nikos Skalkotos
157 25b4d858 Nikos Skalkotos
    def _get_partitions(self, disk):
158 25b4d858 Nikos Skalkotos
        Partition = namedtuple('Partition', 'num start end type fs')
159 8eea5572 Nikos Skalkotos
160 8eea5572 Nikos Skalkotos
        partitions = []
161 25b4d858 Nikos Skalkotos
        for p in disk.partitions:
162 25b4d858 Nikos Skalkotos
            num = p.number
163 25b4d858 Nikos Skalkotos
            start = p.geometry.start
164 25b4d858 Nikos Skalkotos
            end = p.geometry.end
165 25b4d858 Nikos Skalkotos
            ptype = p.type
166 25b4d858 Nikos Skalkotos
            fs = p.fileSystem.type if p.fileSystem is not None else ''
167 25b4d858 Nikos Skalkotos
            partitions.append(Partition(num, start, end, ptype, fs))
168 25b4d858 Nikos Skalkotos
169 25b4d858 Nikos Skalkotos
        return partitions
170 25b4d858 Nikos Skalkotos
171 25b4d858 Nikos Skalkotos
    def _shrink_partitions(self, image):
172 8eea5572 Nikos Skalkotos
173 25b4d858 Nikos Skalkotos
        new_end = self.disk.device.getLength()
174 25b4d858 Nikos Skalkotos
175 25b4d858 Nikos Skalkotos
        image_dev = parted.Device(image)
176 a939e3b8 Nikos Skalkotos
        image_disk = parted.Disk(image_dev)
177 a939e3b8 Nikos Skalkotos
178 a939e3b8 Nikos Skalkotos
        is_extended = lambda p: p.type == parted.PARTITION_EXTENDED
179 a939e3b8 Nikos Skalkotos
        is_logical = lambda p: p.type == parted.PARTITION_LOGICAL
180 a939e3b8 Nikos Skalkotos
181 25b4d858 Nikos Skalkotos
        partitions = self._get_partitions(self.disk)
182 a939e3b8 Nikos Skalkotos
183 a939e3b8 Nikos Skalkotos
        last = partitions[-1]
184 a939e3b8 Nikos Skalkotos
        if last.fs == 'linux-swap(v1)':
185 a939e3b8 Nikos Skalkotos
            MB = 2 ** 20
186 25b4d858 Nikos Skalkotos
            size = (last.end - last.start + 1) * self.disk.device.sectorSize
187 a939e3b8 Nikos Skalkotos
            self.meta['SWAP'] = "%d:%s" % (last.num, (size + MB - 1) // MB)
188 a939e3b8 Nikos Skalkotos
189 a939e3b8 Nikos Skalkotos
            image_disk.deletePartition(
190 a939e3b8 Nikos Skalkotos
                image_disk.getPartitionBySector(last.start))
191 a939e3b8 Nikos Skalkotos
            image_disk.commit()
192 a939e3b8 Nikos Skalkotos
193 a939e3b8 Nikos Skalkotos
            if is_logical(last) and last.num == 5:
194 a939e3b8 Nikos Skalkotos
                extended = image_disk.getExtendedPartition()
195 a939e3b8 Nikos Skalkotos
                image_disk.deletePartition(extended)
196 a939e3b8 Nikos Skalkotos
                image_disk.commit()
197 a939e3b8 Nikos Skalkotos
                partitions.remove(filter(is_extended, partitions)[0])
198 a939e3b8 Nikos Skalkotos
199 a939e3b8 Nikos Skalkotos
            partitions.remove(last)
200 a939e3b8 Nikos Skalkotos
            last = partitions[-1]
201 a939e3b8 Nikos Skalkotos
202 a939e3b8 Nikos Skalkotos
            # Leave 2048 blocks at the end
203 a939e3b8 Nikos Skalkotos
            new_end = last.end + 2048
204 a939e3b8 Nikos Skalkotos
205 25b4d858 Nikos Skalkotos
        mount_options = self._get_mount_options(
206 25b4d858 Nikos Skalkotos
                self.disk.getPartitionBySector(last.start).path)
207 74149d07 Nikos Skalkotos
        if mount_options is not None:
208 25b4d858 Nikos Skalkotos
            stat = os.statvfs(mount_options.mpoint)
209 25b4d858 Nikos Skalkotos
            # Shrink the last partition. The new size should be the size of the
210 25b4d858 Nikos Skalkotos
            # occupied blocks
211 a939e3b8 Nikos Skalkotos
            blcks = stat.f_blocks - stat.f_bavail
212 25b4d858 Nikos Skalkotos
            new_size = (blcks * stat.f_frsize) // self.disk.device.sectorSize
213 a939e3b8 Nikos Skalkotos
214 a939e3b8 Nikos Skalkotos
            # Add 10% just to be on the safe side
215 a939e3b8 Nikos Skalkotos
            part_end = last.start + (new_size * 11) // 10
216 a939e3b8 Nikos Skalkotos
            # Alighn to 2048
217 a939e3b8 Nikos Skalkotos
            part_end = ((part_end + 2047) // 2048) * 2048
218 a939e3b8 Nikos Skalkotos
219 a939e3b8 Nikos Skalkotos
            image_disk.setPartitionGeometry(
220 a939e3b8 Nikos Skalkotos
                image_disk.getPartitionBySector(last.start),
221 a939e3b8 Nikos Skalkotos
                parted.Constraint(device=image_disk.device),
222 83fe59dd Nikos Skalkotos
                start=last.start, end=part_end)
223 a939e3b8 Nikos Skalkotos
            image_disk.commit()
224 a939e3b8 Nikos Skalkotos
225 25b4d858 Nikos Skalkotos
            # Parted may have changed this for better alignment
226 25b4d858 Nikos Skalkotos
            part_end = image_disk.getPartitionBySector(last.start).geometry.end
227 25b4d858 Nikos Skalkotos
            last = last._replace(end=part_end)
228 25b4d858 Nikos Skalkotos
            partitions[-1] = last
229 25b4d858 Nikos Skalkotos
230 25b4d858 Nikos Skalkotos
            # Leave 2048 blocks at the end.
231 83fe59dd Nikos Skalkotos
            new_end = part_end + 2048
232 25b4d858 Nikos Skalkotos
233 a939e3b8 Nikos Skalkotos
            if last.type == parted.PARTITION_LOGICAL:
234 a939e3b8 Nikos Skalkotos
                # Fix the extended partition
235 a939e3b8 Nikos Skalkotos
                extended = disk.getExtendedPartition()
236 a939e3b8 Nikos Skalkotos
237 a939e3b8 Nikos Skalkotos
                image_disk.setPartitionGeometry(extended,
238 a939e3b8 Nikos Skalkotos
                    parted.Constraint(device=img_dev),
239 a939e3b8 Nikos Skalkotos
                    ext.geometry.start, end=last.end)
240 a939e3b8 Nikos Skalkotos
                image_disk.commit()
241 8eea5572 Nikos Skalkotos
242 83fe59dd Nikos Skalkotos
        image_dev.destroy()
243 25b4d858 Nikos Skalkotos
        return new_end
244 8eea5572 Nikos Skalkotos
245 25b4d858 Nikos Skalkotos
    def _map_partition(self, dev, num, start, end):
246 25b4d858 Nikos Skalkotos
        name = os.path.basename(dev)
247 25b4d858 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
248 25b4d858 Nikos Skalkotos
        try:
249 25b4d858 Nikos Skalkotos
            size = end - start + 1
250 25b4d858 Nikos Skalkotos
            os.write(tablefd, "0 %d linear %s %d" % (size, dev, start))
251 25b4d858 Nikos Skalkotos
            dmsetup('create', "%sp%d" % (name, num), table)
252 25b4d858 Nikos Skalkotos
        finally:
253 25b4d858 Nikos Skalkotos
            os.unlink(table)
254 8eea5572 Nikos Skalkotos
255 25b4d858 Nikos Skalkotos
        return "/dev/mapper/%sp%d" % (name, num)
256 8eea5572 Nikos Skalkotos
257 25b4d858 Nikos Skalkotos
    def _unmap_partition(self, dev):
258 25b4d858 Nikos Skalkotos
        if not os.path.exists(dev):
259 25b4d858 Nikos Skalkotos
            return
260 8eea5572 Nikos Skalkotos
261 f3845095 Nikos Skalkotos
        try_fail_repeat(dmsetup, 'remove', dev.split('/dev/mapper/')[1])
262 8eea5572 Nikos Skalkotos
263 25b4d858 Nikos Skalkotos
    def _mount(self, target, devs):
264 25b4d858 Nikos Skalkotos
265 25b4d858 Nikos Skalkotos
        devs.sort(key=lambda d: d[1])
266 25b4d858 Nikos Skalkotos
        for dev, mpoint in devs:
267 25b4d858 Nikos Skalkotos
            absmpoint = os.path.abspath(target + mpoint)
268 25b4d858 Nikos Skalkotos
            if not os.path.exists(absmpoint):
269 25b4d858 Nikos Skalkotos
                os.makedirs(absmpoint)
270 25b4d858 Nikos Skalkotos
            mount(dev, absmpoint)
271 25b4d858 Nikos Skalkotos
272 25b4d858 Nikos Skalkotos
    def _umount_all(self, target):
273 25b4d858 Nikos Skalkotos
        mpoints = []
274 25b4d858 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
275 25b4d858 Nikos Skalkotos
            if entry.mpoint.startswith(os.path.abspath(target)):
276 25b4d858 Nikos Skalkotos
                    mpoints.append(entry.mpoint)
277 74149d07 Nikos Skalkotos
278 25b4d858 Nikos Skalkotos
        mpoints.sort()
279 25b4d858 Nikos Skalkotos
        for mpoint in reversed(mpoints):
280 f3845095 Nikos Skalkotos
            try_fail_repeat(umount, mpoint)
281 25b4d858 Nikos Skalkotos
282 74149d07 Nikos Skalkotos
    def _to_exclude(self):
283 bf15a033 Nikos Skalkotos
        excluded = ['/tmp', '/var/tmp']
284 74149d07 Nikos Skalkotos
        local_filesystems = MKFS_OPTS.keys() + ['rootfs']
285 74149d07 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
286 74149d07 Nikos Skalkotos
            if entry.fs in local_filesystems:
287 74149d07 Nikos Skalkotos
                continue
288 74149d07 Nikos Skalkotos
289 74149d07 Nikos Skalkotos
            mpoint = entry.mpoint
290 74149d07 Nikos Skalkotos
            if mpoint in excluded:
291 74149d07 Nikos Skalkotos
                continue
292 74149d07 Nikos Skalkotos
293 74149d07 Nikos Skalkotos
            descendants = filter(lambda p: p.startswith(mpoint + '/'),
294 74149d07 Nikos Skalkotos
                    excluded)
295 74149d07 Nikos Skalkotos
            if len(descendants):
296 74149d07 Nikos Skalkotos
                for d in descendants:
297 74149d07 Nikos Skalkotos
                    excluded.remove(d)
298 74149d07 Nikos Skalkotos
                excluded.append(mpoint)
299 74149d07 Nikos Skalkotos
                continue
300 74149d07 Nikos Skalkotos
301 74149d07 Nikos Skalkotos
            dirname = mpoint
302 74149d07 Nikos Skalkotos
            basename = ''
303 74149d07 Nikos Skalkotos
            found_ancestor = False
304 74149d07 Nikos Skalkotos
            while dirname != '/':
305 74149d07 Nikos Skalkotos
                (dirname, basename) = os.path.split(dirname)
306 74149d07 Nikos Skalkotos
                if dirname in excluded:
307 74149d07 Nikos Skalkotos
                    found_ancestor = True
308 74149d07 Nikos Skalkotos
                    break
309 74149d07 Nikos Skalkotos
310 74149d07 Nikos Skalkotos
            if not found_ancestor:
311 74149d07 Nikos Skalkotos
                excluded.append(mpoint)
312 74149d07 Nikos Skalkotos
313 74149d07 Nikos Skalkotos
        return map(lambda d: d + "/*", excluded)
314 74149d07 Nikos Skalkotos
315 bf15a033 Nikos Skalkotos
    def _replace_uuids(self, target, new_uuid):
316 bf15a033 Nikos Skalkotos
317 bf15a033 Nikos Skalkotos
        files = ['/etc/fstab',
318 bf15a033 Nikos Skalkotos
                 '/boot/grub/grub.cfg',
319 bf15a033 Nikos Skalkotos
                 '/boot/grub/menu.lst',
320 bf15a033 Nikos Skalkotos
                 '/boot/grub/grub.conf']
321 bf15a033 Nikos Skalkotos
322 567891a6 Nikos Skalkotos
        orig = dict(map(lambda p: (p.number, blkid('-s', 'UUID', '-o',
323 bf15a033 Nikos Skalkotos
            'value', p.path).stdout.strip()), self.disk.partitions))
324 bf15a033 Nikos Skalkotos
325 bf15a033 Nikos Skalkotos
        for f in map(lambda f: target + f, files):
326 bf15a033 Nikos Skalkotos
327 bf15a033 Nikos Skalkotos
            if not os.path.exists(f):
328 bf15a033 Nikos Skalkotos
                continue
329 bf15a033 Nikos Skalkotos
330 bf15a033 Nikos Skalkotos
            with open(f, 'r') as src:
331 bf15a033 Nikos Skalkotos
                lines = src.readlines()
332 bf15a033 Nikos Skalkotos
            with open(f, 'w') as dest:
333 bf15a033 Nikos Skalkotos
                for line in lines:
334 bf15a033 Nikos Skalkotos
                    for i, uuid in new_uuid.items():
335 bf15a033 Nikos Skalkotos
                        line = re.sub(orig[i], uuid, line)
336 bf15a033 Nikos Skalkotos
                    dest.write(line)
337 bf15a033 Nikos Skalkotos
338 25b4d858 Nikos Skalkotos
    def _create_filesystems(self, image):
339 74149d07 Nikos Skalkotos
340 bf15a033 Nikos Skalkotos
        filesystem = {}
341 25b4d858 Nikos Skalkotos
        for p in self.disk.partitions:
342 bf15a033 Nikos Skalkotos
            filesystem[p.number] = self._get_mount_options(p.path)
343 25b4d858 Nikos Skalkotos
344 bf15a033 Nikos Skalkotos
        partitions = self._get_partitions(parted.Disk(parted.Device(image)))
345 bf15a033 Nikos Skalkotos
        unmounted = filter(lambda p: filesystem[p.num] is None, partitions)
346 bf15a033 Nikos Skalkotos
        mounted = filter(lambda p: filesystem[p.num] is not None, partitions)
347 25b4d858 Nikos Skalkotos
348 25b4d858 Nikos Skalkotos
        # For partitions that are not mounted right now, we can simply dd them
349 25b4d858 Nikos Skalkotos
        # into the image.
350 25b4d858 Nikos Skalkotos
        for p in unmounted:
351 25b4d858 Nikos Skalkotos
            dd('if=%s' % self.disk.device.path, 'of=%s' % image,
352 25b4d858 Nikos Skalkotos
               'count=%d' % (p.end - p.start + 1), 'conv=notrunc',
353 25b4d858 Nikos Skalkotos
               'seek=%d' % p.start, 'skip=%d' % p.start)
354 25b4d858 Nikos Skalkotos
355 25b4d858 Nikos Skalkotos
        loop = str(losetup('-f', '--show', image)).strip()
356 25b4d858 Nikos Skalkotos
        mapped = {}
357 25b4d858 Nikos Skalkotos
        try:
358 25b4d858 Nikos Skalkotos
            for p in mounted:
359 74149d07 Nikos Skalkotos
                i = p.num
360 25b4d858 Nikos Skalkotos
                mapped[i] = self._map_partition(loop, i, p.start, p.end)
361 25b4d858 Nikos Skalkotos
362 bf15a033 Nikos Skalkotos
            new_uuid = {}
363 25b4d858 Nikos Skalkotos
            # Create the file systems
364 25b4d858 Nikos Skalkotos
            for i, dev in mapped.iteritems():
365 bf15a033 Nikos Skalkotos
                fs = filesystem[i].fs
366 25b4d858 Nikos Skalkotos
                self.out.output('Creating %s filesystem on partition %d ... ' %
367 25b4d858 Nikos Skalkotos
                    (fs, i), False)
368 25b4d858 Nikos Skalkotos
                get_command('mkfs.%s' % fs)(*(MKFS_OPTS[fs] + [dev]))
369 25b4d858 Nikos Skalkotos
                self.out.success('done')
370 bf15a033 Nikos Skalkotos
                new_uuid[i] = blkid('-s', 'UUID', '-o', 'value', dev
371 bf15a033 Nikos Skalkotos
                    ).stdout.strip()
372 25b4d858 Nikos Skalkotos
373 25b4d858 Nikos Skalkotos
            target = tempfile.mkdtemp()
374 25b4d858 Nikos Skalkotos
            try:
375 25b4d858 Nikos Skalkotos
                absmpoints = self._mount(target,
376 bf15a033 Nikos Skalkotos
                    [(mapped[i], filesystem[i].mpoint) for i in mapped.keys()]
377 25b4d858 Nikos Skalkotos
                )
378 74149d07 Nikos Skalkotos
                exclude = self._to_exclude() + [image]
379 e6f134b3 Nikos Skalkotos
                rsync = Rsync('/', target,
380 e6f134b3 Nikos Skalkotos
                              map(lambda p: os.path.relpath(p, '/'), exclude))
381 d15368cc Nikos Skalkotos
                rsync.archive().run(self.out)
382 25b4d858 Nikos Skalkotos
383 bf15a033 Nikos Skalkotos
                self._replace_uuids(target, new_uuid)
384 bf15a033 Nikos Skalkotos
385 25b4d858 Nikos Skalkotos
            finally:
386 25b4d858 Nikos Skalkotos
                self._umount_all(target)
387 25b4d858 Nikos Skalkotos
                os.rmdir(target)
388 25b4d858 Nikos Skalkotos
        finally:
389 25b4d858 Nikos Skalkotos
            for dev in mapped.values():
390 25b4d858 Nikos Skalkotos
                self._unmap_partition(dev)
391 25b4d858 Nikos Skalkotos
            losetup('-d', loop)
392 25b4d858 Nikos Skalkotos
393 567891a6 Nikos Skalkotos
    def create_image(self, image):
394 f3845095 Nikos Skalkotos
        """Given an image filename, this method will create an image out of the
395 f3845095 Nikos Skalkotos
        running system.
396 f3845095 Nikos Skalkotos
        """
397 a939e3b8 Nikos Skalkotos
398 567891a6 Nikos Skalkotos
        size = self.disk.device.getLength() * self.disk.device.sectorSize
399 a939e3b8 Nikos Skalkotos
400 a939e3b8 Nikos Skalkotos
        # Create sparse file to host the image
401 567891a6 Nikos Skalkotos
        fd = os.open(image, os.O_WRONLY | os.O_CREAT)
402 567891a6 Nikos Skalkotos
        try:
403 567891a6 Nikos Skalkotos
            os.ftruncate(fd, size)
404 567891a6 Nikos Skalkotos
        finally:
405 567891a6 Nikos Skalkotos
            os.close(fd)
406 25b4d858 Nikos Skalkotos
407 25b4d858 Nikos Skalkotos
        self._create_partition_table(image)
408 bf15a033 Nikos Skalkotos
409 25b4d858 Nikos Skalkotos
        end_sector = self._shrink_partitions(image)
410 a939e3b8 Nikos Skalkotos
411 25b4d858 Nikos Skalkotos
        # Check if the available space is enough to host the image
412 25b4d858 Nikos Skalkotos
        dirname = os.path.dirname(image)
413 25b4d858 Nikos Skalkotos
        size = (end_sector + 1) * self.disk.device.sectorSize
414 25b4d858 Nikos Skalkotos
        self.out.output("Examining available space in %s ..." % dirname, False)
415 25b4d858 Nikos Skalkotos
        stat = os.statvfs(dirname)
416 25b4d858 Nikos Skalkotos
        available = stat.f_bavail * stat.f_frsize
417 25b4d858 Nikos Skalkotos
        if available <= size:
418 25b4d858 Nikos Skalkotos
            raise FatalError('Not enough space in %s to host the image' %
419 25b4d858 Nikos Skalkotos
                             dirname)
420 25b4d858 Nikos Skalkotos
        self.out.success("sufficient")
421 8eea5572 Nikos Skalkotos
422 25b4d858 Nikos Skalkotos
        self._create_filesystems(image)
423 8eea5572 Nikos Skalkotos
424 83fe59dd Nikos Skalkotos
        # Truncate image to the new size. I counldn't find a better way to do
425 83fe59dd Nikos Skalkotos
        # this. It seems that python's high level functions work in a different
426 83fe59dd Nikos Skalkotos
        # way.
427 83fe59dd Nikos Skalkotos
        fd = os.open(image, os.O_RDWR)
428 83fe59dd Nikos Skalkotos
        try:
429 83fe59dd Nikos Skalkotos
            os.ftruncate(fd, size)
430 83fe59dd Nikos Skalkotos
        finally:
431 83fe59dd Nikos Skalkotos
            os.close(fd)
432 83fe59dd Nikos Skalkotos
433 25b4d858 Nikos Skalkotos
        return image
434 9e4b4de2 Nikos Skalkotos
435 9e4b4de2 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :