summaryrefslogtreecommitdiffstats
path: root/scripts/lib/wic/plugins/imager/direct.py
diff options
context:
space:
mode:
authorEd Bartosh <ed.bartosh@linux.intel.com>2017-01-31 11:46:11 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2017-02-02 17:37:44 +0000
commit53bd29d65edbc778f712659138644b9e1fb4b273 (patch)
tree5e5345d7191bbe24d9a0311989e812eb6ff16155 /scripts/lib/wic/plugins/imager/direct.py
parent8fda677a08856fc82990742006508fa33f597a41 (diff)
downloadpoky-53bd29d65edbc778f712659138644b9e1fb4b273.tar.gz
wic: renamd direct_plugin.py -> direct.py
As this files is located in plugins/imager subdirectory it's obvious that it's an imager plugin. Renamed to direct.py to be consistent with plugin naming scheme. [YOCTO #10619] (From OE-Core rev: d5db8c2ee91bdd51bfbb2ebf61aea8ff0378d512) 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.py')
-rw-r--r--scripts/lib/wic/plugins/imager/direct.py418
1 files changed, 418 insertions, 0 deletions
diff --git a/scripts/lib/wic/plugins/imager/direct.py b/scripts/lib/wic/plugins/imager/direct.py
new file mode 100644
index 0000000000..9cd7068333
--- /dev/null
+++ b/scripts/lib/wic/plugins/imager/direct.py
@@ -0,0 +1,418 @@
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' imager plugin class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26import os
27import shutil
28import uuid
29import tempfile
30
31from time import strftime
32from os.path import basename, splitext
33
34from wic import msger
35from wic.ksparser import KickStart, KickStartError
36from wic.plugin import pluginmgr
37from wic.pluginbase import ImagerPlugin
38from wic.utils import errors
39from wic.utils.errors import CreatorError, ImageError
40from wic.utils.oe.misc import get_bitbake_var, exec_cmd, exec_native_cmd
41from wic.utils.partitionedfs import Image
42
43class DirectPlugin(ImagerPlugin):
44 """
45 Install a system into a file containing a partitioned disk image.
46
47 An image file is formatted with a partition table, each partition
48 created from a rootfs or other OpenEmbedded build artifact and dd'ed
49 into the virtual disk. The disk image can subsequently be dd'ed onto
50 media and used on actual hardware.
51 """
52
53 name = 'direct'
54
55 @staticmethod
56 def do_create(opts, *args):
57 """
58 Create direct image, called from creator as 'direct' cmd
59 """
60 native_sysroot, kernel_dir, bootimg_dir, rootfs_dir, ksconf, \
61 outdir, oe_builddir, compressor = args
62
63 try:
64 ksobj = KickStart(ksconf)
65 except KickStartError as err:
66 msger.error(str(err))
67
68 name = "%s-%s" % (splitext(basename(ksconf))[0],
69 strftime("%Y%m%d%H%M"))
70
71 # parse possible 'rootfs=name' items
72 krootfs_dir = dict(rdir.split('=') for rdir in rootfs_dir.split(' '))
73
74 creator = DirectImageCreator(name, ksobj, oe_builddir, outdir,
75 krootfs_dir, bootimg_dir, kernel_dir,
76 native_sysroot, compressor, opts.bmap)
77 try:
78 creator.create()
79 creator.assemble()
80 creator.finalize()
81 creator.print_info()
82 except errors.CreatorError:
83 raise
84 finally:
85 creator.cleanup()
86
87class DiskImage():
88 """
89 A Disk backed by a file.
90 """
91 def __init__(self, device, size):
92 self.size = size
93 self.device = device
94 self.created = False
95
96 def create(self):
97 if self.created:
98 return
99 # create sparse disk image
100 with open(self.device, 'w') as sparse:
101 os.ftruncate(sparse.fileno(), self.size)
102
103 self.created = True
104
105class DirectImageCreator:
106 """
107 Installs a system into a file containing a partitioned disk image.
108
109 DirectImageCreator is an advanced ImageCreator subclass; an image
110 file is formatted with a partition table, each partition created
111 from a rootfs or other OpenEmbedded build artifact and dd'ed into
112 the virtual disk. The disk image can subsequently be dd'ed onto
113 media and used on actual hardware.
114 """
115
116 def __init__(self, name, ksobj, oe_builddir, outdir,
117 rootfs_dir, bootimg_dir, kernel_dir,
118 native_sysroot, compressor, bmap=False):
119 """
120 Initialize a DirectImageCreator instance.
121
122 This method takes the same arguments as ImageCreator.__init__()
123 """
124 self.name = name
125 self.outdir = outdir
126 self.workdir = tempfile.mktemp(prefix='wic')
127 self.ks = ksobj
128
129 self._image = None
130 self._disks = {}
131 self._disk_format = "direct"
132 self._disk_names = []
133 self.ptable_format = self.ks.bootloader.ptable
134
135 self.oe_builddir = oe_builddir
136 self.rootfs_dir = rootfs_dir
137 self.bootimg_dir = bootimg_dir
138 self.kernel_dir = kernel_dir
139 self.native_sysroot = native_sysroot
140 self.compressor = compressor
141 self.bmap = bmap
142
143 def _get_part_num(self, num, parts):
144 """calculate the real partition number, accounting for partitions not
145 in the partition table and logical partitions
146 """
147 realnum = 0
148 for pnum, part in enumerate(parts, 1):
149 if not part.no_table:
150 realnum += 1
151 if pnum == num:
152 if part.no_table:
153 return 0
154 if self.ptable_format == 'msdos' and realnum > 3:
155 # account for logical partition numbering, ex. sda5..
156 return realnum + 1
157 return realnum
158
159 def _write_fstab(self, image_rootfs):
160 """overriden to generate fstab (temporarily) in rootfs. This is called
161 from _create, make sure it doesn't get called from
162 BaseImage.create()
163 """
164 if not image_rootfs:
165 return
166
167 fstab_path = image_rootfs + "/etc/fstab"
168 if not os.path.isfile(fstab_path):
169 return
170
171 with open(fstab_path) as fstab:
172 fstab_lines = fstab.readlines()
173
174 if self._update_fstab(fstab_lines, self._get_parts()):
175 shutil.copyfile(fstab_path, fstab_path + ".orig")
176
177 with open(fstab_path, "w") as fstab:
178 fstab.writelines(fstab_lines)
179
180 return fstab_path
181
182 def _update_fstab(self, fstab_lines, parts):
183 """Assume partition order same as in wks"""
184 updated = False
185 for num, part in enumerate(parts, 1):
186 pnum = self._get_part_num(num, parts)
187 if not pnum or not part.mountpoint \
188 or part.mountpoint in ("/", "/boot"):
189 continue
190
191 # mmc device partitions are named mmcblk0p1, mmcblk0p2..
192 prefix = 'p' if part.disk.startswith('mmcblk') else ''
193 device_name = "/dev/%s%s%d" % (part.disk, prefix, pnum)
194
195 opts = part.fsopts if part.fsopts else "defaults"
196 line = "\t".join([device_name, part.mountpoint, part.fstype,
197 opts, "0", "0"]) + "\n"
198
199 fstab_lines.append(line)
200 updated = True
201
202 return updated
203
204 def set_bootimg_dir(self, bootimg_dir):
205 """
206 Accessor for bootimg_dir, the actual location used for the source
207 of the bootimg. Should be set by source plugins (only if they
208 change the default bootimg source) so the correct info gets
209 displayed for print_outimage_info().
210 """
211 self.bootimg_dir = bootimg_dir
212
213 def _get_parts(self):
214 if not self.ks:
215 raise CreatorError("Failed to get partition info, "
216 "please check your kickstart setting.")
217
218 # Set a default partition if no partition is given out
219 if not self.ks.partitions:
220 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
221 args = partstr.split()
222 part = self.ks.parse(args[1:])
223 if part not in self.ks.partitions:
224 self.ks.partitions.append(part)
225
226 # partitions list from kickstart file
227 return self.ks.partitions
228
229 def _full_path(self, path, name, extention):
230 """ Construct full file path to a file we generate. """
231 return os.path.join(path, "%s-%s.%s" % (self.name, name, extention))
232
233 #
234 # Actual implemention
235 #
236 def create(self):
237 """
238 For 'wic', we already have our build artifacts - we just create
239 filesystems from the artifacts directly and combine them into
240 a partitioned image.
241 """
242 parts = self._get_parts()
243
244 self._image = Image(self.native_sysroot)
245
246 disk_ids = {}
247 for num, part in enumerate(parts, 1):
248 # as a convenience, set source to the boot partition source
249 # instead of forcing it to be set via bootloader --source
250 if not self.ks.bootloader.source and part.mountpoint == "/boot":
251 self.ks.bootloader.source = part.source
252
253 # generate parition UUIDs
254 if not part.uuid and part.use_uuid:
255 if self.ptable_format == 'gpt':
256 part.uuid = str(uuid.uuid4())
257 else: # msdos partition table
258 if part.disk not in disk_ids:
259 disk_ids[part.disk] = int.from_bytes(os.urandom(4), 'little')
260 disk_id = disk_ids[part.disk]
261 part.uuid = '%0x-%02d' % (disk_id, self._get_part_num(num, parts))
262
263 fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
264
265 for part in parts:
266 # get rootfs size from bitbake variable if it's not set in .ks file
267 if not part.size:
268 # and if rootfs name is specified for the partition
269 image_name = self.rootfs_dir.get(part.rootfs_dir)
270 if image_name and os.path.sep not in image_name:
271 # Bitbake variable ROOTFS_SIZE is calculated in
272 # Image._get_rootfs_size method from meta/lib/oe/image.py
273 # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
274 # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
275 rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name)
276 if rsize_bb:
277 part.size = int(round(float(rsize_bb)))
278 # need to create the filesystems in order to get their
279 # sizes before we can add them and do the layout.
280 # Image.create() actually calls __format_disks() to create
281 # the disk images and carve out the partitions, then
282 # self.assemble() calls Image.assemble() which calls
283 # __write_partitition() for each partition to dd the fs
284 # into the partitions.
285 part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
286 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
287
288
289 self._image.add_partition(part.disk_size, part.disk,
290 part.mountpoint, part.source_file,
291 part.fstype, part.label,
292 fsopts=part.fsopts, boot=part.active,
293 align=part.align, no_table=part.no_table,
294 part_type=part.part_type, uuid=part.uuid,
295 system_id=part.system_id)
296
297 if fstab_path:
298 shutil.move(fstab_path + ".orig", fstab_path)
299
300 self._image.layout_partitions(self.ptable_format)
301
302 for disk_name, disk in self._image.disks.items():
303 full_path = self._full_path(self.workdir, disk_name, "direct")
304 msger.debug("Adding disk %s as %s with size %s bytes" \
305 % (disk_name, full_path, disk['min_size']))
306 disk_obj = DiskImage(full_path, disk['min_size'])
307 #self._disks[disk_name] = disk_obj
308 self._image.add_disk(disk_name, disk_obj, disk_ids.get(disk_name))
309
310 self._image.create()
311
312 def assemble(self):
313 """
314 Assemble partitions into disk image(s)
315 """
316 for disk_name, disk in self._image.disks.items():
317 full_path = self._full_path(self.workdir, disk_name, "direct")
318 msger.debug("Assembling disk %s as %s with size %s bytes" \
319 % (disk_name, full_path, disk['min_size']))
320 self._image.assemble(full_path)
321
322 def finalize(self):
323 """
324 Finalize the disk image.
325
326 For example, prepare the image to be bootable by e.g.
327 creating and installing a bootloader configuration.
328 """
329 source_plugin = self.ks.bootloader.source
330 if source_plugin:
331 name = "do_install_disk"
332 methods = pluginmgr.get_source_plugin_methods(source_plugin,
333 {name: None})
334 for disk_name, disk in self._image.disks.items():
335 methods["do_install_disk"](disk, disk_name, self, self.workdir,
336 self.oe_builddir, self.bootimg_dir,
337 self.kernel_dir, self.native_sysroot)
338
339 for disk_name, disk in self._image.disks.items():
340 full_path = self._full_path(self.workdir, disk_name, "direct")
341 # Generate .bmap
342 if self.bmap:
343 msger.debug("Generating bmap file for %s" % disk_name)
344 exec_native_cmd("bmaptool create %s -o %s.bmap" % (full_path, full_path),
345 self.native_sysroot)
346 # Compress the image
347 if self.compressor:
348 msger.debug("Compressing disk %s with %s" % (disk_name, self.compressor))
349 exec_cmd("%s %s" % (self.compressor, full_path))
350
351 def print_info(self):
352 """
353 Print the image(s) and artifacts used, for the user.
354 """
355 msg = "The new image(s) can be found here:\n"
356
357 parts = self._get_parts()
358
359 for disk_name in self._image.disks:
360 extension = "direct" + {"gzip": ".gz",
361 "bzip2": ".bz2",
362 "xz": ".xz",
363 "": ""}.get(self.compressor)
364 full_path = self._full_path(self.outdir, disk_name, extension)
365 msg += ' %s\n\n' % full_path
366
367 msg += 'The following build artifacts were used to create the image(s):\n'
368 for part in parts:
369 if part.rootfs_dir is None:
370 continue
371 if part.mountpoint == '/':
372 suffix = ':'
373 else:
374 suffix = '["%s"]:' % (part.mountpoint or part.label)
375 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.rootfs_dir)
376
377 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
378 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
379 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
380
381 msger.info(msg)
382
383 @property
384 def rootdev(self):
385 """
386 Get root device name to use as a 'root' parameter
387 in kernel command line.
388
389 Assume partition order same as in wks
390 """
391 parts = self._get_parts()
392 for num, part in enumerate(parts, 1):
393 if part.mountpoint == "/":
394 if part.uuid:
395 return "PARTUUID=%s" % part.uuid
396 else:
397 suffix = 'p' if part.disk.startswith('mmcblk') else ''
398 pnum = self._get_part_num(num, parts)
399 return "/dev/%s%s%-d" % (part.disk, suffix, pnum)
400
401 def cleanup(self):
402 if self._image:
403 try:
404 self._image.cleanup()
405 except ImageError as err:
406 msger.warning("%s" % err)
407
408 # Move results to the output dir
409 if not os.path.exists(self.outdir):
410 os.makedirs(self.outdir)
411
412 for fname in os.listdir(self.workdir):
413 path = os.path.join(self.workdir, fname)
414 if os.path.isfile(path):
415 shutil.move(path, os.path.join(self.outdir, fname))
416
417 # remove work directory
418 shutil.rmtree(self.workdir, ignore_errors=True)