blob: d5603fa9153589e6c1685ef931805c4583e5ec46 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# 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
27import os
28import shutil
29
30from wic import kickstart, msger
31from wic.utils import fs_related
32from wic.utils.oe.misc import get_bitbake_var
33from wic.utils.partitionedfs import Image
34from wic.utils.errors import CreatorError, ImageError
35from wic.imager.baseimager import BaseImageCreator
36from wic.plugin import pluginmgr
37from wic.utils.oe.misc import exec_cmd
38
39disk_methods = {
40 "do_install_disk":None,
41}
42
43class DirectImageCreator(BaseImageCreator):
44 """
45 Installs a system into a file containing a partitioned disk image.
46
47 DirectImageCreator is an advanced ImageCreator subclass; an image
48 file is formatted with a partition table, each partition created
49 from a rootfs or other OpenEmbedded build artifact and dd'ed into
50 the virtual disk. The disk image can subsequently be dd'ed onto
51 media and used on actual hardware.
52 """
53
54 def __init__(self, oe_builddir, image_output_dir, rootfs_dir, bootimg_dir,
55 kernel_dir, native_sysroot, compressor, creatoropts=None):
56 """
57 Initialize a DirectImageCreator instance.
58
59 This method takes the same arguments as ImageCreator.__init__()
60 """
61 BaseImageCreator.__init__(self, creatoropts)
62
63 self.__image = None
64 self.__disks = {}
65 self.__disk_format = "direct"
66 self._disk_names = []
67 self.ptable_format = self.ks.handler.bootloader.ptable
68
69 self.oe_builddir = oe_builddir
70 if image_output_dir:
71 self.tmpdir = image_output_dir
72 self.rootfs_dir = rootfs_dir
73 self.bootimg_dir = bootimg_dir
74 self.kernel_dir = kernel_dir
75 self.native_sysroot = native_sysroot
76 self.compressor = compressor
77
78 def __get_part_num(self, num, parts):
79 """calculate the real partition number, accounting for partitions not
80 in the partition table and logical partitions
81 """
82 realnum = 0
83 for pnum, part in enumerate(parts, 1):
84 if not part.no_table:
85 realnum += 1
86 if pnum == num:
87 if part.no_table:
88 return 0
89 if self.ptable_format == 'msdos' and realnum > 3:
90 # account for logical partition numbering, ex. sda5..
91 return realnum + 1
92 return realnum
93
94 def _write_fstab(self, image_rootfs):
95 """overriden to generate fstab (temporarily) in rootfs. This is called
96 from _create, make sure it doesn't get called from
97 BaseImage.create()
98 """
99 if not image_rootfs:
100 return
101
102 fstab_path = image_rootfs + "/etc/fstab"
103 if not os.path.isfile(fstab_path):
104 return
105
106 with open(fstab_path) as fstab:
107 fstab_lines = fstab.readlines()
108
109 if self._update_fstab(fstab_lines, self._get_parts()):
110 shutil.copyfile(fstab_path, fstab_path + ".orig")
111
112 with open(fstab_path, "w") as fstab:
113 fstab.writelines(fstab_lines)
114
115 return fstab_path
116
117 def _update_fstab(self, fstab_lines, parts):
118 """Assume partition order same as in wks"""
119 updated = False
120 for num, part in enumerate(parts, 1):
121 pnum = self.__get_part_num(num, parts)
122 if not pnum or not part.mountpoint \
123 or part.mountpoint in ("/", "/boot"):
124 continue
125
126 # mmc device partitions are named mmcblk0p1, mmcblk0p2..
127 prefix = 'p' if part.disk.startswith('mmcblk') else ''
128 device_name = "/dev/%s%s%d" % (part.disk, prefix, pnum)
129
130 opts = part.fsopts if part.fsopts else "defaults"
131 line = "\t".join([device_name, part.mountpoint, part.fstype,
132 opts, "0", "0"]) + "\n"
133
134 fstab_lines.append(line)
135 updated = True
136
137 return updated
138
139 def set_bootimg_dir(self, bootimg_dir):
140 """
141 Accessor for bootimg_dir, the actual location used for the source
142 of the bootimg. Should be set by source plugins (only if they
143 change the default bootimg source) so the correct info gets
144 displayed for print_outimage_info().
145 """
146 self.bootimg_dir = bootimg_dir
147
148 def _get_parts(self):
149 if not self.ks:
150 raise CreatorError("Failed to get partition info, "
151 "please check your kickstart setting.")
152
153 # Set a default partition if no partition is given out
154 if not self.ks.handler.partition.partitions:
155 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
156 args = partstr.split()
157 part = self.ks.handler.partition.parse(args[1:])
158 if part not in self.ks.handler.partition.partitions:
159 self.ks.handler.partition.partitions.append(part)
160
161 # partitions list from kickstart file
162 return kickstart.get_partitions(self.ks)
163
164 def get_disk_names(self):
165 """ Returns a list of physical target disk names (e.g., 'sdb') which
166 will be created. """
167
168 if self._disk_names:
169 return self._disk_names
170
171 #get partition info from ks handler
172 parts = self._get_parts()
173
174 for i in range(len(parts)):
175 if parts[i].disk:
176 disk_name = parts[i].disk
177 else:
178 raise CreatorError("Failed to create disks, no --ondisk "
179 "specified in partition line of ks file")
180
181 if parts[i].mountpoint and not parts[i].fstype:
182 raise CreatorError("Failed to create disks, no --fstype "
183 "specified for partition with mountpoint "
184 "'%s' in the ks file")
185
186 self._disk_names.append(disk_name)
187
188 return self._disk_names
189
190 def _full_name(self, name, extention):
191 """ Construct full file name for a file we generate. """
192 return "%s-%s.%s" % (self.name, name, extention)
193
194 def _full_path(self, path, name, extention):
195 """ Construct full file path to a file we generate. """
196 return os.path.join(path, self._full_name(name, extention))
197
198 def get_default_source_plugin(self):
199 """
200 The default source plugin i.e. the plugin that's consulted for
201 overall image generation tasks outside of any particular
202 partition. For convenience, we just hang it off the
203 bootloader handler since it's the one non-partition object in
204 any setup. By default the default plugin is set to the same
205 plugin as the /boot partition; since we hang it off the
206 bootloader object, the default can be explicitly set using the
207 --source bootloader param.
208 """
209 return self.ks.handler.bootloader.source
210
211 #
212 # Actual implemention
213 #
214 def _create(self):
215 """
216 For 'wic', we already have our build artifacts - we just create
217 filesystems from the artifacts directly and combine them into
218 a partitioned image.
219 """
220 parts = self._get_parts()
221
222 self.__image = Image(self.native_sysroot)
223
224 for part in parts:
225 # as a convenience, set source to the boot partition source
226 # instead of forcing it to be set via bootloader --source
227 if not self.ks.handler.bootloader.source and part.mountpoint == "/boot":
228 self.ks.handler.bootloader.source = part.source
229
230 fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
231
232 for part in parts:
233 # get rootfs size from bitbake variable if it's not set in .ks file
234 if not part.size:
235 # and if rootfs name is specified for the partition
236 image_name = part.get_rootfs()
237 if image_name:
238 # Bitbake variable ROOTFS_SIZE is calculated in
239 # Image._get_rootfs_size method from meta/lib/oe/image.py
240 # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
241 # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
242 rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name)
243 if rsize_bb:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500244 part.size = int(round(float(rsize_bb)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500245 # need to create the filesystems in order to get their
246 # sizes before we can add them and do the layout.
247 # Image.create() actually calls __format_disks() to create
248 # the disk images and carve out the partitions, then
249 # self.assemble() calls Image.assemble() which calls
250 # __write_partitition() for each partition to dd the fs
251 # into the partitions.
252 part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
253 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
254
255
256 self.__image.add_partition(int(part.size),
257 part.disk,
258 part.mountpoint,
259 part.source_file,
260 part.fstype,
261 part.label,
262 fsopts=part.fsopts,
263 boot=part.active,
264 align=part.align,
265 no_table=part.no_table,
266 part_type=part.part_type,
267 uuid=part.uuid)
268
269 if fstab_path:
270 shutil.move(fstab_path + ".orig", fstab_path)
271
272 self.__image.layout_partitions(self.ptable_format)
273
274 self.__imgdir = self.workdir
275 for disk_name, disk in self.__image.disks.items():
276 full_path = self._full_path(self.__imgdir, disk_name, "direct")
277 msger.debug("Adding disk %s as %s with size %s bytes" \
278 % (disk_name, full_path, disk['min_size']))
279 disk_obj = fs_related.DiskImage(full_path, disk['min_size'])
280 self.__disks[disk_name] = disk_obj
281 self.__image.add_disk(disk_name, disk_obj)
282
283 self.__image.create()
284
285 def assemble(self):
286 """
287 Assemble partitions into disk image(s)
288 """
289 for disk_name, disk in self.__image.disks.items():
290 full_path = self._full_path(self.__imgdir, disk_name, "direct")
291 msger.debug("Assembling disk %s as %s with size %s bytes" \
292 % (disk_name, full_path, disk['min_size']))
293 self.__image.assemble(full_path)
294
295 def finalize(self):
296 """
297 Finalize the disk image.
298
299 For example, prepare the image to be bootable by e.g.
300 creating and installing a bootloader configuration.
301
302 """
303 source_plugin = self.get_default_source_plugin()
304 if source_plugin:
305 self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods)
306 for disk_name, disk in self.__image.disks.items():
307 self._source_methods["do_install_disk"](disk, disk_name, self,
308 self.workdir,
309 self.oe_builddir,
310 self.bootimg_dir,
311 self.kernel_dir,
312 self.native_sysroot)
313 # Compress the image
314 if self.compressor:
315 for disk_name, disk in self.__image.disks.items():
316 full_path = self._full_path(self.__imgdir, disk_name, "direct")
317 msger.debug("Compressing disk %s with %s" % \
318 (disk_name, self.compressor))
319 exec_cmd("%s %s" % (self.compressor, full_path))
320
321 def print_outimage_info(self):
322 """
323 Print the image(s) and artifacts used, for the user.
324 """
325 msg = "The new image(s) can be found here:\n"
326
327 parts = self._get_parts()
328
329 for disk_name in self.__image.disks:
330 extension = "direct" + {"gzip": ".gz",
331 "bzip2": ".bz2",
332 "xz": ".xz",
333 "": ""}.get(self.compressor)
334 full_path = self._full_path(self.__imgdir, disk_name, extension)
335 msg += ' %s\n\n' % full_path
336
337 msg += 'The following build artifacts were used to create the image(s):\n'
338 for part in parts:
339 if part.get_rootfs() is None:
340 continue
341 if part.mountpoint == '/':
342 suffix = ':'
343 else:
344 suffix = '["%s"]:' % (part.mountpoint or part.label)
345 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.get_rootfs())
346
347 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
348 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
349 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
350
351 msger.info(msg)
352
353 @property
354 def rootdev(self):
355 """
356 Get root device name to use as a 'root' parameter
357 in kernel command line.
358
359 Assume partition order same as in wks
360 """
361 parts = self._get_parts()
362 for num, part in enumerate(parts, 1):
363 if part.mountpoint == "/":
364 if part.uuid:
365 return "PARTUUID=%s" % part.uuid
366 else:
367 suffix = 'p' if part.disk.startswith('mmcblk') else ''
368 pnum = self.__get_part_num(num, parts)
369 return "/dev/%s%s%-d" % (part.disk, suffix, pnum)
370
371 def _cleanup(self):
372 if not self.__image is None:
373 try:
374 self.__image.cleanup()
375 except ImageError, err:
376 msger.warning("%s" % err)
377