Statistics
| Branch: | Tag: | Revision:

root / image_creator / bundle_volume.py @ b0376b3f

History | View | Annotate | Download (16.3 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 b0376b3f Nikos Skalkotos
import uuid
38 9517bf29 Nikos Skalkotos
from collections import namedtuple
39 27a4229d Nikos Skalkotos
40 8eea5572 Nikos Skalkotos
import parted
41 8eea5572 Nikos Skalkotos
42 74149d07 Nikos Skalkotos
from image_creator.rsync import Rsync
43 27a4229d Nikos Skalkotos
from image_creator.util import get_command
44 9e4b4de2 Nikos Skalkotos
from image_creator.util import FatalError
45 f3845095 Nikos Skalkotos
from image_creator.util import try_fail_repeat
46 c16850ae Nikos Skalkotos
from image_creator.util import free_space
47 b0376b3f Nikos Skalkotos
from image_creator.gpt import GPTPartitionTable
48 9e4b4de2 Nikos Skalkotos
49 27a4229d Nikos Skalkotos
findfs = get_command('findfs')
50 27a4229d Nikos Skalkotos
dd = get_command('dd')
51 8eea5572 Nikos Skalkotos
dmsetup = get_command('dmsetup')
52 25b4d858 Nikos Skalkotos
losetup = get_command('losetup')
53 25b4d858 Nikos Skalkotos
mount = get_command('mount')
54 25b4d858 Nikos Skalkotos
umount = get_command('umount')
55 bf15a033 Nikos Skalkotos
blkid = get_command('blkid')
56 25b4d858 Nikos Skalkotos
57 6228d45e Nikos Skalkotos
MKFS_OPTS = {'ext2': ['-F'],
58 6228d45e Nikos Skalkotos
             'ext3': ['-F'],
59 6228d45e Nikos Skalkotos
             'ext4': ['-F'],
60 6228d45e Nikos Skalkotos
             'reiserfs': ['-ff'],
61 6228d45e Nikos Skalkotos
             'btrfs': [],
62 6228d45e Nikos Skalkotos
             'minix': [],
63 6228d45e Nikos Skalkotos
             'xfs': ['-f'],
64 6228d45e Nikos Skalkotos
             'jfs': ['-f'],
65 6228d45e Nikos Skalkotos
             'ntfs': ['-F'],
66 6228d45e Nikos Skalkotos
             'msdos': [],
67 6228d45e Nikos Skalkotos
             'vfat': []}
68 9517bf29 Nikos Skalkotos
69 9517bf29 Nikos Skalkotos
70 f3845095 Nikos Skalkotos
class BundleVolume(object):
71 f3845095 Nikos Skalkotos
    """This class can be used to create an image out of the running system"""
72 27a4229d Nikos Skalkotos
73 c16850ae Nikos Skalkotos
    def __init__(self, out, meta, tmp=None):
74 f3845095 Nikos Skalkotos
        """Create an instance of the BundleVolume class."""
75 8eea5572 Nikos Skalkotos
        self.out = out
76 8eea5572 Nikos Skalkotos
        self.meta = meta
77 c16850ae Nikos Skalkotos
        self.tmp = tmp
78 27a4229d Nikos Skalkotos
79 a939e3b8 Nikos Skalkotos
        self.out.output('Searching for root device ...', False)
80 8eea5572 Nikos Skalkotos
        root = self._get_root_partition()
81 27a4229d Nikos Skalkotos
82 8eea5572 Nikos Skalkotos
        if root.startswith("UUID=") or root.startswith("LABEL="):
83 25b4d858 Nikos Skalkotos
            root = findfs(root).stdout.strip()
84 27a4229d Nikos Skalkotos
85 25b4d858 Nikos Skalkotos
        if not re.match('/dev/[hsv]d[a-z][1-9]*$', root):
86 25b4d858 Nikos Skalkotos
            raise FatalError("Don't know how to handle root device: %s" % root)
87 9517bf29 Nikos Skalkotos
88 25b4d858 Nikos Skalkotos
        out.success(root)
89 9517bf29 Nikos Skalkotos
90 25b4d858 Nikos Skalkotos
        disk_file = re.split('[0-9]', root)[0]
91 25b4d858 Nikos Skalkotos
        device = parted.Device(disk_file)
92 25b4d858 Nikos Skalkotos
        self.disk = parted.Disk(device)
93 9517bf29 Nikos Skalkotos
94 a939e3b8 Nikos Skalkotos
    def _read_fstable(self, f):
95 25b4d858 Nikos Skalkotos
96 8eea5572 Nikos Skalkotos
        if not os.path.isfile(f):
97 8eea5572 Nikos Skalkotos
            raise FatalError("Unable to open: `%s'. File is missing." % f)
98 27a4229d Nikos Skalkotos
99 74149d07 Nikos Skalkotos
        FileSystemTableEntry = namedtuple('FileSystemTableEntry',
100 6228d45e Nikos Skalkotos
                                          'dev mpoint fs opts freq passno')
101 8eea5572 Nikos Skalkotos
        with open(f) as table:
102 8eea5572 Nikos Skalkotos
            for line in iter(table):
103 8eea5572 Nikos Skalkotos
                entry = line.split('#')[0].strip().split()
104 8eea5572 Nikos Skalkotos
                if len(entry) != 6:
105 8eea5572 Nikos Skalkotos
                    continue
106 74149d07 Nikos Skalkotos
                yield FileSystemTableEntry(*entry)
107 27a4229d Nikos Skalkotos
108 a939e3b8 Nikos Skalkotos
    def _get_root_partition(self):
109 8eea5572 Nikos Skalkotos
        for entry in self._read_fstable('/etc/fstab'):
110 8eea5572 Nikos Skalkotos
            if entry.mpoint == '/':
111 8eea5572 Nikos Skalkotos
                return entry.dev
112 27a4229d Nikos Skalkotos
113 8eea5572 Nikos Skalkotos
        raise FatalError("Unable to find root device in /etc/fstab")
114 27a4229d Nikos Skalkotos
115 a939e3b8 Nikos Skalkotos
    def _is_mpoint(self, path):
116 a939e3b8 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
117 8eea5572 Nikos Skalkotos
            if entry.mpoint == path:
118 8eea5572 Nikos Skalkotos
                return True
119 8eea5572 Nikos Skalkotos
        return False
120 27a4229d Nikos Skalkotos
121 25b4d858 Nikos Skalkotos
    def _get_mount_options(self, device):
122 a939e3b8 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
123 8eea5572 Nikos Skalkotos
            if not entry.dev.startswith('/'):
124 8eea5572 Nikos Skalkotos
                continue
125 9517bf29 Nikos Skalkotos
126 8eea5572 Nikos Skalkotos
            if os.path.realpath(entry.dev) == os.path.realpath(device):
127 8eea5572 Nikos Skalkotos
                return entry
128 9517bf29 Nikos Skalkotos
129 25b4d858 Nikos Skalkotos
        return None
130 9517bf29 Nikos Skalkotos
131 25b4d858 Nikos Skalkotos
    def _create_partition_table(self, image):
132 8eea5572 Nikos Skalkotos
133 8eea5572 Nikos Skalkotos
        # Copy the MBR and the space between the MBR and the first partition.
134 b0376b3f Nikos Skalkotos
        # In msdos partitons tables Grub Stage 1.5 is located there.
135 b0376b3f Nikos Skalkotos
        # In gpt partition tables the Primary GPT Header is 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 b0376b3f Nikos Skalkotos
        if self.disk.type == 'gpt':
143 b0376b3f Nikos Skalkotos
            # Copy the Secondary GPT Header
144 b0376b3f Nikos Skalkotos
            table = GPTPartitionTable(self.disk.device.path)
145 b0376b3f Nikos Skalkotos
            dd('if=%s' % self.disk.device.path, 'of=%s' % image,
146 b0376b3f Nikos Skalkotos
            'bs=%d' % self.disk.device.sectorSize, 'conv=notrunc',
147 b0376b3f Nikos Skalkotos
            'seek=%d' % table.primary.last_usable_lba,
148 b0376b3f Nikos Skalkotos
            'skip=%d' % table.primary.last_usable_lba)
149 b0376b3f Nikos Skalkotos
150 8eea5572 Nikos Skalkotos
        # Create the Extended boot records (EBRs) in the image
151 25b4d858 Nikos Skalkotos
        extended = self.disk.getExtendedPartition()
152 8eea5572 Nikos Skalkotos
        if not extended:
153 8eea5572 Nikos Skalkotos
            return
154 8eea5572 Nikos Skalkotos
155 8eea5572 Nikos Skalkotos
        # Extended boot records precede the logical partitions they describe
156 25b4d858 Nikos Skalkotos
        logical = self.disk.getLogicalPartitions()
157 8eea5572 Nikos Skalkotos
        start = extended.geometry.start
158 8eea5572 Nikos Skalkotos
        for i in range(len(logical)):
159 8eea5572 Nikos Skalkotos
            end = logical[i].geometry.start - 1
160 25b4d858 Nikos Skalkotos
            dd('if=%s' % self.disk.device.path, 'of=%s' % image,
161 8eea5572 Nikos Skalkotos
               'count=%d' % (end - start + 1), 'conv=notrunc',
162 8eea5572 Nikos Skalkotos
               'seek=%d' % start, 'skip=%d' % start)
163 8eea5572 Nikos Skalkotos
            start = logical[i].geometry.end + 1
164 8eea5572 Nikos Skalkotos
165 25b4d858 Nikos Skalkotos
    def _get_partitions(self, disk):
166 25b4d858 Nikos Skalkotos
        Partition = namedtuple('Partition', 'num start end type fs')
167 8eea5572 Nikos Skalkotos
168 8eea5572 Nikos Skalkotos
        partitions = []
169 25b4d858 Nikos Skalkotos
        for p in disk.partitions:
170 25b4d858 Nikos Skalkotos
            num = p.number
171 25b4d858 Nikos Skalkotos
            start = p.geometry.start
172 25b4d858 Nikos Skalkotos
            end = p.geometry.end
173 25b4d858 Nikos Skalkotos
            ptype = p.type
174 25b4d858 Nikos Skalkotos
            fs = p.fileSystem.type if p.fileSystem is not None else ''
175 25b4d858 Nikos Skalkotos
            partitions.append(Partition(num, start, end, ptype, fs))
176 25b4d858 Nikos Skalkotos
177 25b4d858 Nikos Skalkotos
        return partitions
178 25b4d858 Nikos Skalkotos
179 25b4d858 Nikos Skalkotos
    def _shrink_partitions(self, image):
180 8eea5572 Nikos Skalkotos
181 e62eb365 Nikos Skalkotos
        new_end = self.disk.device.length
182 25b4d858 Nikos Skalkotos
183 b0376b3f Nikos Skalkotos
        image_disk = parted.Disk(parted.Device(image))
184 a939e3b8 Nikos Skalkotos
185 a939e3b8 Nikos Skalkotos
        is_extended = lambda p: p.type == parted.PARTITION_EXTENDED
186 a939e3b8 Nikos Skalkotos
        is_logical = lambda p: p.type == parted.PARTITION_LOGICAL
187 a939e3b8 Nikos Skalkotos
188 25b4d858 Nikos Skalkotos
        partitions = self._get_partitions(self.disk)
189 a939e3b8 Nikos Skalkotos
190 a939e3b8 Nikos Skalkotos
        last = partitions[-1]
191 a939e3b8 Nikos Skalkotos
        if last.fs == 'linux-swap(v1)':
192 a939e3b8 Nikos Skalkotos
            MB = 2 ** 20
193 25b4d858 Nikos Skalkotos
            size = (last.end - last.start + 1) * self.disk.device.sectorSize
194 a939e3b8 Nikos Skalkotos
            self.meta['SWAP'] = "%d:%s" % (last.num, (size + MB - 1) // MB)
195 a939e3b8 Nikos Skalkotos
196 a939e3b8 Nikos Skalkotos
            image_disk.deletePartition(
197 a939e3b8 Nikos Skalkotos
                image_disk.getPartitionBySector(last.start))
198 b0376b3f Nikos Skalkotos
            image_disk.commitToDevice()
199 a939e3b8 Nikos Skalkotos
200 a939e3b8 Nikos Skalkotos
            if is_logical(last) and last.num == 5:
201 a939e3b8 Nikos Skalkotos
                extended = image_disk.getExtendedPartition()
202 a939e3b8 Nikos Skalkotos
                image_disk.deletePartition(extended)
203 b0376b3f Nikos Skalkotos
                image_disk.commitToDevice()
204 a939e3b8 Nikos Skalkotos
                partitions.remove(filter(is_extended, partitions)[0])
205 a939e3b8 Nikos Skalkotos
206 a939e3b8 Nikos Skalkotos
            partitions.remove(last)
207 a939e3b8 Nikos Skalkotos
            last = partitions[-1]
208 a939e3b8 Nikos Skalkotos
209 b0376b3f Nikos Skalkotos
            new_end = last.end
210 a939e3b8 Nikos Skalkotos
211 25b4d858 Nikos Skalkotos
        mount_options = self._get_mount_options(
212 6228d45e Nikos Skalkotos
            self.disk.getPartitionBySector(last.start).path)
213 74149d07 Nikos Skalkotos
        if mount_options is not None:
214 25b4d858 Nikos Skalkotos
            stat = os.statvfs(mount_options.mpoint)
215 25b4d858 Nikos Skalkotos
            # Shrink the last partition. The new size should be the size of the
216 25b4d858 Nikos Skalkotos
            # occupied blocks
217 a939e3b8 Nikos Skalkotos
            blcks = stat.f_blocks - stat.f_bavail
218 25b4d858 Nikos Skalkotos
            new_size = (blcks * stat.f_frsize) // self.disk.device.sectorSize
219 a939e3b8 Nikos Skalkotos
220 a939e3b8 Nikos Skalkotos
            # Add 10% just to be on the safe side
221 a939e3b8 Nikos Skalkotos
            part_end = last.start + (new_size * 11) // 10
222 4a35faa5 Nikos Skalkotos
            # Align to 2048
223 a939e3b8 Nikos Skalkotos
            part_end = ((part_end + 2047) // 2048) * 2048
224 a939e3b8 Nikos Skalkotos
225 a939e3b8 Nikos Skalkotos
            image_disk.setPartitionGeometry(
226 a939e3b8 Nikos Skalkotos
                image_disk.getPartitionBySector(last.start),
227 a939e3b8 Nikos Skalkotos
                parted.Constraint(device=image_disk.device),
228 83fe59dd Nikos Skalkotos
                start=last.start, end=part_end)
229 b0376b3f Nikos Skalkotos
            image_disk.commitToDevice()
230 a939e3b8 Nikos Skalkotos
231 25b4d858 Nikos Skalkotos
            # Parted may have changed this for better alignment
232 25b4d858 Nikos Skalkotos
            part_end = image_disk.getPartitionBySector(last.start).geometry.end
233 25b4d858 Nikos Skalkotos
            last = last._replace(end=part_end)
234 25b4d858 Nikos Skalkotos
            partitions[-1] = last
235 25b4d858 Nikos Skalkotos
236 b0376b3f Nikos Skalkotos
            new_end = part_end
237 25b4d858 Nikos Skalkotos
238 a939e3b8 Nikos Skalkotos
            if last.type == parted.PARTITION_LOGICAL:
239 a939e3b8 Nikos Skalkotos
                # Fix the extended partition
240 b0376b3f Nikos Skalkotos
                image_disk.minimizeExtendedPartition()
241 8eea5572 Nikos Skalkotos
242 b0376b3f Nikos Skalkotos
        return (new_end, self._get_partitions(image_disk))
243 8eea5572 Nikos Skalkotos
244 25b4d858 Nikos Skalkotos
    def _map_partition(self, dev, num, start, end):
245 b0376b3f Nikos Skalkotos
        name = os.path.basename(dev) + "_" + uuid.uuid4().hex
246 25b4d858 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
247 25b4d858 Nikos Skalkotos
        try:
248 25b4d858 Nikos Skalkotos
            size = end - start + 1
249 25b4d858 Nikos Skalkotos
            os.write(tablefd, "0 %d linear %s %d" % (size, dev, start))
250 25b4d858 Nikos Skalkotos
            dmsetup('create', "%sp%d" % (name, num), table)
251 25b4d858 Nikos Skalkotos
        finally:
252 25b4d858 Nikos Skalkotos
            os.unlink(table)
253 8eea5572 Nikos Skalkotos
254 25b4d858 Nikos Skalkotos
        return "/dev/mapper/%sp%d" % (name, num)
255 8eea5572 Nikos Skalkotos
256 25b4d858 Nikos Skalkotos
    def _unmap_partition(self, dev):
257 25b4d858 Nikos Skalkotos
        if not os.path.exists(dev):
258 25b4d858 Nikos Skalkotos
            return
259 8eea5572 Nikos Skalkotos
260 f3845095 Nikos Skalkotos
        try_fail_repeat(dmsetup, 'remove', dev.split('/dev/mapper/')[1])
261 8eea5572 Nikos Skalkotos
262 25b4d858 Nikos Skalkotos
    def _mount(self, target, devs):
263 25b4d858 Nikos Skalkotos
264 25b4d858 Nikos Skalkotos
        devs.sort(key=lambda d: d[1])
265 25b4d858 Nikos Skalkotos
        for dev, mpoint in devs:
266 25b4d858 Nikos Skalkotos
            absmpoint = os.path.abspath(target + mpoint)
267 25b4d858 Nikos Skalkotos
            if not os.path.exists(absmpoint):
268 25b4d858 Nikos Skalkotos
                os.makedirs(absmpoint)
269 25b4d858 Nikos Skalkotos
            mount(dev, absmpoint)
270 25b4d858 Nikos Skalkotos
271 25b4d858 Nikos Skalkotos
    def _umount_all(self, target):
272 25b4d858 Nikos Skalkotos
        mpoints = []
273 25b4d858 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
274 25b4d858 Nikos Skalkotos
            if entry.mpoint.startswith(os.path.abspath(target)):
275 25b4d858 Nikos Skalkotos
                    mpoints.append(entry.mpoint)
276 74149d07 Nikos Skalkotos
277 25b4d858 Nikos Skalkotos
        mpoints.sort()
278 25b4d858 Nikos Skalkotos
        for mpoint in reversed(mpoints):
279 f3845095 Nikos Skalkotos
            try_fail_repeat(umount, mpoint)
280 25b4d858 Nikos Skalkotos
281 74149d07 Nikos Skalkotos
    def _to_exclude(self):
282 bf15a033 Nikos Skalkotos
        excluded = ['/tmp', '/var/tmp']
283 c16850ae Nikos Skalkotos
        if self.tmp is not None:
284 c16850ae Nikos Skalkotos
            excluded.append(self.tmp)
285 74149d07 Nikos Skalkotos
        local_filesystems = MKFS_OPTS.keys() + ['rootfs']
286 74149d07 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
287 74149d07 Nikos Skalkotos
            if entry.fs in local_filesystems:
288 74149d07 Nikos Skalkotos
                continue
289 74149d07 Nikos Skalkotos
290 74149d07 Nikos Skalkotos
            mpoint = entry.mpoint
291 74149d07 Nikos Skalkotos
            if mpoint in excluded:
292 74149d07 Nikos Skalkotos
                continue
293 74149d07 Nikos Skalkotos
294 6228d45e Nikos Skalkotos
            descendants = filter(
295 6228d45e Nikos Skalkotos
                lambda p: p.startswith(mpoint + '/'), excluded)
296 74149d07 Nikos Skalkotos
            if len(descendants):
297 74149d07 Nikos Skalkotos
                for d in descendants:
298 74149d07 Nikos Skalkotos
                    excluded.remove(d)
299 74149d07 Nikos Skalkotos
                excluded.append(mpoint)
300 74149d07 Nikos Skalkotos
                continue
301 74149d07 Nikos Skalkotos
302 74149d07 Nikos Skalkotos
            dirname = mpoint
303 74149d07 Nikos Skalkotos
            basename = ''
304 74149d07 Nikos Skalkotos
            found_ancestor = False
305 74149d07 Nikos Skalkotos
            while dirname != '/':
306 74149d07 Nikos Skalkotos
                (dirname, basename) = os.path.split(dirname)
307 74149d07 Nikos Skalkotos
                if dirname in excluded:
308 74149d07 Nikos Skalkotos
                    found_ancestor = True
309 74149d07 Nikos Skalkotos
                    break
310 74149d07 Nikos Skalkotos
311 74149d07 Nikos Skalkotos
            if not found_ancestor:
312 74149d07 Nikos Skalkotos
                excluded.append(mpoint)
313 74149d07 Nikos Skalkotos
314 252b2b5d Nikos Skalkotos
        return excluded
315 74149d07 Nikos Skalkotos
316 bf15a033 Nikos Skalkotos
    def _replace_uuids(self, target, new_uuid):
317 bf15a033 Nikos Skalkotos
318 bf15a033 Nikos Skalkotos
        files = ['/etc/fstab',
319 bf15a033 Nikos Skalkotos
                 '/boot/grub/grub.cfg',
320 bf15a033 Nikos Skalkotos
                 '/boot/grub/menu.lst',
321 bf15a033 Nikos Skalkotos
                 '/boot/grub/grub.conf']
322 bf15a033 Nikos Skalkotos
323 b0376b3f Nikos Skalkotos
        orig = {}
324 b0376b3f Nikos Skalkotos
        for p in self.disk.partitions:
325 b0376b3f Nikos Skalkotos
            if p.number in new_uuid.keys():
326 b0376b3f Nikos Skalkotos
                orig[p.number] = \
327 b0376b3f Nikos Skalkotos
                    blkid('-s', 'UUID', '-o', 'value', p.path).stdout.strip()
328 bf15a033 Nikos Skalkotos
329 bf15a033 Nikos Skalkotos
        for f in map(lambda f: target + f, files):
330 bf15a033 Nikos Skalkotos
            if not os.path.exists(f):
331 bf15a033 Nikos Skalkotos
                continue
332 bf15a033 Nikos Skalkotos
333 bf15a033 Nikos Skalkotos
            with open(f, 'r') as src:
334 bf15a033 Nikos Skalkotos
                lines = src.readlines()
335 bf15a033 Nikos Skalkotos
            with open(f, 'w') as dest:
336 bf15a033 Nikos Skalkotos
                for line in lines:
337 bf15a033 Nikos Skalkotos
                    for i, uuid in new_uuid.items():
338 bf15a033 Nikos Skalkotos
                        line = re.sub(orig[i], uuid, line)
339 bf15a033 Nikos Skalkotos
                    dest.write(line)
340 bf15a033 Nikos Skalkotos
341 b0376b3f Nikos Skalkotos
    def _create_filesystems(self, image, partitions):
342 74149d07 Nikos Skalkotos
343 bf15a033 Nikos Skalkotos
        filesystem = {}
344 25b4d858 Nikos Skalkotos
        for p in self.disk.partitions:
345 bf15a033 Nikos Skalkotos
            filesystem[p.number] = self._get_mount_options(p.path)
346 25b4d858 Nikos Skalkotos
347 bf15a033 Nikos Skalkotos
        unmounted = filter(lambda p: filesystem[p.num] is None, partitions)
348 bf15a033 Nikos Skalkotos
        mounted = filter(lambda p: filesystem[p.num] is not None, partitions)
349 25b4d858 Nikos Skalkotos
350 25b4d858 Nikos Skalkotos
        # For partitions that are not mounted right now, we can simply dd them
351 25b4d858 Nikos Skalkotos
        # into the image.
352 25b4d858 Nikos Skalkotos
        for p in unmounted:
353 25b4d858 Nikos Skalkotos
            dd('if=%s' % self.disk.device.path, 'of=%s' % image,
354 25b4d858 Nikos Skalkotos
               'count=%d' % (p.end - p.start + 1), 'conv=notrunc',
355 25b4d858 Nikos Skalkotos
               'seek=%d' % p.start, 'skip=%d' % p.start)
356 25b4d858 Nikos Skalkotos
357 25b4d858 Nikos Skalkotos
        loop = str(losetup('-f', '--show', image)).strip()
358 25b4d858 Nikos Skalkotos
        mapped = {}
359 25b4d858 Nikos Skalkotos
        try:
360 25b4d858 Nikos Skalkotos
            for p in mounted:
361 74149d07 Nikos Skalkotos
                i = p.num
362 25b4d858 Nikos Skalkotos
                mapped[i] = self._map_partition(loop, i, p.start, p.end)
363 25b4d858 Nikos Skalkotos
364 bf15a033 Nikos Skalkotos
            new_uuid = {}
365 25b4d858 Nikos Skalkotos
            # Create the file systems
366 25b4d858 Nikos Skalkotos
            for i, dev in mapped.iteritems():
367 bf15a033 Nikos Skalkotos
                fs = filesystem[i].fs
368 25b4d858 Nikos Skalkotos
                self.out.output('Creating %s filesystem on partition %d ... ' %
369 6228d45e Nikos Skalkotos
                                (fs, i), False)
370 25b4d858 Nikos Skalkotos
                get_command('mkfs.%s' % fs)(*(MKFS_OPTS[fs] + [dev]))
371 25b4d858 Nikos Skalkotos
                self.out.success('done')
372 6228d45e Nikos Skalkotos
                new_uuid[i] = blkid(
373 6228d45e Nikos Skalkotos
                    '-s', 'UUID', '-o', 'value', dev).stdout.strip()
374 25b4d858 Nikos Skalkotos
375 25b4d858 Nikos Skalkotos
            target = tempfile.mkdtemp()
376 25b4d858 Nikos Skalkotos
            try:
377 25b4d858 Nikos Skalkotos
                absmpoints = self._mount(target,
378 6228d45e Nikos Skalkotos
                                         [(mapped[i], filesystem[i].mpoint)
379 6228d45e Nikos Skalkotos
                                         for i in mapped.keys()])
380 252b2b5d Nikos Skalkotos
                excluded = self._to_exclude()
381 1fa75c4c Nikos Skalkotos
382 1fa75c4c Nikos Skalkotos
                rsync = Rsync(self.out)
383 1fa75c4c Nikos Skalkotos
384 1fa75c4c Nikos Skalkotos
                # Excluded paths need to be relative to the source
385 29274f83 Nikos Skalkotos
                for excl in map(lambda p: p[1:], excluded + [image]):
386 1fa75c4c Nikos Skalkotos
                    rsync.exclude(excl)
387 1fa75c4c Nikos Skalkotos
388 1fa75c4c Nikos Skalkotos
                rsync.archive().hard_links().xattrs().sparse().acls()
389 f2ddf4db Nikos Skalkotos
                rsync.run('/', target, 'host', 'temporary image')
390 25b4d858 Nikos Skalkotos
391 252b2b5d Nikos Skalkotos
                # Create missing mountpoints. Since they are mountpoints, we
392 252b2b5d Nikos Skalkotos
                # cannot determine the ownership and the mode of the real
393 252b2b5d Nikos Skalkotos
                # directory. Make them inherit those properties from their
394 252b2b5d Nikos Skalkotos
                # parent dir
395 252b2b5d Nikos Skalkotos
                for excl in excluded:
396 252b2b5d Nikos Skalkotos
                   dirname = os.path.dirname(excl)
397 252b2b5d Nikos Skalkotos
                   stat = os.stat(dirname)
398 252b2b5d Nikos Skalkotos
                   os.mkdir(target + excl, stat.st_mode)
399 252b2b5d Nikos Skalkotos
                   os.chown(target + excl, stat.st_uid, stat.st_gid)
400 252b2b5d Nikos Skalkotos
401 6228d45e Nikos Skalkotos
                # We need to replace the old UUID referencies with the new
402 6228d45e Nikos Skalkotos
                # ones in grub configuration files and /etc/fstab for file
403 6228d45e Nikos Skalkotos
                # systems that have been recreated.
404 bf15a033 Nikos Skalkotos
                self._replace_uuids(target, new_uuid)
405 bf15a033 Nikos Skalkotos
406 25b4d858 Nikos Skalkotos
            finally:
407 25b4d858 Nikos Skalkotos
                self._umount_all(target)
408 25b4d858 Nikos Skalkotos
                os.rmdir(target)
409 25b4d858 Nikos Skalkotos
        finally:
410 25b4d858 Nikos Skalkotos
            for dev in mapped.values():
411 25b4d858 Nikos Skalkotos
                self._unmap_partition(dev)
412 25b4d858 Nikos Skalkotos
            losetup('-d', loop)
413 25b4d858 Nikos Skalkotos
414 567891a6 Nikos Skalkotos
    def create_image(self, image):
415 f3845095 Nikos Skalkotos
        """Given an image filename, this method will create an image out of the
416 f3845095 Nikos Skalkotos
        running system.
417 f3845095 Nikos Skalkotos
        """
418 a939e3b8 Nikos Skalkotos
419 e62eb365 Nikos Skalkotos
        size = self.disk.device.length * self.disk.device.sectorSize
420 a939e3b8 Nikos Skalkotos
421 a939e3b8 Nikos Skalkotos
        # Create sparse file to host the image
422 567891a6 Nikos Skalkotos
        fd = os.open(image, os.O_WRONLY | os.O_CREAT)
423 567891a6 Nikos Skalkotos
        try:
424 567891a6 Nikos Skalkotos
            os.ftruncate(fd, size)
425 567891a6 Nikos Skalkotos
        finally:
426 567891a6 Nikos Skalkotos
            os.close(fd)
427 25b4d858 Nikos Skalkotos
428 25b4d858 Nikos Skalkotos
        self._create_partition_table(image)
429 b0376b3f Nikos Skalkotos
        end_sector, partitions = self._shrink_partitions(image)
430 b0376b3f Nikos Skalkotos
431 b0376b3f Nikos Skalkotos
        if self.disk.type == 'gpt':
432 b0376b3f Nikos Skalkotos
            old_size = size
433 b0376b3f Nikos Skalkotos
            size = (end_sector + 1) * self.disk.device.sectorSize
434 b0376b3f Nikos Skalkotos
            ptable = GPTPartitionTable(image)
435 b0376b3f Nikos Skalkotos
            size = ptable.shrink(size, old_size)
436 b0376b3f Nikos Skalkotos
        else:
437 b0376b3f Nikos Skalkotos
            # Alighn to 2048
438 b0376b3f Nikos Skalkotos
            end_sector = ((end_sector + 2047) // 2048) * 2048
439 b0376b3f Nikos Skalkotos
            size = (end_sector + 1) * self.disk.device.sectorSize
440 fa39c7e3 Nikos Skalkotos
441 fa39c7e3 Nikos Skalkotos
        # Truncate image to the new size.
442 fa39c7e3 Nikos Skalkotos
        fd = os.open(image, os.O_RDWR)
443 fa39c7e3 Nikos Skalkotos
        try:
444 fa39c7e3 Nikos Skalkotos
            os.ftruncate(fd, size)
445 fa39c7e3 Nikos Skalkotos
        finally:
446 fa39c7e3 Nikos Skalkotos
            os.close(fd)
447 fa39c7e3 Nikos Skalkotos
448 25b4d858 Nikos Skalkotos
        # Check if the available space is enough to host the image
449 25b4d858 Nikos Skalkotos
        dirname = os.path.dirname(image)
450 1294501a Nikos Skalkotos
        self.out.output("Examining available space ...", False)
451 c16850ae Nikos Skalkotos
        if free_space(dirname) <= size:
452 f2ddf4db Nikos Skalkotos
            raise FatalError('Not enough space under %s to host the image' %
453 25b4d858 Nikos Skalkotos
                             dirname)
454 25b4d858 Nikos Skalkotos
        self.out.success("sufficient")
455 8eea5572 Nikos Skalkotos
456 b0376b3f Nikos Skalkotos
        self._create_filesystems(image, partitions)
457 8eea5572 Nikos Skalkotos
458 25b4d858 Nikos Skalkotos
        return image
459 9e4b4de2 Nikos Skalkotos
460 9e4b4de2 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :