summaryrefslogtreecommitdiffstats
path: root/scripts/lib/wic/plugins/imager/direct_plugin.py
diff options
context:
space:
mode:
authorEd Bartosh <ed.bartosh@linux.intel.com>2017-01-30 23:54:29 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2017-02-02 17:37:44 +0000
commitee10c2f1bb26f86aad141c2b622d9365af431cd7 (patch)
tree22b397351509129e7e11456e7802589062127d86 /scripts/lib/wic/plugins/imager/direct_plugin.py
parentdba8cf377c14665fa759779f9b1af094377cf0c6 (diff)
downloadpoky-ee10c2f1bb26f86aad141c2b622d9365af431cd7.tar.gz
wic: moved content of direct.py to direct_plugin
This move simplifies directory structure and makes further refactoring easier. The code from direct.py was used only in direct_plugin, so it's safe to move it there. [YOCTO #10619] (From OE-Core rev: a8f5ebb26183faa9af6eb72f4dabfcf83aa1e8d4) Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts/lib/wic/plugins/imager/direct_plugin.py')
-rw-r--r--scripts/lib/wic/plugins/imager/direct_plugin.py396
1 files changed, 380 insertions, 16 deletions
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#
26import os
27import shutil
28import uuid
29import tempfile
30import time
26 31
27from time import strftime 32from time import strftime
28
29from os.path import basename, splitext 33from os.path import basename, splitext
30from wic.utils import errors
31from wic.ksparser import KickStart, KickStartError
32from wic import msger
33 34
34import wic.imager.direct as direct 35from wic import msger
36from wic.ksparser import KickStart, KickStartError
37from wic.plugin import pluginmgr
35from wic.pluginbase import ImagerPlugin 38from wic.pluginbase import ImagerPlugin
39from wic.utils import errors
40from wic.utils.errors import CreatorError, ImageError
41from wic.utils.oe.misc import get_bitbake_var, exec_cmd, exec_native_cmd
42from wic.utils.partitionedfs import Image
36 43
37class DirectPlugin(ImagerPlugin): 44class 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 112disk_methods = {
113 "do_install_disk":None,
114}
115
116class 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
137class 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