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 :
|