diff options
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/lib/wic/imager/__init__.py | 0 | ||||
-rw-r--r-- | scripts/lib/wic/imager/direct.py | 402 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/imager/direct_plugin.py | 396 |
3 files changed, 380 insertions, 418 deletions
diff --git a/scripts/lib/wic/imager/__init__.py b/scripts/lib/wic/imager/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 --- a/scripts/lib/wic/imager/__init__.py +++ /dev/null | |||
diff --git a/scripts/lib/wic/imager/direct.py b/scripts/lib/wic/imager/direct.py deleted file mode 100644 index ff06b504ce..0000000000 --- a/scripts/lib/wic/imager/direct.py +++ /dev/null | |||
@@ -1,402 +0,0 @@ | |||
1 | # ex:ts=4:sw=4:sts=4:et | ||
2 | # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- | ||
3 | # | ||
4 | # Copyright (c) 2013, Intel Corporation. | ||
5 | # All rights reserved. | ||
6 | # | ||
7 | # This program is free software; you can redistribute it and/or modify | ||
8 | # it under the terms of the GNU General Public License version 2 as | ||
9 | # published by the Free Software Foundation. | ||
10 | # | ||
11 | # This program is distributed in the hope that it will be useful, | ||
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | # GNU General Public License for more details. | ||
15 | # | ||
16 | # You should have received a copy of the GNU General Public License along | ||
17 | # with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | # | ||
20 | # DESCRIPTION | ||
21 | # This implements the 'direct' image creator class for 'wic' | ||
22 | # | ||
23 | # AUTHORS | ||
24 | # Tom Zanussi <tom.zanussi (at] linux.intel.com> | ||
25 | # | ||
26 | |||
27 | import os | ||
28 | import shutil | ||
29 | import uuid | ||
30 | import tempfile | ||
31 | |||
32 | from wic import msger | ||
33 | from wic.utils.oe.misc import get_bitbake_var | ||
34 | from wic.utils.partitionedfs import Image | ||
35 | from wic.utils.errors import CreatorError, ImageError | ||
36 | from wic.plugin import pluginmgr | ||
37 | from wic.utils.oe.misc import exec_cmd, exec_native_cmd | ||
38 | |||
39 | disk_methods = { | ||
40 | "do_install_disk":None, | ||
41 | } | ||
42 | |||
43 | class DiskImage(): | ||
44 | """ | ||
45 | A Disk backed by a file. | ||
46 | """ | ||
47 | def __init__(self, device, size): | ||
48 | self.size = size | ||
49 | self.device = device | ||
50 | self.created = False | ||
51 | |||
52 | def exists(self): | ||
53 | return os.path.exists(self.device) | ||
54 | |||
55 | def create(self): | ||
56 | if self.created: | ||
57 | return | ||
58 | # create sparse disk image | ||
59 | with open(self.device, 'w') as sparse: | ||
60 | os.ftruncate(sparse.fileno(), self.size) | ||
61 | |||
62 | self.created = True | ||
63 | |||
64 | class DirectImageCreator: | ||
65 | """ | ||
66 | Installs a system into a file containing a partitioned disk image. | ||
67 | |||
68 | DirectImageCreator is an advanced ImageCreator subclass; an image | ||
69 | file is formatted with a partition table, each partition created | ||
70 | from a rootfs or other OpenEmbedded build artifact and dd'ed into | ||
71 | the virtual disk. The disk image can subsequently be dd'ed onto | ||
72 | media and used on actual hardware. | ||
73 | """ | ||
74 | |||
75 | def __init__(self, image_name, ksobj, oe_builddir, image_output_dir, | ||
76 | rootfs_dir, bootimg_dir, kernel_dir, native_sysroot, | ||
77 | compressor, bmap=False): | ||
78 | """ | ||
79 | Initialize a DirectImageCreator instance. | ||
80 | |||
81 | This method takes the same arguments as ImageCreator.__init__() | ||
82 | """ | ||
83 | self.name = image_name | ||
84 | self.outdir = image_output_dir | ||
85 | self.workdir = tempfile.mktemp(prefix='wic') | ||
86 | self.ks = ksobj | ||
87 | |||
88 | self.__image = None | ||
89 | self.__disks = {} | ||
90 | self.__disk_format = "direct" | ||
91 | self._disk_names = [] | ||
92 | self.ptable_format = self.ks.bootloader.ptable | ||
93 | |||
94 | self.oe_builddir = oe_builddir | ||
95 | self.rootfs_dir = rootfs_dir | ||
96 | self.bootimg_dir = bootimg_dir | ||
97 | self.kernel_dir = kernel_dir | ||
98 | self.native_sysroot = native_sysroot | ||
99 | self.compressor = compressor | ||
100 | self.bmap = bmap | ||
101 | |||
102 | def _get_part_num(self, num, parts): | ||
103 | """calculate the real partition number, accounting for partitions not | ||
104 | in the partition table and logical partitions | ||
105 | """ | ||
106 | realnum = 0 | ||
107 | for pnum, part in enumerate(parts, 1): | ||
108 | if not part.no_table: | ||
109 | realnum += 1 | ||
110 | if pnum == num: | ||
111 | if part.no_table: | ||
112 | return 0 | ||
113 | if self.ptable_format == 'msdos' and realnum > 3: | ||
114 | # account for logical partition numbering, ex. sda5.. | ||
115 | return realnum + 1 | ||
116 | return realnum | ||
117 | |||
118 | def _write_fstab(self, image_rootfs): | ||
119 | """overriden to generate fstab (temporarily) in rootfs. This is called | ||
120 | from _create, make sure it doesn't get called from | ||
121 | BaseImage.create() | ||
122 | """ | ||
123 | if not image_rootfs: | ||
124 | return | ||
125 | |||
126 | fstab_path = image_rootfs + "/etc/fstab" | ||
127 | if not os.path.isfile(fstab_path): | ||
128 | return | ||
129 | |||
130 | with open(fstab_path) as fstab: | ||
131 | fstab_lines = fstab.readlines() | ||
132 | |||
133 | if self._update_fstab(fstab_lines, self._get_parts()): | ||
134 | shutil.copyfile(fstab_path, fstab_path + ".orig") | ||
135 | |||
136 | with open(fstab_path, "w") as fstab: | ||
137 | fstab.writelines(fstab_lines) | ||
138 | |||
139 | return fstab_path | ||
140 | |||
141 | def _update_fstab(self, fstab_lines, parts): | ||
142 | """Assume partition order same as in wks""" | ||
143 | updated = False | ||
144 | for num, part in enumerate(parts, 1): | ||
145 | pnum = self._get_part_num(num, parts) | ||
146 | if not pnum or not part.mountpoint \ | ||
147 | or part.mountpoint in ("/", "/boot"): | ||
148 | continue | ||
149 | |||
150 | # mmc device partitions are named mmcblk0p1, mmcblk0p2.. | ||
151 | prefix = 'p' if part.disk.startswith('mmcblk') else '' | ||
152 | device_name = "/dev/%s%s%d" % (part.disk, prefix, pnum) | ||
153 | |||
154 | opts = part.fsopts if part.fsopts else "defaults" | ||
155 | line = "\t".join([device_name, part.mountpoint, part.fstype, | ||
156 | opts, "0", "0"]) + "\n" | ||
157 | |||
158 | fstab_lines.append(line) | ||
159 | updated = True | ||
160 | |||
161 | return updated | ||
162 | |||
163 | def set_bootimg_dir(self, bootimg_dir): | ||
164 | """ | ||
165 | Accessor for bootimg_dir, the actual location used for the source | ||
166 | of the bootimg. Should be set by source plugins (only if they | ||
167 | change the default bootimg source) so the correct info gets | ||
168 | displayed for print_outimage_info(). | ||
169 | """ | ||
170 | self.bootimg_dir = bootimg_dir | ||
171 | |||
172 | def _get_parts(self): | ||
173 | if not self.ks: | ||
174 | raise CreatorError("Failed to get partition info, " | ||
175 | "please check your kickstart setting.") | ||
176 | |||
177 | # Set a default partition if no partition is given out | ||
178 | if not self.ks.partitions: | ||
179 | partstr = "part / --size 1900 --ondisk sda --fstype=ext3" | ||
180 | args = partstr.split() | ||
181 | part = self.ks.parse(args[1:]) | ||
182 | if part not in self.ks.partitions: | ||
183 | self.ks.partitions.append(part) | ||
184 | |||
185 | # partitions list from kickstart file | ||
186 | return self.ks.partitions | ||
187 | |||
188 | def _full_name(self, name, extention): | ||
189 | """ Construct full file name for a file we generate. """ | ||
190 | return "%s-%s.%s" % (self.name, name, extention) | ||
191 | |||
192 | def _full_path(self, path, name, extention): | ||
193 | """ Construct full file path to a file we generate. """ | ||
194 | return os.path.join(path, self._full_name(name, extention)) | ||
195 | |||
196 | def get_default_source_plugin(self): | ||
197 | """ | ||
198 | The default source plugin i.e. the plugin that's consulted for | ||
199 | overall image generation tasks outside of any particular | ||
200 | partition. For convenience, we just hang it off the | ||
201 | bootloader handler since it's the one non-partition object in | ||
202 | any setup. By default the default plugin is set to the same | ||
203 | plugin as the /boot partition; since we hang it off the | ||
204 | bootloader object, the default can be explicitly set using the | ||
205 | --source bootloader param. | ||
206 | """ | ||
207 | return self.ks.bootloader.source | ||
208 | |||
209 | # | ||
210 | # Actual implemention | ||
211 | # | ||
212 | def create(self): | ||
213 | """ | ||
214 | For 'wic', we already have our build artifacts - we just create | ||
215 | filesystems from the artifacts directly and combine them into | ||
216 | a partitioned image. | ||
217 | """ | ||
218 | parts = self._get_parts() | ||
219 | |||
220 | self._image = Image(self.native_sysroot) | ||
221 | |||
222 | disk_ids = {} | ||
223 | for num, part in enumerate(parts, 1): | ||
224 | # as a convenience, set source to the boot partition source | ||
225 | # instead of forcing it to be set via bootloader --source | ||
226 | if not self.ks.bootloader.source and part.mountpoint == "/boot": | ||
227 | self.ks.bootloader.source = part.source | ||
228 | |||
229 | # generate parition UUIDs | ||
230 | if not part.uuid and part.use_uuid: | ||
231 | if self.ptable_format == 'gpt': | ||
232 | part.uuid = str(uuid.uuid4()) | ||
233 | else: # msdos partition table | ||
234 | if part.disk not in disk_ids: | ||
235 | disk_ids[part.disk] = int.from_bytes(os.urandom(4), 'little') | ||
236 | disk_id = disk_ids[part.disk] | ||
237 | part.uuid = '%0x-%02d' % (disk_id, self._get_part_num(num, parts)) | ||
238 | |||
239 | fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR")) | ||
240 | |||
241 | for part in parts: | ||
242 | # get rootfs size from bitbake variable if it's not set in .ks file | ||
243 | if not part.size: | ||
244 | # and if rootfs name is specified for the partition | ||
245 | image_name = self.rootfs_dir.get(part.rootfs_dir) | ||
246 | if image_name and os.path.sep not in image_name: | ||
247 | # Bitbake variable ROOTFS_SIZE is calculated in | ||
248 | # Image._get_rootfs_size method from meta/lib/oe/image.py | ||
249 | # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT, | ||
250 | # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE | ||
251 | rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name) | ||
252 | if rsize_bb: | ||
253 | part.size = int(round(float(rsize_bb))) | ||
254 | # need to create the filesystems in order to get their | ||
255 | # sizes before we can add them and do the layout. | ||
256 | # Image.create() actually calls __format_disks() to create | ||
257 | # the disk images and carve out the partitions, then | ||
258 | # self.assemble() calls Image.assemble() which calls | ||
259 | # __write_partitition() for each partition to dd the fs | ||
260 | # into the partitions. | ||
261 | part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir, | ||
262 | self.bootimg_dir, self.kernel_dir, self.native_sysroot) | ||
263 | |||
264 | |||
265 | self._image.add_partition(part.disk_size, | ||
266 | part.disk, | ||
267 | part.mountpoint, | ||
268 | part.source_file, | ||
269 | part.fstype, | ||
270 | part.label, | ||
271 | fsopts=part.fsopts, | ||
272 | boot=part.active, | ||
273 | align=part.align, | ||
274 | no_table=part.no_table, | ||
275 | part_type=part.part_type, | ||
276 | uuid=part.uuid, | ||
277 | system_id=part.system_id) | ||
278 | |||
279 | if fstab_path: | ||
280 | shutil.move(fstab_path + ".orig", fstab_path) | ||
281 | |||
282 | self._image.layout_partitions(self.ptable_format) | ||
283 | |||
284 | for disk_name, disk in self._image.disks.items(): | ||
285 | full_path = self._full_path(self.workdir, disk_name, "direct") | ||
286 | msger.debug("Adding disk %s as %s with size %s bytes" \ | ||
287 | % (disk_name, full_path, disk['min_size'])) | ||
288 | disk_obj = DiskImage(full_path, disk['min_size']) | ||
289 | #self._disks[disk_name] = disk_obj | ||
290 | self._image.add_disk(disk_name, disk_obj, disk_ids.get(disk_name)) | ||
291 | |||
292 | self._image.create() | ||
293 | |||
294 | def assemble(self): | ||
295 | """ | ||
296 | Assemble partitions into disk image(s) | ||
297 | """ | ||
298 | for disk_name, disk in self._image.disks.items(): | ||
299 | full_path = self._full_path(self.workdir, disk_name, "direct") | ||
300 | msger.debug("Assembling disk %s as %s with size %s bytes" \ | ||
301 | % (disk_name, full_path, disk['min_size'])) | ||
302 | self._image.assemble(full_path) | ||
303 | |||
304 | def finalize(self): | ||
305 | """ | ||
306 | Finalize the disk image. | ||
307 | |||
308 | For example, prepare the image to be bootable by e.g. | ||
309 | creating and installing a bootloader configuration. | ||
310 | """ | ||
311 | source_plugin = self.get_default_source_plugin() | ||
312 | if source_plugin: | ||
313 | self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods) | ||
314 | for disk_name, disk in self._image.disks.items(): | ||
315 | self._source_methods["do_install_disk"](disk, disk_name, self, | ||
316 | self.workdir, | ||
317 | self.oe_builddir, | ||
318 | self.bootimg_dir, | ||
319 | self.kernel_dir, | ||
320 | self.native_sysroot) | ||
321 | |||
322 | for disk_name, disk in self._image.disks.items(): | ||
323 | full_path = self._full_path(self.workdir, disk_name, "direct") | ||
324 | # Generate .bmap | ||
325 | if self.bmap: | ||
326 | msger.debug("Generating bmap file for %s" % disk_name) | ||
327 | exec_native_cmd("bmaptool create %s -o %s.bmap" % (full_path, full_path), | ||
328 | self.native_sysroot) | ||
329 | # Compress the image | ||
330 | if self.compressor: | ||
331 | msger.debug("Compressing disk %s with %s" % (disk_name, self.compressor)) | ||
332 | exec_cmd("%s %s" % (self.compressor, full_path)) | ||
333 | |||
334 | def print_outimage_info(self): | ||
335 | """ | ||
336 | Print the image(s) and artifacts used, for the user. | ||
337 | """ | ||
338 | msg = "The new image(s) can be found here:\n" | ||
339 | |||
340 | parts = self._get_parts() | ||
341 | |||
342 | for disk_name in self._image.disks: | ||
343 | extension = "direct" + {"gzip": ".gz", | ||
344 | "bzip2": ".bz2", | ||
345 | "xz": ".xz", | ||
346 | "": ""}.get(self.compressor) | ||
347 | full_path = self._full_path(self.outdir, disk_name, extension) | ||
348 | msg += ' %s\n\n' % full_path | ||
349 | |||
350 | msg += 'The following build artifacts were used to create the image(s):\n' | ||
351 | for part in parts: | ||
352 | if part.rootfs_dir is None: | ||
353 | continue | ||
354 | if part.mountpoint == '/': | ||
355 | suffix = ':' | ||
356 | else: | ||
357 | suffix = '["%s"]:' % (part.mountpoint or part.label) | ||
358 | msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.rootfs_dir) | ||
359 | |||
360 | msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir | ||
361 | msg += ' KERNEL_DIR: %s\n' % self.kernel_dir | ||
362 | msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot | ||
363 | |||
364 | msger.info(msg) | ||
365 | |||
366 | @property | ||
367 | def rootdev(self): | ||
368 | """ | ||
369 | Get root device name to use as a 'root' parameter | ||
370 | in kernel command line. | ||
371 | |||
372 | Assume partition order same as in wks | ||
373 | """ | ||
374 | parts = self._get_parts() | ||
375 | for num, part in enumerate(parts, 1): | ||
376 | if part.mountpoint == "/": | ||
377 | if part.uuid: | ||
378 | return "PARTUUID=%s" % part.uuid | ||
379 | else: | ||
380 | suffix = 'p' if part.disk.startswith('mmcblk') else '' | ||
381 | pnum = self._get_part_num(num, parts) | ||
382 | return "/dev/%s%s%-d" % (part.disk, suffix, pnum) | ||
383 | |||
384 | def cleanup(self): | ||
385 | if self._image: | ||
386 | try: | ||
387 | self._image.cleanup() | ||
388 | except ImageError as err: | ||
389 | msger.warning("%s" % err) | ||
390 | |||
391 | # Move results to the output dir | ||
392 | if not os.path.exists(self.outdir): | ||
393 | os.makedirs(self.outdir) | ||
394 | |||
395 | for fname in os.listdir(self.workdir): | ||
396 | path = os.path.join(self.workdir, fname) | ||
397 | if os.path.isfile(path): | ||
398 | shutil.move(path, os.path.join(self.outdir, fname)) | ||
399 | |||
400 | # remove work directory | ||
401 | shutil.rmtree(self.workdir, ignore_errors=True) | ||
402 | |||
diff --git a/scripts/lib/wic/plugins/imager/direct_plugin.py b/scripts/lib/wic/plugins/imager/direct_plugin.py index e839d2feae..91a9792814 100644 --- a/scripts/lib/wic/plugins/imager/direct_plugin.py +++ b/scripts/lib/wic/plugins/imager/direct_plugin.py | |||
@@ -23,16 +23,23 @@ | |||
23 | # AUTHORS | 23 | # AUTHORS |
24 | # Tom Zanussi <tom.zanussi (at] linux.intel.com> | 24 | # Tom Zanussi <tom.zanussi (at] linux.intel.com> |
25 | # | 25 | # |
26 | import os | ||
27 | import shutil | ||
28 | import uuid | ||
29 | import tempfile | ||
30 | import time | ||
26 | 31 | ||
27 | from time import strftime | 32 | from time import strftime |
28 | |||
29 | from os.path import basename, splitext | 33 | from os.path import basename, splitext |
30 | from wic.utils import errors | ||
31 | from wic.ksparser import KickStart, KickStartError | ||
32 | from wic import msger | ||
33 | 34 | ||
34 | import wic.imager.direct as direct | 35 | from wic import msger |
36 | from wic.ksparser import KickStart, KickStartError | ||
37 | from wic.plugin import pluginmgr | ||
35 | from wic.pluginbase import ImagerPlugin | 38 | from wic.pluginbase import ImagerPlugin |
39 | from wic.utils import errors | ||
40 | from wic.utils.errors import CreatorError, ImageError | ||
41 | from wic.utils.oe.misc import get_bitbake_var, exec_cmd, exec_native_cmd | ||
42 | from wic.utils.partitionedfs import Image | ||
36 | 43 | ||
37 | class DirectPlugin(ImagerPlugin): | 44 | class DirectPlugin(ImagerPlugin): |
38 | """ | 45 | """ |
@@ -87,16 +94,10 @@ class DirectPlugin(ImagerPlugin): | |||
87 | strftime("%Y%m%d%H%M")) | 94 | strftime("%Y%m%d%H%M")) |
88 | krootfs_dir = cls.__rootfs_dir_to_dict(rootfs_dir) | 95 | krootfs_dir = cls.__rootfs_dir_to_dict(rootfs_dir) |
89 | 96 | ||
90 | creator = direct.DirectImageCreator(image_name, | 97 | creator = DirectImageCreator(image_name, ksobj, oe_builddir, |
91 | ksobj, | 98 | image_output_dir, krootfs_dir, |
92 | oe_builddir, | 99 | bootimg_dir, kernel_dir, native_sysroot, |
93 | image_output_dir, | 100 | compressor, opts.bmap) |
94 | krootfs_dir, | ||
95 | bootimg_dir, | ||
96 | kernel_dir, | ||
97 | native_sysroot, | ||
98 | compressor, | ||
99 | opts.bmap) | ||
100 | try: | 101 | try: |
101 | creator.create() | 102 | creator.create() |
102 | creator.assemble() | 103 | creator.assemble() |
@@ -108,4 +109,367 @@ class DirectPlugin(ImagerPlugin): | |||
108 | finally: | 109 | finally: |
109 | creator.cleanup() | 110 | creator.cleanup() |
110 | 111 | ||
111 | return 0 | 112 | disk_methods = { |
113 | "do_install_disk":None, | ||
114 | } | ||
115 | |||
116 | class DiskImage(): | ||
117 | """ | ||
118 | A Disk backed by a file. | ||
119 | """ | ||
120 | def __init__(self, device, size): | ||
121 | self.size = size | ||
122 | self.device = device | ||
123 | self.created = False | ||
124 | |||
125 | def exists(self): | ||
126 | return os.path.exists(self.device) | ||
127 | |||
128 | def create(self): | ||
129 | if self.created: | ||
130 | return | ||
131 | # create sparse disk image | ||
132 | with open(self.device, 'w') as sparse: | ||
133 | os.ftruncate(sparse.fileno(), self.size) | ||
134 | |||
135 | self.created = True | ||
136 | |||
137 | class DirectImageCreator: | ||
138 | """ | ||
139 | Installs a system into a file containing a partitioned disk image. | ||
140 | |||
141 | DirectImageCreator is an advanced ImageCreator subclass; an image | ||
142 | file is formatted with a partition table, each partition created | ||
143 | from a rootfs or other OpenEmbedded build artifact and dd'ed into | ||
144 | the virtual disk. The disk image can subsequently be dd'ed onto | ||
145 | media and used on actual hardware. | ||
146 | """ | ||
147 | |||
148 | def __init__(self, image_name, ksobj, oe_builddir, image_output_dir, | ||
149 | rootfs_dir, bootimg_dir, kernel_dir, native_sysroot, | ||
150 | compressor, bmap=False): | ||
151 | """ | ||
152 | Initialize a DirectImageCreator instance. | ||
153 | |||
154 | This method takes the same arguments as ImageCreator.__init__() | ||
155 | """ | ||
156 | self.name = image_name | ||
157 | self.outdir = image_output_dir | ||
158 | self.workdir = tempfile.mktemp(prefix='wic') | ||
159 | self.ks = ksobj | ||
160 | |||
161 | self.__image = None | ||
162 | self.__disks = {} | ||
163 | self.__disk_format = "direct" | ||
164 | self._disk_names = [] | ||
165 | self.ptable_format = self.ks.bootloader.ptable | ||
166 | |||
167 | self.oe_builddir = oe_builddir | ||
168 | self.rootfs_dir = rootfs_dir | ||
169 | self.bootimg_dir = bootimg_dir | ||
170 | self.kernel_dir = kernel_dir | ||
171 | self.native_sysroot = native_sysroot | ||
172 | self.compressor = compressor | ||
173 | self.bmap = bmap | ||
174 | |||
175 | def _get_part_num(self, num, parts): | ||
176 | """calculate the real partition number, accounting for partitions not | ||
177 | in the partition table and logical partitions | ||
178 | """ | ||
179 | realnum = 0 | ||
180 | for pnum, part in enumerate(parts, 1): | ||
181 | if not part.no_table: | ||
182 | realnum += 1 | ||
183 | if pnum == num: | ||
184 | if part.no_table: | ||
185 | return 0 | ||
186 | if self.ptable_format == 'msdos' and realnum > 3: | ||
187 | # account for logical partition numbering, ex. sda5.. | ||
188 | return realnum + 1 | ||
189 | return realnum | ||
190 | |||
191 | def _write_fstab(self, image_rootfs): | ||
192 | """overriden to generate fstab (temporarily) in rootfs. This is called | ||
193 | from _create, make sure it doesn't get called from | ||
194 | BaseImage.create() | ||
195 | """ | ||
196 | if not image_rootfs: | ||
197 | return | ||
198 | |||
199 | fstab_path = image_rootfs + "/etc/fstab" | ||
200 | if not os.path.isfile(fstab_path): | ||
201 | return | ||
202 | |||
203 | with open(fstab_path) as fstab: | ||
204 | fstab_lines = fstab.readlines() | ||
205 | |||
206 | if self._update_fstab(fstab_lines, self._get_parts()): | ||
207 | shutil.copyfile(fstab_path, fstab_path + ".orig") | ||
208 | |||
209 | with open(fstab_path, "w") as fstab: | ||
210 | fstab.writelines(fstab_lines) | ||
211 | |||
212 | return fstab_path | ||
213 | |||
214 | def _update_fstab(self, fstab_lines, parts): | ||
215 | """Assume partition order same as in wks""" | ||
216 | updated = False | ||
217 | for num, part in enumerate(parts, 1): | ||
218 | pnum = self._get_part_num(num, parts) | ||
219 | if not pnum or not part.mountpoint \ | ||
220 | or part.mountpoint in ("/", "/boot"): | ||
221 | continue | ||
222 | |||
223 | # mmc device partitions are named mmcblk0p1, mmcblk0p2.. | ||
224 | prefix = 'p' if part.disk.startswith('mmcblk') else '' | ||
225 | device_name = "/dev/%s%s%d" % (part.disk, prefix, pnum) | ||
226 | |||
227 | opts = part.fsopts if part.fsopts else "defaults" | ||
228 | line = "\t".join([device_name, part.mountpoint, part.fstype, | ||
229 | opts, "0", "0"]) + "\n" | ||
230 | |||
231 | fstab_lines.append(line) | ||
232 | updated = True | ||
233 | |||
234 | return updated | ||
235 | |||
236 | def set_bootimg_dir(self, bootimg_dir): | ||
237 | """ | ||
238 | Accessor for bootimg_dir, the actual location used for the source | ||
239 | of the bootimg. Should be set by source plugins (only if they | ||
240 | change the default bootimg source) so the correct info gets | ||
241 | displayed for print_outimage_info(). | ||
242 | """ | ||
243 | self.bootimg_dir = bootimg_dir | ||
244 | |||
245 | def _get_parts(self): | ||
246 | if not self.ks: | ||
247 | raise CreatorError("Failed to get partition info, " | ||
248 | "please check your kickstart setting.") | ||
249 | |||
250 | # Set a default partition if no partition is given out | ||
251 | if not self.ks.partitions: | ||
252 | partstr = "part / --size 1900 --ondisk sda --fstype=ext3" | ||
253 | args = partstr.split() | ||
254 | part = self.ks.parse(args[1:]) | ||
255 | if part not in self.ks.partitions: | ||
256 | self.ks.partitions.append(part) | ||
257 | |||
258 | # partitions list from kickstart file | ||
259 | return self.ks.partitions | ||
260 | |||
261 | def _full_name(self, name, extention): | ||
262 | """ Construct full file name for a file we generate. """ | ||
263 | return "%s-%s.%s" % (self.name, name, extention) | ||
264 | |||
265 | def _full_path(self, path, name, extention): | ||
266 | """ Construct full file path to a file we generate. """ | ||
267 | return os.path.join(path, self._full_name(name, extention)) | ||
268 | |||
269 | def get_default_source_plugin(self): | ||
270 | """ | ||
271 | The default source plugin i.e. the plugin that's consulted for | ||
272 | overall image generation tasks outside of any particular | ||
273 | partition. For convenience, we just hang it off the | ||
274 | bootloader handler since it's the one non-partition object in | ||
275 | any setup. By default the default plugin is set to the same | ||
276 | plugin as the /boot partition; since we hang it off the | ||
277 | bootloader object, the default can be explicitly set using the | ||
278 | --source bootloader param. | ||
279 | """ | ||
280 | return self.ks.bootloader.source | ||
281 | |||
282 | # | ||
283 | # Actual implemention | ||
284 | # | ||
285 | def create(self): | ||
286 | """ | ||
287 | For 'wic', we already have our build artifacts - we just create | ||
288 | filesystems from the artifacts directly and combine them into | ||
289 | a partitioned image. | ||
290 | """ | ||
291 | parts = self._get_parts() | ||
292 | |||
293 | self._image = Image(self.native_sysroot) | ||
294 | |||
295 | disk_ids = {} | ||
296 | for num, part in enumerate(parts, 1): | ||
297 | # as a convenience, set source to the boot partition source | ||
298 | # instead of forcing it to be set via bootloader --source | ||
299 | if not self.ks.bootloader.source and part.mountpoint == "/boot": | ||
300 | self.ks.bootloader.source = part.source | ||
301 | |||
302 | # generate parition UUIDs | ||
303 | if not part.uuid and part.use_uuid: | ||
304 | if self.ptable_format == 'gpt': | ||
305 | part.uuid = str(uuid.uuid4()) | ||
306 | else: # msdos partition table | ||
307 | if part.disk not in disk_ids: | ||
308 | disk_ids[part.disk] = int.from_bytes(os.urandom(4), 'little') | ||
309 | disk_id = disk_ids[part.disk] | ||
310 | part.uuid = '%0x-%02d' % (disk_id, self._get_part_num(num, parts)) | ||
311 | |||
312 | fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR")) | ||
313 | |||
314 | for part in parts: | ||
315 | # get rootfs size from bitbake variable if it's not set in .ks file | ||
316 | if not part.size: | ||
317 | # and if rootfs name is specified for the partition | ||
318 | image_name = self.rootfs_dir.get(part.rootfs_dir) | ||
319 | if image_name and os.path.sep not in image_name: | ||
320 | # Bitbake variable ROOTFS_SIZE is calculated in | ||
321 | # Image._get_rootfs_size method from meta/lib/oe/image.py | ||
322 | # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT, | ||
323 | # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE | ||
324 | rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name) | ||
325 | if rsize_bb: | ||
326 | part.size = int(round(float(rsize_bb))) | ||
327 | # need to create the filesystems in order to get their | ||
328 | # sizes before we can add them and do the layout. | ||
329 | # Image.create() actually calls __format_disks() to create | ||
330 | # the disk images and carve out the partitions, then | ||
331 | # self.assemble() calls Image.assemble() which calls | ||
332 | # __write_partitition() for each partition to dd the fs | ||
333 | # into the partitions. | ||
334 | part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir, | ||
335 | self.bootimg_dir, self.kernel_dir, self.native_sysroot) | ||
336 | |||
337 | |||
338 | self._image.add_partition(part.disk_size, | ||
339 | part.disk, | ||
340 | part.mountpoint, | ||
341 | part.source_file, | ||
342 | part.fstype, | ||
343 | part.label, | ||
344 | fsopts=part.fsopts, | ||
345 | boot=part.active, | ||
346 | align=part.align, | ||
347 | no_table=part.no_table, | ||
348 | part_type=part.part_type, | ||
349 | uuid=part.uuid, | ||
350 | system_id=part.system_id) | ||
351 | |||
352 | if fstab_path: | ||
353 | shutil.move(fstab_path + ".orig", fstab_path) | ||
354 | |||
355 | self._image.layout_partitions(self.ptable_format) | ||
356 | |||
357 | for disk_name, disk in self._image.disks.items(): | ||
358 | full_path = self._full_path(self.workdir, disk_name, "direct") | ||
359 | msger.debug("Adding disk %s as %s with size %s bytes" \ | ||
360 | % (disk_name, full_path, disk['min_size'])) | ||
361 | disk_obj = DiskImage(full_path, disk['min_size']) | ||
362 | #self._disks[disk_name] = disk_obj | ||
363 | self._image.add_disk(disk_name, disk_obj, disk_ids.get(disk_name)) | ||
364 | |||
365 | self._image.create() | ||
366 | |||
367 | def assemble(self): | ||
368 | """ | ||
369 | Assemble partitions into disk image(s) | ||
370 | """ | ||
371 | for disk_name, disk in self._image.disks.items(): | ||
372 | full_path = self._full_path(self.workdir, disk_name, "direct") | ||
373 | msger.debug("Assembling disk %s as %s with size %s bytes" \ | ||
374 | % (disk_name, full_path, disk['min_size'])) | ||
375 | self._image.assemble(full_path) | ||
376 | |||
377 | def finalize(self): | ||
378 | """ | ||
379 | Finalize the disk image. | ||
380 | |||
381 | For example, prepare the image to be bootable by e.g. | ||
382 | creating and installing a bootloader configuration. | ||
383 | """ | ||
384 | source_plugin = self.get_default_source_plugin() | ||
385 | if source_plugin: | ||
386 | self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods) | ||
387 | for disk_name, disk in self._image.disks.items(): | ||
388 | self._source_methods["do_install_disk"](disk, disk_name, self, | ||
389 | self.workdir, | ||
390 | self.oe_builddir, | ||
391 | self.bootimg_dir, | ||
392 | self.kernel_dir, | ||
393 | self.native_sysroot) | ||
394 | |||
395 | for disk_name, disk in self._image.disks.items(): | ||
396 | full_path = self._full_path(self.workdir, disk_name, "direct") | ||
397 | # Generate .bmap | ||
398 | if self.bmap: | ||
399 | msger.debug("Generating bmap file for %s" % disk_name) | ||
400 | exec_native_cmd("bmaptool create %s -o %s.bmap" % (full_path, full_path), | ||
401 | self.native_sysroot) | ||
402 | # Compress the image | ||
403 | if self.compressor: | ||
404 | msger.debug("Compressing disk %s with %s" % (disk_name, self.compressor)) | ||
405 | exec_cmd("%s %s" % (self.compressor, full_path)) | ||
406 | |||
407 | def print_outimage_info(self): | ||
408 | """ | ||
409 | Print the image(s) and artifacts used, for the user. | ||
410 | """ | ||
411 | msg = "The new image(s) can be found here:\n" | ||
412 | |||
413 | parts = self._get_parts() | ||
414 | |||
415 | for disk_name in self._image.disks: | ||
416 | extension = "direct" + {"gzip": ".gz", | ||
417 | "bzip2": ".bz2", | ||
418 | "xz": ".xz", | ||
419 | "": ""}.get(self.compressor) | ||
420 | full_path = self._full_path(self.outdir, disk_name, extension) | ||
421 | msg += ' %s\n\n' % full_path | ||
422 | |||
423 | msg += 'The following build artifacts were used to create the image(s):\n' | ||
424 | for part in parts: | ||
425 | if part.rootfs_dir is None: | ||
426 | continue | ||
427 | if part.mountpoint == '/': | ||
428 | suffix = ':' | ||
429 | else: | ||
430 | suffix = '["%s"]:' % (part.mountpoint or part.label) | ||
431 | msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.rootfs_dir) | ||
432 | |||
433 | msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir | ||
434 | msg += ' KERNEL_DIR: %s\n' % self.kernel_dir | ||
435 | msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot | ||
436 | |||
437 | msger.info(msg) | ||
438 | |||
439 | @property | ||
440 | def rootdev(self): | ||
441 | """ | ||
442 | Get root device name to use as a 'root' parameter | ||
443 | in kernel command line. | ||
444 | |||
445 | Assume partition order same as in wks | ||
446 | """ | ||
447 | parts = self._get_parts() | ||
448 | for num, part in enumerate(parts, 1): | ||
449 | if part.mountpoint == "/": | ||
450 | if part.uuid: | ||
451 | return "PARTUUID=%s" % part.uuid | ||
452 | else: | ||
453 | suffix = 'p' if part.disk.startswith('mmcblk') else '' | ||
454 | pnum = self._get_part_num(num, parts) | ||
455 | return "/dev/%s%s%-d" % (part.disk, suffix, pnum) | ||
456 | |||
457 | def cleanup(self): | ||
458 | if self._image: | ||
459 | try: | ||
460 | self._image.cleanup() | ||
461 | except ImageError as err: | ||
462 | msger.warning("%s" % err) | ||
463 | |||
464 | # Move results to the output dir | ||
465 | if not os.path.exists(self.outdir): | ||
466 | os.makedirs(self.outdir) | ||
467 | |||
468 | for fname in os.listdir(self.workdir): | ||
469 | path = os.path.join(self.workdir, fname) | ||
470 | if os.path.isfile(path): | ||
471 | shutil.move(path, os.path.join(self.outdir, fname)) | ||
472 | |||
473 | # remove work directory | ||
474 | shutil.rmtree(self.workdir, ignore_errors=True) | ||
475 | |||