blob: a1b424965173146a1ae555a6fbd060ef67733f6f [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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050030from wic import msger
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031from 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 = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050067 self.ptable_format = self.ks.bootloader.ptable
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068
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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500154 if not self.ks.partitions:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
156 args = partstr.split()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500157 part = self.ks.parse(args[1:])
158 if part not in self.ks.partitions:
159 self.ks.partitions.append(part)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160
161 # partitions list from kickstart file
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500162 return self.ks.partitions
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500163
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 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500209 return self.ks.bootloader.source
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210
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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500227 if not self.ks.bootloader.source and part.mountpoint == "/boot":
228 self.ks.bootloader.source = part.source
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500229
230 fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
231
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500232 shutil.rmtree(self.workdir)
233 os.mkdir(self.workdir)
234
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500235 for part in parts:
236 # get rootfs size from bitbake variable if it's not set in .ks file
237 if not part.size:
238 # and if rootfs name is specified for the partition
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500239 image_name = part.rootfs_dir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500240 if image_name:
241 # Bitbake variable ROOTFS_SIZE is calculated in
242 # Image._get_rootfs_size method from meta/lib/oe/image.py
243 # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
244 # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
245 rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name)
246 if rsize_bb:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500247 part.size = int(round(float(rsize_bb)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248 # need to create the filesystems in order to get their
249 # sizes before we can add them and do the layout.
250 # Image.create() actually calls __format_disks() to create
251 # the disk images and carve out the partitions, then
252 # self.assemble() calls Image.assemble() which calls
253 # __write_partitition() for each partition to dd the fs
254 # into the partitions.
255 part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
256 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
257
258
259 self.__image.add_partition(int(part.size),
260 part.disk,
261 part.mountpoint,
262 part.source_file,
263 part.fstype,
264 part.label,
265 fsopts=part.fsopts,
266 boot=part.active,
267 align=part.align,
268 no_table=part.no_table,
269 part_type=part.part_type,
270 uuid=part.uuid)
271
272 if fstab_path:
273 shutil.move(fstab_path + ".orig", fstab_path)
274
275 self.__image.layout_partitions(self.ptable_format)
276
277 self.__imgdir = self.workdir
278 for disk_name, disk in self.__image.disks.items():
279 full_path = self._full_path(self.__imgdir, disk_name, "direct")
280 msger.debug("Adding disk %s as %s with size %s bytes" \
281 % (disk_name, full_path, disk['min_size']))
282 disk_obj = fs_related.DiskImage(full_path, disk['min_size'])
283 self.__disks[disk_name] = disk_obj
284 self.__image.add_disk(disk_name, disk_obj)
285
286 self.__image.create()
287
288 def assemble(self):
289 """
290 Assemble partitions into disk image(s)
291 """
292 for disk_name, disk in self.__image.disks.items():
293 full_path = self._full_path(self.__imgdir, disk_name, "direct")
294 msger.debug("Assembling disk %s as %s with size %s bytes" \
295 % (disk_name, full_path, disk['min_size']))
296 self.__image.assemble(full_path)
297
298 def finalize(self):
299 """
300 Finalize the disk image.
301
302 For example, prepare the image to be bootable by e.g.
303 creating and installing a bootloader configuration.
304
305 """
306 source_plugin = self.get_default_source_plugin()
307 if source_plugin:
308 self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods)
309 for disk_name, disk in self.__image.disks.items():
310 self._source_methods["do_install_disk"](disk, disk_name, self,
311 self.workdir,
312 self.oe_builddir,
313 self.bootimg_dir,
314 self.kernel_dir,
315 self.native_sysroot)
316 # Compress the image
317 if self.compressor:
318 for disk_name, disk in self.__image.disks.items():
319 full_path = self._full_path(self.__imgdir, disk_name, "direct")
320 msger.debug("Compressing disk %s with %s" % \
321 (disk_name, self.compressor))
322 exec_cmd("%s %s" % (self.compressor, full_path))
323
324 def print_outimage_info(self):
325 """
326 Print the image(s) and artifacts used, for the user.
327 """
328 msg = "The new image(s) can be found here:\n"
329
330 parts = self._get_parts()
331
332 for disk_name in self.__image.disks:
333 extension = "direct" + {"gzip": ".gz",
334 "bzip2": ".bz2",
335 "xz": ".xz",
336 "": ""}.get(self.compressor)
337 full_path = self._full_path(self.__imgdir, disk_name, extension)
338 msg += ' %s\n\n' % full_path
339
340 msg += 'The following build artifacts were used to create the image(s):\n'
341 for part in parts:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500342 if part.rootfs_dir is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500343 continue
344 if part.mountpoint == '/':
345 suffix = ':'
346 else:
347 suffix = '["%s"]:' % (part.mountpoint or part.label)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500348 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.rootfs_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349
350 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
351 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
352 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
353
354 msger.info(msg)
355
356 @property
357 def rootdev(self):
358 """
359 Get root device name to use as a 'root' parameter
360 in kernel command line.
361
362 Assume partition order same as in wks
363 """
364 parts = self._get_parts()
365 for num, part in enumerate(parts, 1):
366 if part.mountpoint == "/":
367 if part.uuid:
368 return "PARTUUID=%s" % part.uuid
369 else:
370 suffix = 'p' if part.disk.startswith('mmcblk') else ''
371 pnum = self.__get_part_num(num, parts)
372 return "/dev/%s%s%-d" % (part.disk, suffix, pnum)
373
374 def _cleanup(self):
375 if not self.__image is None:
376 try:
377 self.__image.cleanup()
378 except ImageError, err:
379 msger.warning("%s" % err)
380