Statistics
| Branch: | Tag: | Revision:

root / image_creator / bundle_volume.py @ bf3a282c

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