blob: 5a103bbc7e80f01d8bf482e0860c497552582410 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#!/usr/bin/env python -tt
2#
3# Copyright (c) 2009, 2010, 2011 Intel, Inc.
4# Copyright (c) 2007, 2008 Red Hat, Inc.
5# Copyright (c) 2008 Daniel P. Berrange
6# Copyright (c) 2008 David P. Huff
7#
8# This program is free software; you can redistribute it and/or modify it
9# under the terms of the GNU General Public License as published by the Free
10# Software Foundation; version 2 of the License
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15# for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc., 59
19# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21import os
22from wic import msger
23from wic.utils.errors import ImageError
24from wic.utils.oe.misc import exec_cmd, exec_native_cmd
25
26# Overhead of the MBR partitioning scheme (just one sector)
27MBR_OVERHEAD = 1
28
29# Overhead of the GPT partitioning scheme
30GPT_OVERHEAD = 34
31
32# Size of a sector in bytes
33SECTOR_SIZE = 512
34
35class Image(object):
36 """
37 Generic base object for an image.
38
39 An Image is a container for a set of DiskImages and associated
40 partitions.
41 """
42 def __init__(self, native_sysroot=None):
43 self.disks = {}
44 self.partitions = []
45 # Size of a sector used in calculations
46 self.sector_size = SECTOR_SIZE
47 self._partitions_layed_out = False
48 self.native_sysroot = native_sysroot
49
50 def __add_disk(self, disk_name):
51 """ Add a disk 'disk_name' to the internal list of disks. Note,
52 'disk_name' is the name of the disk in the target system
53 (e.g., sdb). """
54
55 if disk_name in self.disks:
56 # We already have this disk
57 return
58
59 assert not self._partitions_layed_out
60
61 self.disks[disk_name] = \
62 {'disk': None, # Disk object
63 'numpart': 0, # Number of allocate partitions
64 'realpart': 0, # Number of partitions in the partition table
65 'partitions': [], # Indexes to self.partitions
66 'offset': 0, # Offset of next partition (in sectors)
67 # Minimum required disk size to fit all partitions (in bytes)
68 'min_size': 0,
69 'ptable_format': "msdos"} # Partition table format
70
71 def add_disk(self, disk_name, disk_obj):
72 """ Add a disk object which have to be partitioned. More than one disk
73 can be added. In case of multiple disks, disk partitions have to be
74 added for each disk separately with 'add_partition()". """
75
76 self.__add_disk(disk_name)
77 self.disks[disk_name]['disk'] = disk_obj
78
79 def __add_partition(self, part):
80 """ This is a helper function for 'add_partition()' which adds a
81 partition to the internal list of partitions. """
82
83 assert not self._partitions_layed_out
84
85 self.partitions.append(part)
86 self.__add_disk(part['disk_name'])
87
88 def add_partition(self, size, disk_name, mountpoint, source_file=None, fstype=None,
89 label=None, fsopts=None, boot=False, align=None, no_table=False,
90 part_type=None, uuid=None):
91 """ Add the next partition. Prtitions have to be added in the
92 first-to-last order. """
93
94 ks_pnum = len(self.partitions)
95
96 # Converting kB to sectors for parted
97 size = size * 1024 / self.sector_size
98
99 # We still need partition for "/" or non-subvolume
100 if mountpoint == "/" or not fsopts:
101 part = {'ks_pnum': ks_pnum, # Partition number in the KS file
102 'size': size, # In sectors
103 'mountpoint': mountpoint, # Mount relative to chroot
104 'source_file': source_file, # partition contents
105 'fstype': fstype, # Filesystem type
106 'fsopts': fsopts, # Filesystem mount options
107 'label': label, # Partition label
108 'disk_name': disk_name, # physical disk name holding partition
109 'device': None, # kpartx device node for partition
110 'num': None, # Partition number
111 'boot': boot, # Bootable flag
112 'align': align, # Partition alignment
113 'no_table' : no_table, # Partition does not appear in partition table
114 'part_type' : part_type, # Partition type
115 'uuid': uuid} # Partition UUID
116
117 self.__add_partition(part)
118
119 def layout_partitions(self, ptable_format="msdos"):
120 """ Layout the partitions, meaning calculate the position of every
121 partition on the disk. The 'ptable_format' parameter defines the
122 partition table format and may be "msdos". """
123
124 msger.debug("Assigning %s partitions to disks" % ptable_format)
125
126 if self._partitions_layed_out:
127 return
128
129 self._partitions_layed_out = True
130
131 # Go through partitions in the order they are added in .ks file
132 for num in range(len(self.partitions)):
133 part = self.partitions[num]
134
135 if not self.disks.has_key(part['disk_name']):
136 raise ImageError("No disk %s for partition %s" \
137 % (part['disk_name'], part['mountpoint']))
138
139 if ptable_format == 'msdos' and part['part_type']:
140 # The --part-type can also be implemented for MBR partitions,
141 # in which case it would map to the 1-byte "partition type"
142 # filed at offset 3 of the partition entry.
143 raise ImageError("setting custom partition type is not " \
144 "implemented for msdos partitions")
145
146 # Get the disk where the partition is located
147 disk = self.disks[part['disk_name']]
148 disk['numpart'] += 1
149 if not part['no_table']:
150 disk['realpart'] += 1
151 disk['ptable_format'] = ptable_format
152
153 if disk['numpart'] == 1:
154 if ptable_format == "msdos":
155 overhead = MBR_OVERHEAD
156 elif ptable_format == "gpt":
157 overhead = GPT_OVERHEAD
158
159 # Skip one sector required for the partitioning scheme overhead
160 disk['offset'] += overhead
161
162 if disk['realpart'] > 3:
163 # Reserve a sector for EBR for every logical partition
164 # before alignment is performed.
165 if ptable_format == "msdos":
166 disk['offset'] += 1
167
168
169 if part['align']:
170 # If not first partition and we do have alignment set we need
171 # to align the partition.
172 # FIXME: This leaves a empty spaces to the disk. To fill the
173 # gaps we could enlargea the previous partition?
174
175 # Calc how much the alignment is off.
176 align_sectors = disk['offset'] % (part['align'] * 1024 / self.sector_size)
177
178 if align_sectors:
179 # If partition is not aligned as required, we need
180 # to move forward to the next alignment point
181 align_sectors = (part['align'] * 1024 / self.sector_size) - align_sectors
182
183 msger.debug("Realignment for %s%s with %s sectors, original"
184 " offset %s, target alignment is %sK." %
185 (part['disk_name'], disk['numpart'], align_sectors,
186 disk['offset'], part['align']))
187
188 # increase the offset so we actually start the partition on right alignment
189 disk['offset'] += align_sectors
190
191 part['start'] = disk['offset']
192 disk['offset'] += part['size']
193
194 part['type'] = 'primary'
195 if not part['no_table']:
196 part['num'] = disk['realpart']
197 else:
198 part['num'] = 0
199
200 if disk['ptable_format'] == "msdos":
201 if disk['realpart'] > 3:
202 part['type'] = 'logical'
203 part['num'] = disk['realpart'] + 1
204
205 disk['partitions'].append(num)
206 msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
207 "sectors (%d bytes)." \
208 % (part['mountpoint'], part['disk_name'], part['num'],
209 part['start'], part['start'] + part['size'] - 1,
210 part['size'], part['size'] * self.sector_size))
211
212 # Once all the partitions have been layed out, we can calculate the
213 # minumim disk sizes.
214 for disk in self.disks.values():
215 disk['min_size'] = disk['offset']
216 if disk['ptable_format'] == "gpt":
217 disk['min_size'] += GPT_OVERHEAD
218
219 disk['min_size'] *= self.sector_size
220
221 def __create_partition(self, device, parttype, fstype, start, size):
222 """ Create a partition on an image described by the 'device' object. """
223
224 # Start is included to the size so we need to substract one from the end.
225 end = start + size - 1
226 msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" %
227 (parttype, start, end, size))
228
229 cmd = "parted -s %s unit s mkpart %s" % (device, parttype)
230 if fstype:
231 cmd += " %s" % fstype
232 cmd += " %d %d" % (start, end)
233
234 return exec_native_cmd(cmd, self.native_sysroot)
235
236 def __format_disks(self):
237 self.layout_partitions()
238
239 for dev in self.disks.keys():
240 disk = self.disks[dev]
241 msger.debug("Initializing partition table for %s" % \
242 (disk['disk'].device))
243 exec_native_cmd("parted -s %s mklabel %s" % \
244 (disk['disk'].device, disk['ptable_format']),
245 self.native_sysroot)
246
247 msger.debug("Creating partitions")
248
249 for part in self.partitions:
250 if part['num'] == 0:
251 continue
252
253 disk = self.disks[part['disk_name']]
254 if disk['ptable_format'] == "msdos" and part['num'] == 5:
255 # Create an extended partition (note: extended
256 # partition is described in MBR and contains all
257 # logical partitions). The logical partitions save a
258 # sector for an EBR just before the start of a
259 # partition. The extended partition must start one
260 # sector before the start of the first logical
261 # partition. This way the first EBR is inside of the
262 # extended partition. Since the extended partitions
263 # starts a sector before the first logical partition,
264 # add a sector at the back, so that there is enough
265 # room for all logical partitions.
266 self.__create_partition(disk['disk'].device, "extended",
267 None, part['start'] - 1,
268 disk['offset'] - part['start'] + 1)
269
270 if part['fstype'] == "swap":
271 parted_fs_type = "linux-swap"
272 elif part['fstype'] == "vfat":
273 parted_fs_type = "fat32"
274 elif part['fstype'] == "msdos":
275 parted_fs_type = "fat16"
276 elif part['fstype'] == "ontrackdm6aux3":
277 parted_fs_type = "ontrackdm6aux3"
278 else:
279 # Type for ext2/ext3/ext4/btrfs
280 parted_fs_type = "ext2"
281
282 # Boot ROM of OMAP boards require vfat boot partition to have an
283 # even number of sectors.
284 if part['mountpoint'] == "/boot" and part['fstype'] in ["vfat", "msdos"] \
285 and part['size'] % 2:
286 msger.debug("Substracting one sector from '%s' partition to " \
287 "get even number of sectors for the partition" % \
288 part['mountpoint'])
289 part['size'] -= 1
290
291 self.__create_partition(disk['disk'].device, part['type'],
292 parted_fs_type, part['start'], part['size'])
293
294 if part['part_type']:
295 msger.debug("partition %d: set type UID to %s" % \
296 (part['num'], part['part_type']))
297 exec_native_cmd("sgdisk --typecode=%d:%s %s" % \
298 (part['num'], part['part_type'],
299 disk['disk'].device), self.native_sysroot)
300
301 if part['uuid']:
302 msger.debug("partition %d: set UUID to %s" % \
303 (part['num'], part['uuid']))
304 exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \
305 (part['num'], part['uuid'], disk['disk'].device),
306 self.native_sysroot)
307
308 if part['boot']:
309 flag_name = "legacy_boot" if disk['ptable_format'] == 'gpt' else "boot"
310 msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \
311 (flag_name, part['num'], disk['disk'].device))
312 exec_native_cmd("parted -s %s set %d %s on" % \
313 (disk['disk'].device, part['num'], flag_name),
314 self.native_sysroot)
315
316 # Parted defaults to enabling the lba flag for fat16 partitions,
317 # which causes compatibility issues with some firmware (and really
318 # isn't necessary).
319 if parted_fs_type == "fat16":
320 if disk['ptable_format'] == 'msdos':
321 msger.debug("Disable 'lba' flag for partition '%s' on disk '%s'" % \
322 (part['num'], disk['disk'].device))
323 exec_native_cmd("parted -s %s set %d lba off" % \
324 (disk['disk'].device, part['num']),
325 self.native_sysroot)
326
327 def cleanup(self):
328 if self.disks:
329 for dev in self.disks:
330 disk = self.disks[dev]
331 try:
332 disk['disk'].cleanup()
333 except:
334 pass
335
336 def assemble(self, image_file):
337 msger.debug("Installing partitions")
338
339 for part in self.partitions:
340 source = part['source_file']
341 if source:
342 # install source_file contents into a partition
343 cmd = "dd if=%s of=%s bs=%d seek=%d count=%d conv=notrunc" % \
344 (source, image_file, self.sector_size,
345 part['start'], part['size'])
346 exec_cmd(cmd)
347
348 msger.debug("Installed %s in partition %d, sectors %d-%d, "
349 "size %d sectors" % \
350 (source, part['num'], part['start'],
351 part['start'] + part['size'] - 1, part['size']))
352
353 os.rename(source, image_file + '.p%d' % part['num'])
354
355 def create(self):
356 for dev in self.disks.keys():
357 disk = self.disks[dev]
358 disk['disk'].create()
359
360 self.__format_disks()
361
362 return