Statistics
| Branch: | Tag: | Revision:

root / image_creator / bundle_volume.py @ 56884b64

History | View | Annotate | Download (16.6 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 c631f3e4 Nikos Skalkotos
            # Make sure the partition starts where the old partition started.
226 c631f3e4 Nikos Skalkotos
            constraint = parted.Constraint(device=image_disk.device)
227 c631f3e4 Nikos Skalkotos
            constraint.startRange = parted.Geometry(device=image_disk.device,
228 c631f3e4 Nikos Skalkotos
                                                    start=last.start, length=1)
229 c631f3e4 Nikos Skalkotos
230 a939e3b8 Nikos Skalkotos
            image_disk.setPartitionGeometry(
231 c631f3e4 Nikos Skalkotos
                image_disk.getPartitionBySector(last.start), constraint,
232 83fe59dd Nikos Skalkotos
                start=last.start, end=part_end)
233 b0376b3f Nikos Skalkotos
            image_disk.commitToDevice()
234 a939e3b8 Nikos Skalkotos
235 25b4d858 Nikos Skalkotos
            # Parted may have changed this for better alignment
236 25b4d858 Nikos Skalkotos
            part_end = image_disk.getPartitionBySector(last.start).geometry.end
237 25b4d858 Nikos Skalkotos
            last = last._replace(end=part_end)
238 25b4d858 Nikos Skalkotos
            partitions[-1] = last
239 25b4d858 Nikos Skalkotos
240 b0376b3f Nikos Skalkotos
            new_end = part_end
241 25b4d858 Nikos Skalkotos
242 a939e3b8 Nikos Skalkotos
            if last.type == parted.PARTITION_LOGICAL:
243 a939e3b8 Nikos Skalkotos
                # Fix the extended partition
244 b0376b3f Nikos Skalkotos
                image_disk.minimizeExtendedPartition()
245 8eea5572 Nikos Skalkotos
246 b0376b3f Nikos Skalkotos
        return (new_end, self._get_partitions(image_disk))
247 8eea5572 Nikos Skalkotos
248 25b4d858 Nikos Skalkotos
    def _map_partition(self, dev, num, start, end):
249 b0376b3f Nikos Skalkotos
        name = os.path.basename(dev) + "_" + uuid.uuid4().hex
250 25b4d858 Nikos Skalkotos
        tablefd, table = tempfile.mkstemp()
251 25b4d858 Nikos Skalkotos
        try:
252 25b4d858 Nikos Skalkotos
            size = end - start + 1
253 25b4d858 Nikos Skalkotos
            os.write(tablefd, "0 %d linear %s %d" % (size, dev, start))
254 25b4d858 Nikos Skalkotos
            dmsetup('create', "%sp%d" % (name, num), table)
255 25b4d858 Nikos Skalkotos
        finally:
256 25b4d858 Nikos Skalkotos
            os.unlink(table)
257 8eea5572 Nikos Skalkotos
258 25b4d858 Nikos Skalkotos
        return "/dev/mapper/%sp%d" % (name, num)
259 8eea5572 Nikos Skalkotos
260 25b4d858 Nikos Skalkotos
    def _unmap_partition(self, dev):
261 25b4d858 Nikos Skalkotos
        if not os.path.exists(dev):
262 25b4d858 Nikos Skalkotos
            return
263 8eea5572 Nikos Skalkotos
264 f3845095 Nikos Skalkotos
        try_fail_repeat(dmsetup, 'remove', dev.split('/dev/mapper/')[1])
265 8eea5572 Nikos Skalkotos
266 25b4d858 Nikos Skalkotos
    def _mount(self, target, devs):
267 25b4d858 Nikos Skalkotos
268 25b4d858 Nikos Skalkotos
        devs.sort(key=lambda d: d[1])
269 25b4d858 Nikos Skalkotos
        for dev, mpoint in devs:
270 25b4d858 Nikos Skalkotos
            absmpoint = os.path.abspath(target + mpoint)
271 25b4d858 Nikos Skalkotos
            if not os.path.exists(absmpoint):
272 25b4d858 Nikos Skalkotos
                os.makedirs(absmpoint)
273 25b4d858 Nikos Skalkotos
            mount(dev, absmpoint)
274 25b4d858 Nikos Skalkotos
275 25b4d858 Nikos Skalkotos
    def _umount_all(self, target):
276 25b4d858 Nikos Skalkotos
        mpoints = []
277 25b4d858 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
278 25b4d858 Nikos Skalkotos
            if entry.mpoint.startswith(os.path.abspath(target)):
279 25b4d858 Nikos Skalkotos
                    mpoints.append(entry.mpoint)
280 74149d07 Nikos Skalkotos
281 25b4d858 Nikos Skalkotos
        mpoints.sort()
282 25b4d858 Nikos Skalkotos
        for mpoint in reversed(mpoints):
283 f3845095 Nikos Skalkotos
            try_fail_repeat(umount, mpoint)
284 25b4d858 Nikos Skalkotos
285 74149d07 Nikos Skalkotos
    def _to_exclude(self):
286 bf15a033 Nikos Skalkotos
        excluded = ['/tmp', '/var/tmp']
287 c16850ae Nikos Skalkotos
        if self.tmp is not None:
288 c16850ae Nikos Skalkotos
            excluded.append(self.tmp)
289 74149d07 Nikos Skalkotos
        local_filesystems = MKFS_OPTS.keys() + ['rootfs']
290 74149d07 Nikos Skalkotos
        for entry in self._read_fstable('/proc/mounts'):
291 74149d07 Nikos Skalkotos
            if entry.fs in local_filesystems:
292 74149d07 Nikos Skalkotos
                continue
293 74149d07 Nikos Skalkotos
294 74149d07 Nikos Skalkotos
            mpoint = entry.mpoint
295 74149d07 Nikos Skalkotos
            if mpoint in excluded:
296 74149d07 Nikos Skalkotos
                continue
297 74149d07 Nikos Skalkotos
298 6228d45e Nikos Skalkotos
            descendants = filter(
299 6228d45e Nikos Skalkotos
                lambda p: p.startswith(mpoint + '/'), excluded)
300 74149d07 Nikos Skalkotos
            if len(descendants):
301 74149d07 Nikos Skalkotos
                for d in descendants:
302 74149d07 Nikos Skalkotos
                    excluded.remove(d)
303 74149d07 Nikos Skalkotos
                excluded.append(mpoint)
304 74149d07 Nikos Skalkotos
                continue
305 74149d07 Nikos Skalkotos
306 74149d07 Nikos Skalkotos
            dirname = mpoint
307 74149d07 Nikos Skalkotos
            basename = ''
308 74149d07 Nikos Skalkotos
            found_ancestor = False
309 74149d07 Nikos Skalkotos
            while dirname != '/':
310 74149d07 Nikos Skalkotos
                (dirname, basename) = os.path.split(dirname)
311 74149d07 Nikos Skalkotos
                if dirname in excluded:
312 74149d07 Nikos Skalkotos
                    found_ancestor = True
313 74149d07 Nikos Skalkotos
                    break
314 74149d07 Nikos Skalkotos
315 74149d07 Nikos Skalkotos
            if not found_ancestor:
316 74149d07 Nikos Skalkotos
                excluded.append(mpoint)
317 74149d07 Nikos Skalkotos
318 252b2b5d Nikos Skalkotos
        return excluded
319 74149d07 Nikos Skalkotos
320 bf15a033 Nikos Skalkotos
    def _replace_uuids(self, target, new_uuid):
321 bf15a033 Nikos Skalkotos
322 bf15a033 Nikos Skalkotos
        files = ['/etc/fstab',
323 bf15a033 Nikos Skalkotos
                 '/boot/grub/grub.cfg',
324 bf15a033 Nikos Skalkotos
                 '/boot/grub/menu.lst',
325 bf15a033 Nikos Skalkotos
                 '/boot/grub/grub.conf']
326 bf15a033 Nikos Skalkotos
327 b0376b3f Nikos Skalkotos
        orig = {}
328 b0376b3f Nikos Skalkotos
        for p in self.disk.partitions:
329 b0376b3f Nikos Skalkotos
            if p.number in new_uuid.keys():
330 b0376b3f Nikos Skalkotos
                orig[p.number] = \
331 b0376b3f Nikos Skalkotos
                    blkid('-s', 'UUID', '-o', 'value', p.path).stdout.strip()
332 bf15a033 Nikos Skalkotos
333 bf15a033 Nikos Skalkotos
        for f in map(lambda f: target + f, files):
334 bf15a033 Nikos Skalkotos
            if not os.path.exists(f):
335 bf15a033 Nikos Skalkotos
                continue
336 bf15a033 Nikos Skalkotos
337 bf15a033 Nikos Skalkotos
            with open(f, 'r') as src:
338 bf15a033 Nikos Skalkotos
                lines = src.readlines()
339 bf15a033 Nikos Skalkotos
            with open(f, 'w') as dest:
340 bf15a033 Nikos Skalkotos
                for line in lines:
341 bf15a033 Nikos Skalkotos
                    for i, uuid in new_uuid.items():
342 bf15a033 Nikos Skalkotos
                        line = re.sub(orig[i], uuid, line)
343 bf15a033 Nikos Skalkotos
                    dest.write(line)
344 bf15a033 Nikos Skalkotos
345 b0376b3f Nikos Skalkotos
    def _create_filesystems(self, image, partitions):
346 74149d07 Nikos Skalkotos
347 bf15a033 Nikos Skalkotos
        filesystem = {}
348 25b4d858 Nikos Skalkotos
        for p in self.disk.partitions:
349 bf15a033 Nikos Skalkotos
            filesystem[p.number] = self._get_mount_options(p.path)
350 25b4d858 Nikos Skalkotos
351 bf15a033 Nikos Skalkotos
        unmounted = filter(lambda p: filesystem[p.num] is None, partitions)
352 bf15a033 Nikos Skalkotos
        mounted = filter(lambda p: filesystem[p.num] is not None, partitions)
353 25b4d858 Nikos Skalkotos
354 25b4d858 Nikos Skalkotos
        # For partitions that are not mounted right now, we can simply dd them
355 25b4d858 Nikos Skalkotos
        # into the image.
356 25b4d858 Nikos Skalkotos
        for p in unmounted:
357 10a5c2bf Nikos Skalkotos
            self.out.output('Cloning partition %d ... ' % p.num, False)
358 25b4d858 Nikos Skalkotos
            dd('if=%s' % self.disk.device.path, 'of=%s' % image,
359 25b4d858 Nikos Skalkotos
               'count=%d' % (p.end - p.start + 1), 'conv=notrunc',
360 25b4d858 Nikos Skalkotos
               'seek=%d' % p.start, 'skip=%d' % p.start)
361 10a5c2bf Nikos Skalkotos
            self.out.success("done")
362 25b4d858 Nikos Skalkotos
363 25b4d858 Nikos Skalkotos
        loop = str(losetup('-f', '--show', image)).strip()
364 25b4d858 Nikos Skalkotos
        mapped = {}
365 25b4d858 Nikos Skalkotos
        try:
366 25b4d858 Nikos Skalkotos
            for p in mounted:
367 74149d07 Nikos Skalkotos
                i = p.num
368 25b4d858 Nikos Skalkotos
                mapped[i] = self._map_partition(loop, i, p.start, p.end)
369 25b4d858 Nikos Skalkotos
370 bf15a033 Nikos Skalkotos
            new_uuid = {}
371 25b4d858 Nikos Skalkotos
            # Create the file systems
372 25b4d858 Nikos Skalkotos
            for i, dev in mapped.iteritems():
373 bf15a033 Nikos Skalkotos
                fs = filesystem[i].fs
374 25b4d858 Nikos Skalkotos
                self.out.output('Creating %s filesystem on partition %d ... ' %
375 6228d45e Nikos Skalkotos
                                (fs, i), False)
376 25b4d858 Nikos Skalkotos
                get_command('mkfs.%s' % fs)(*(MKFS_OPTS[fs] + [dev]))
377 25b4d858 Nikos Skalkotos
                self.out.success('done')
378 6228d45e Nikos Skalkotos
                new_uuid[i] = blkid(
379 6228d45e Nikos Skalkotos
                    '-s', 'UUID', '-o', 'value', dev).stdout.strip()
380 25b4d858 Nikos Skalkotos
381 25b4d858 Nikos Skalkotos
            target = tempfile.mkdtemp()
382 25b4d858 Nikos Skalkotos
            try:
383 25b4d858 Nikos Skalkotos
                absmpoints = self._mount(target,
384 6228d45e Nikos Skalkotos
                                         [(mapped[i], filesystem[i].mpoint)
385 6228d45e Nikos Skalkotos
                                         for i in mapped.keys()])
386 252b2b5d Nikos Skalkotos
                excluded = self._to_exclude()
387 1fa75c4c Nikos Skalkotos
388 1fa75c4c Nikos Skalkotos
                rsync = Rsync(self.out)
389 1fa75c4c Nikos Skalkotos
390 1fa75c4c Nikos Skalkotos
                # Excluded paths need to be relative to the source
391 29274f83 Nikos Skalkotos
                for excl in map(lambda p: p[1:], excluded + [image]):
392 1fa75c4c Nikos Skalkotos
                    rsync.exclude(excl)
393 1fa75c4c Nikos Skalkotos
394 1fa75c4c Nikos Skalkotos
                rsync.archive().hard_links().xattrs().sparse().acls()
395 f2ddf4db Nikos Skalkotos
                rsync.run('/', target, 'host', 'temporary image')
396 25b4d858 Nikos Skalkotos
397 252b2b5d Nikos Skalkotos
                # Create missing mountpoints. Since they are mountpoints, we
398 252b2b5d Nikos Skalkotos
                # cannot determine the ownership and the mode of the real
399 252b2b5d Nikos Skalkotos
                # directory. Make them inherit those properties from their
400 252b2b5d Nikos Skalkotos
                # parent dir
401 252b2b5d Nikos Skalkotos
                for excl in excluded:
402 10a5c2bf Nikos Skalkotos
                    dirname = os.path.dirname(excl)
403 10a5c2bf Nikos Skalkotos
                    stat = os.stat(dirname)
404 10a5c2bf Nikos Skalkotos
                    os.mkdir(target + excl, stat.st_mode)
405 10a5c2bf Nikos Skalkotos
                    os.chown(target + excl, stat.st_uid, stat.st_gid)
406 252b2b5d Nikos Skalkotos
407 6228d45e Nikos Skalkotos
                # We need to replace the old UUID referencies with the new
408 6228d45e Nikos Skalkotos
                # ones in grub configuration files and /etc/fstab for file
409 6228d45e Nikos Skalkotos
                # systems that have been recreated.
410 bf15a033 Nikos Skalkotos
                self._replace_uuids(target, new_uuid)
411 bf15a033 Nikos Skalkotos
412 25b4d858 Nikos Skalkotos
            finally:
413 25b4d858 Nikos Skalkotos
                self._umount_all(target)
414 25b4d858 Nikos Skalkotos
                os.rmdir(target)
415 25b4d858 Nikos Skalkotos
        finally:
416 25b4d858 Nikos Skalkotos
            for dev in mapped.values():
417 25b4d858 Nikos Skalkotos
                self._unmap_partition(dev)
418 25b4d858 Nikos Skalkotos
            losetup('-d', loop)
419 25b4d858 Nikos Skalkotos
420 567891a6 Nikos Skalkotos
    def create_image(self, image):
421 f3845095 Nikos Skalkotos
        """Given an image filename, this method will create an image out of the
422 f3845095 Nikos Skalkotos
        running system.
423 f3845095 Nikos Skalkotos
        """
424 a939e3b8 Nikos Skalkotos
425 e62eb365 Nikos Skalkotos
        size = self.disk.device.length * self.disk.device.sectorSize
426 a939e3b8 Nikos Skalkotos
427 a939e3b8 Nikos Skalkotos
        # Create sparse file to host the image
428 567891a6 Nikos Skalkotos
        fd = os.open(image, os.O_WRONLY | os.O_CREAT)
429 567891a6 Nikos Skalkotos
        try:
430 567891a6 Nikos Skalkotos
            os.ftruncate(fd, size)
431 567891a6 Nikos Skalkotos
        finally:
432 567891a6 Nikos Skalkotos
            os.close(fd)
433 25b4d858 Nikos Skalkotos
434 25b4d858 Nikos Skalkotos
        self._create_partition_table(image)
435 b0376b3f Nikos Skalkotos
        end_sector, partitions = self._shrink_partitions(image)
436 b0376b3f Nikos Skalkotos
437 b0376b3f Nikos Skalkotos
        if self.disk.type == 'gpt':
438 b0376b3f Nikos Skalkotos
            old_size = size
439 b0376b3f Nikos Skalkotos
            size = (end_sector + 1) * self.disk.device.sectorSize
440 b0376b3f Nikos Skalkotos
            ptable = GPTPartitionTable(image)
441 b0376b3f Nikos Skalkotos
            size = ptable.shrink(size, old_size)
442 b0376b3f Nikos Skalkotos
        else:
443 b0376b3f Nikos Skalkotos
            # Alighn to 2048
444 b0376b3f Nikos Skalkotos
            end_sector = ((end_sector + 2047) // 2048) * 2048
445 b0376b3f Nikos Skalkotos
            size = (end_sector + 1) * self.disk.device.sectorSize
446 fa39c7e3 Nikos Skalkotos
447 fa39c7e3 Nikos Skalkotos
        # Truncate image to the new size.
448 fa39c7e3 Nikos Skalkotos
        fd = os.open(image, os.O_RDWR)
449 fa39c7e3 Nikos Skalkotos
        try:
450 fa39c7e3 Nikos Skalkotos
            os.ftruncate(fd, size)
451 fa39c7e3 Nikos Skalkotos
        finally:
452 fa39c7e3 Nikos Skalkotos
            os.close(fd)
453 fa39c7e3 Nikos Skalkotos
454 25b4d858 Nikos Skalkotos
        # Check if the available space is enough to host the image
455 25b4d858 Nikos Skalkotos
        dirname = os.path.dirname(image)
456 1294501a Nikos Skalkotos
        self.out.output("Examining available space ...", False)
457 c16850ae Nikos Skalkotos
        if free_space(dirname) <= size:
458 f2ddf4db Nikos Skalkotos
            raise FatalError('Not enough space under %s to host the image' %
459 25b4d858 Nikos Skalkotos
                             dirname)
460 25b4d858 Nikos Skalkotos
        self.out.success("sufficient")
461 8eea5572 Nikos Skalkotos
462 b0376b3f Nikos Skalkotos
        self._create_filesystems(image, partitions)
463 8eea5572 Nikos Skalkotos
464 25b4d858 Nikos Skalkotos
        return image
465 9e4b4de2 Nikos Skalkotos
466 9e4b4de2 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :