Revision 9517bf29

b/image_creator/bundle_volume.py
35 35
import re
36 36
import parted
37 37
import uuid
38
from collections import namedtuple
38 39

  
39 40
from image_creator.util import get_command
40 41
from image_creator.util import FatalError
......
43 44
truncate = get_command('truncate')
44 45
dd = get_command('dd')
45 46

  
46
def get_root_partition():
47
    if not os.path.isfile('/etc/fstab'):
48
        raise FatalError("Unable to open `/etc/fstab'. File is missing.")
49 47

  
50
    with open('/etc/fstab') as fstab:
51
        for line in iter(fstab):
48
def fstable(f):
49
    if not os.path.isfile(f):
50
        raise FatalError("Unable to open: `%s'. File is missing." % f)
51

  
52
    Entry = namedtuple('Entry', 'dev mpoint fs opts freq passno')
53

  
54
    with open(f) as table:
55
        for line in iter(table):
52 56
            entry = line.split('#')[0].strip().split()
53 57
            if len(entry) != 6:
54 58
                continue
59
            yield Entry(*entry)
55 60

  
56
            if entry[1] == "/":
57
                return entry[0]
58 61

  
59
        raise FatalError("Unable to find root device in /etc/fstab")
62
def get_root_partition():
63
    for entry in fstable('/etc/fstab'):
64
        if entry.mpoint == '/':
65
            return entry.dev
60 66

  
61
def mnt_mounted():
62
    if not os.path.isfile('/etc/mtab'):
63
        raise FatalError("Unable to open `/etc/fstab'. File is missing.")
67
    raise FatalError("Unable to find root device in /etc/fstab")
64 68

  
65
    with open('/etc/mtab') as mtab:
66
        for line in iter(mtab):
67
            entry = line.split('#')[0].strip().split()
68
            if len(entry) != 6:
69
                continue
70

  
71
            if entry[1] == '/mnt':
72
                return True
73 69

  
70
def is_mpoint(path):
71
    for entry in fstable('/proc/mounts'):
72
        if entry.mpoint == path:
73
            return True
74 74
    return False
75 75

  
76 76

  
77
def part_to_dev(part):
78
    return re.split('[0-9]', part)[0]
77
def mpoint(device):
78
    for entry in fstable('/proc/mounts'):
79
        if not entry.dev.startswith('/'):
80
            continue
81

  
82
        if os.path.realpath(entry.dev) == os.path.realpath(device):
83
            return entry.mpoint
79 84

  
80
def part_to_num(part):
81
    return re.split('[^0-9]+', part)[-1]
85
    return ""
82 86

  
83
def bundle_volume(out):
84 87

  
85
    if mnt_mounted():
88
def create_EBRs(src, dest):
89

  
90
    # The Extended boot records precede the logical partitions they describe
91
    extended = src.getExtendedPartition()
92
    if not extended:
93
        return
94

  
95
    logical = src.getLogicalPartitions()
96
    start = extended.geometry.start
97
    for l in range(len(logical)):
98
        end = l.geometry.start - 1
99
        dd('if=%s' % src.path, 'of=%s' % dest, 'count=%d' % (end - start + 1),
100
            'conv=notrunc', 'seek=%d' % start, 'skip=%d' % start)
101
        start = l.geometry.end + 1
102

  
103

  
104
def bundle_volume(out, meta):
105

  
106
    if is_mpoint('/mnt'):
86 107
        raise FatalError('The directory /mnt where the image will be hosted'
87 108
            'is mounted. Please unmount it and start over again.')
88 109

  
......
97 118
    if not re.match('/dev/[hsv]d[a-z][1-9]*$', root_part):
98 119
        raise FatalError("Don't know how to handle root device: %s" % root_dev)
99 120

  
100
    device = parted.Device(part_to_dev(root_part))
121
    part_to_dev = lambda p: re.split('[0-9]', p)[0]
122

  
123
    root_dev = part_to_dev(root_part)
124

  
125
    out.success('%s' % root_dev)
126

  
127
    src_dev = parted.Device(root_dev)
101 128

  
102
    image = '/mnt/%s.diskdump' % uuid.uuid4().hex
129
    img = '/mnt/%s.diskdump' % uuid.uuid4().hex
130
    disk_size = src_dev.getLength() * src_dev.sectorSize
103 131

  
104 132
    # Create sparse file to host the image
105
    truncate("-s", "%d" % (device.getLength() * device.sectorSize), image)
133
    truncate("-s", "%d" % disk_size, img)
106 134

  
107
    disk = parted.Disk(device)
108
    if disk.type != 'msdos':
135
    src_disk = parted.Disk(src_dev)
136
    if src_disk.type != 'msdos':
109 137
        raise FatalError('For now we can only handle msdos partition tables')
