blob: ad596d26f2c3c07e0d475bab1dbbbfd65e4dc65b [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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050099 part = {'ks_pnum': ks_pnum, # Partition number in the KS file
100 'size': size, # In sectors
101 'mountpoint': mountpoint, # Mount relative to chroot
102 'source_file': source_file, # partition contents
103 'fstype': fstype, # Filesystem type
104 'fsopts': fsopts, # Filesystem mount options
105 'label': label, # Partition label
106 'disk_name': disk_name, # physical disk name holding partition
107 'device': None, # kpartx device node for partition
108 'num': None, # Partition number
109 'boot': boot, # Bootable flag
110 'align': align, # Partition alignment
111 'no_table' : no_table, # Partition does not appear in partition table
112 'part_type' : part_type, # Partition type
113 'uuid': uuid} # Partition UUID
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500115 self.__add_partition(part)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116
117 def layout_partitions(self, ptable_format="msdos"):
118 """ Layout the partitions, meaning calculate the position of every
119 partition on the disk. The 'ptable_format' parameter defines the
120 partition table format and may be "msdos". """
121
122 msger.debug("Assigning %s partitions to disks" % ptable_format)
123
124 if self._partitions_layed_out:
125 return
126
127 self._partitions_layed_out = True
128
129 # Go through partitions in the order they are added in .ks file
130 for num in range(len(self.partitions)):
131 part = self.partitions[num]
132
133 if not self.disks.has_key(part['disk_name']):
134 raise ImageError("No disk %s for partition %s" \
135 % (part['disk_name'], part['mountpoint']))
136
137 if ptable_format == 'msdos' and part['part_type']:
138 # The --part-type can also be implemented for MBR partitions,
139 # in which case it would map to the 1-byte "partition type"
140 # filed at offset 3 of the partition entry.
141 raise ImageError("setting custom partition type is not " \
142 "implemented for msdos partitions")
143
144 # Get the disk where the partition is located
145 disk = self.disks[part['disk_name']]
146 disk['numpart'] += 1
147 if not part['no_table']:
148 disk['realpart'] += 1
149 disk['ptable_format'] = ptable_format
150
151 if disk['numpart'] == 1:
152 if ptable_format == "msdos":
153 overhead = MBR_OVERHEAD
154 elif ptable_format == "gpt":
155 overhead = GPT_OVERHEAD
156
157 # Skip one sector required for the partitioning scheme overhead
158 disk['offset'] += overhead
159
160 if disk['realpart'] > 3:
161 # Reserve a sector for EBR for every logical partition
162 # before alignment is performed.
163 if ptable_format == "msdos":
164 disk['offset'] += 1
165
166
167 if part['align']:
168 # If not first partition and we do have alignment set we need
169 # to align the partition.
170 # FIXME: This leaves a empty spaces to the disk. To fill the
171 # gaps we could enlargea the previous partition?
172
173 # Calc how much the alignment is off.
174 align_sectors = disk['offset'] % (part['align'] * 1024 / self.sector_size)
175
176 if align_sectors:
177 # If partition is not aligned as required, we need
178 # to move forward to the next alignment point
179 align_sectors = (part['align'] * 1024 / self.sector_size) - align_sectors
180
181 msger.debug("Realignment for %s%s with %s sectors, original"
182 " offset %s, target alignment is %sK." %
183 (part['disk_name'], disk['numpart'], align_sectors,
184 disk['offset'], part['align']))
185
186 # increase the offset so we actually start the partition on right alignment
187 disk['offset'] += align_sectors
188
189 part['start'] = disk['offset']
190 disk['offset'] += part['size']
191
192 part['type'] = 'primary'
193 if not part['no_table']:
194 part['num'] = disk['realpart']
195 else:
196 part['num'] = 0
197
198 if disk['ptable_format'] == "msdos":
199 if disk['realpart'] > 3:
200 part['type'] = 'logical'
201 part['num'] = disk['realpart'] + 1
202
203 disk['partitions'].append(num)
204 msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
205 "sectors (%d bytes)." \
206 % (part['mountpoint'], part['disk_name'], part['num'],
207 part['start'], part['start'] + part['size'] - 1,
208 part['size'], part['size'] * self.sector_size))
209
210 # Once all the partitions have been layed out, we can calculate the
211 # minumim disk sizes.
212 for disk in self.disks.values():
213 disk['min_size'] = disk['offset']
214 if disk['ptable_format'] == "gpt":
215 disk['min_size'] += GPT_OVERHEAD
216
217 disk['min_size'] *= self.sector_size
218
219 def __create_partition(self, device, parttype, fstype, start, size):
220 """ Create a partition on an image described by the 'device' object. """
221
222 # Start is included to the size so we need to substract one from the end.
223 end = start + size - 1
224 msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" %
225 (parttype, start, end, size))
226
227 cmd = "parted -s %s unit s mkpart %s" % (device, parttype)
228 if fstype:
229 cmd += " %s" % fstype
230 cmd += " %d %d" % (start, end)
231
232 return exec_native_cmd(cmd, self.native_sysroot)
233
234 def __format_disks(self):
235 self.layout_partitions()
236
237 for dev in self.disks.keys():
238 disk = self.disks[dev]
239 msger.debug("Initializing partition table for %s" % \
240 (disk['disk'].device))
241 exec_native_cmd("parted -s %s mklabel %s" % \
242 (disk['disk'].device, disk['ptable_format']),
243 self.native_sysroot)
244
245 msger.debug("Creating partitions")
246
247 for part in self.partitions:
248 if part['num'] == 0:
249 continue
250
251 disk = self.disks[part['disk_name']]
252 if disk['ptable_format'] == "msdos" and part['num'] == 5:
253 # Create an extended partition (note: extended
254 # partition is described in MBR and contains all
255 # logical partitions). The logical partitions save a
256 # sector for an EBR just before the start of a
257 # partition. The extended partition must start one
258 # sector before the start of the first logical
259 # partition. This way the first EBR is inside of the
260 # extended partition. Since the extended partitions
261 # starts a sector before the first logical partition,
262 # add a sector at the back, so that there is enough
263 # room for all logical partitions.
264 self.__create_partition(disk['disk'].device, "extended",
265 None, part['start'] - 1,
266 disk['offset'] - part['start'] + 1)
267
268 if part['fstype'] == "swap":
269 parted_fs_type = "linux-swap"
270 elif part['fstype'] == "vfat":
271 parted_fs_type = "fat32"
272 elif part['fstype'] == "msdos":
273 parted_fs_type = "fat16"
274 elif part['fstype'] == "ontrackdm6aux3":
275 parted_fs_type = "ontrackdm6aux3"
276 else:
277 # Type for ext2/ext3/ext4/btrfs
278 parted_fs_type = "ext2"
279
280 # Boot ROM of OMAP boards require vfat boot partition to have an
281 # even number of sectors.
282 if part['mountpoint'] == "/boot" and part['fstype'] in ["vfat", "msdos"] \
283 and part['size'] % 2:
284 msger.debug("Substracting one sector from '%s' partition to " \
285 "get even number of sectors for the partition" % \
286 part['mountpoint'])
287 part['size'] -= 1
288
289 self.__create_partition(disk['disk'].device, part['type'],
290 parted_fs_type, part['start'], part['size'])
291
292 if part['part_type']:
293 msger.debug("partition %d: set type UID to %s" % \
294 (part['num'], part['part_type']))
295 exec_native_cmd("sgdisk --typecode=%d:%s %s" % \
296 (part['num'], part['part_type'],
297 disk['disk'].device), self.native_sysroot)
298
299 if part['uuid']:
300 msger.debug("partition %d: set UUID to %s" % \
301 (part['num'], part['uuid']))
302 exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \
303 (part['num'], part['uuid'], disk['disk'].device),
304 self.native_sysroot)
305
306 if part['boot']:
307 flag_name = "legacy_boot" if disk['ptable_format'] == 'gpt' else "boot"
308 msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \
309 (flag_name, part['num'], disk['disk'].device))
310 exec_native_cmd("parted -s %s set %d %s on" % \
311 (disk['disk'].device, part['num'], flag_name),
312 self.native_sysroot)
313
314 # Parted defaults to enabling the lba flag for fat16 partitions,
315 # which causes compatibility issues with some firmware (and really
316 # isn't necessary).
317 if parted_fs_type == "fat16":
318 if disk['ptable_format'] == 'msdos':
319 msger.debug("Disable 'lba' flag for partition '%s' on disk '%s'" % \
320 (part['num'], disk['disk'].device))
321 exec_native_cmd("parted -s %s set %d lba off" % \
322 (disk['disk'].device, part['num']),
323 self.native_sysroot)
324
325 def cleanup(self):
326 if self.disks:
327 for dev in self.disks:
328 disk = self.disks[dev]
329 try:
330 disk['disk'].cleanup()
331 except:
332 pass
333
334 def assemble(self, image_file):
335 msger.debug("Installing partitions")
336
337 for part in self.partitions:
338 source = part['source_file']
339 if source:
340 # install source_file contents into a partition
341 cmd = "dd if=%s of=%s bs=%d seek=%d count=%d conv=notrunc" % \
342 (source, image_file, self.sector_size,
343 part['start'], part['size'])
344 exec_cmd(cmd)
345
346 msger.debug("Installed %s in partition %d, sectors %d-%d, "
347 "size %d sectors" % \
348 (source, part['num'], part['start'],
349 part['start'] + part['size'] - 1, part['size']))
350
351 os.rename(source, image_file + '.p%d' % part['num'])
352
353 def create(self):
354 for dev in self.disks.keys():
355 disk = self.disks[dev]
356 disk['disk'].create()
357
358 self.__format_disks()
359
360 return