110 138

  
111 139
    # Copy the MBR and the space between the MBR and the first partition.
112 140
    # In Grub version 1 Stage 1.5 is located there.
113
    first_sector = disk.getPrimaryPartitions()[0].geometry.start
141
    first_sector = src_disk.getPrimaryPartitions()[0].geometry.start
114 142

  
115
    dd('if=%s' % device.path, 'of=%s' % image, 'bs=%d' % device.sectorSize,
143
    dd('if=%s' % src_dev.path, 'of=%s' % img, 'bs=%d' % src_dev.sectorSize,
116 144
        'count=%d' % first_sector, 'conv=notrunc')
117 145

  
118
    return image
146
    # Create the Extended boot records (EBRs) in the image
147
    create_EBRs(src_disk, img)
148

  
149
    img_dev = parted.Device(img)
150
    img_disk = parted.Disk(img_dev)
151

  
152
    Partition = namedtuple('Partition', 'num start end type fs mpoint')
153

  
154
    partitions = []
155
    for p in src_disk.partitions:
156
        g = p.geometry
157
        f = p.fileSystem
158
        partitions.append(Partition(p.number, g.start, g.end, p.type,
159
            f.type if f is not None else '', mpoint(p.path)))
160

  
161
    last = partitions[-1]
162
    new_end = src_dev.getLength()
163
    if last.fs == 'linux-swap(v1)':
164
        size = (last.end - last.start + 1) * src_dev.sectorSize
165
        MB = 2 ** 20
166
        meta['SWAP'] = "%d:%s" % (last.num, ((size + MB - 1) // MB))
167
        img_disk.deletePartition(img_disk.getPartitionBySector(last.start))
168
        if last.type == parted.PARTITION_LOGICAL and last.num == 5:
169
            img_disk.deletePartition(img_disk.getExtendedPartition())
170
        partitions.remove(last)
171
        last = partitions[-1]
172

  
173
        # Leave 2048 blocks at the end
174
        new_end = last.end + 2048
175

  
176
    if last.mpoint:
177
        stat = statvfs(last.mpoint)
178
        occupied_blocks = stat.f_blocks - stat.f_bavail
179
        new_size = (occupied * stat.f_frsize) // src_dev.sectorSize
180
        # Add 10% just to be on the safe side
181
        last.end = last.start + (new_size * 11) // 10
182

  
183
        # Alighn to 2048
184
        last.end = ((last.end + 2047) // 2048) * 2048
185

  
186
        # Leave 2048 blocks at the end.
187
        new_end = new_size + 2048
188

  
189
        img_disk.setPartitionGeometry(
190
            img_disk.getPartitionBySector(last.start),
191
            parted.Constraint(device=img_dev), start=last.star, end=last.end)
192

  
193
        if last.type == parted.PARTITION_LOGICAL:
194
            # Fix the extended partition
195
            ext = disk.getExtendedPartition()
196

  
197
            img_disk.setPartitionGeometry(ext,
198
                parted.Constraint(device=img_dev), ext.geometry.start,
199
                end=last.end)
200

  
201
    # Check if we have the available space on the filesystem hosting /mnt
202
    # for the image.
203
    out.output("Examining available space in /mnt ... ", False)
204
    stat = os.statvfs('/mnt')
205
    image_size = (new_end + 1) * src_dev.sectorSize
206
    available = stat.f_bavail * stat.f_frsize
207

  
208
    if available <= image_size:
209
        raise FatalError('Not enough space in /mnt to host the image')
210

  
211
    out.success("sufficient")
212

  
213
    return img
119 214

  
120 215
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
b/image_creator/disk.py
67 67
        self._devices = []
68 68
        self.source = source
69 69
        self.out = output
70
        self.meta = {}
70 71

  
71 72
    def _add_cleanup(self, job, *args):
72 73
        self._cleanup_jobs.append((job, args))
......
79 80

  
80 81
    def _dir_to_disk(self):
81 82
        if self.source == '/':
82
            return bundle_volume(self.out)
83
            return bundle_volume(self.out, self.meta)
83 84
        raise FatalError("Using a directory as media source is supported")
84 85

  
85 86
    def cleanup(self):
......
165 166
    as created by the device-mapper.
166 167
    """
167 168

  
168
    def __init__(self, device, output, bootable=True):
169
    def __init__(self, device, output, bootable=True, meta={}):
169 170
        """Create a new DiskDevice."""
170 171

  
171 172
        self.real_device = device
172 173
        self.out = output
173 174
        self.bootable = bootable
175
        self.meta = meta
174 176
        self.progress_bar = None
175 177
        self.guestfs_device = None
176 178
        self.size = 0
177
        self.meta = {}
178 179

  
179 180
        self.g = guestfs.GuestFS()
180 181
        self.g.add_drive_opts(self.real_device, readonly=0)

Also available in: Unified diff