blob: 146a0d1535dcf8cb61ceb60f6835f87555b3458c [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:
244 # convert from Kb to Mb
245 part.size = int(round(float(rsize_bb) / 1024.))
246 # need to create the filesystems in order to get their
247 # sizes before we can add them and do the layout.
248 # Image.create() actually calls __format_disks() to create
249 # the disk images and carve out the partitions, then
250 # self.assemble() calls Image.assemble() which calls
251 # __write_partitition() for each partition to dd the fs
252 # into the partitions.
253 part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
254 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
255
256
257 self.__image.add_partition(int(part.size),
258 part.disk,
259 part.mountpoint,
260 part.source_file,
261 part.fstype,
262 part.label,
263 fsopts=part.fsopts,
264 boot=part.active,
265 align=part.align,
266 no_table=part.no_table,
267 part_type=part.part_type,
268 uuid=part.uuid)
269
270 if fstab_path:
271 shutil.move(fstab_path + ".orig", fstab_path)
272
273 self.__image.layout_partitions(self.ptable_format)
274
275 self.__imgdir = self.workdir
276 for disk_name, disk in self.__image.disks.items():
277 full_path = self._full_path(self.__imgdir, disk_name, "direct")
278 msger.debug("Adding disk %s as %s with size %s bytes" \
279 % (disk_name, full_path, disk['min_size']))
280 disk_obj = fs_related.DiskImage(full_path, disk['min_size'])
281 self.__disks[disk_name] = disk_obj
282 self.__image.add_disk(disk_name, disk_obj)
283
284 self.__image.create()
285
286 def assemble(self):
287 """
288 Assemble partitions into disk image(s)
289 """
290 for disk_name, disk in self.__image.disks.items():
291 full_path = self._full_path(self.__imgdir, disk_name, "direct")
292 msger.debug("Assembling disk %s as %s with size %s bytes" \
293 % (disk_name, full_path, disk['min_size']))
294 self.__image.assemble(full_path)
295
296 def finalize(self):
297 """
298 Finalize the disk image.
299
300 For example, prepare the image to be bootable by e.g.
301 creating and installing a bootloader configuration.
302
303 """
304 source_plugin = self.get_default_source_plugin()
305 if source_plugin:
306 self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods)
307 for disk_name, disk in self.__image.disks.items():
308 self._source_methods["do_install_disk"](disk, disk_name, self,
309 self.workdir,
310 self.oe_builddir,
311 self.bootimg_dir,
312 self.kernel_dir,
313 self.native_sysroot)
314 # Compress the image
315 if self.compressor:
316 for disk_name, disk in self.__image.disks.items():
317 full_path = self._full_path(self.__imgdir, disk_name, "direct")
318 msger.debug("Compressing disk %s with %s" % \
319 (disk_name, self.compressor))
320 exec_cmd("%s %s" % (self.compressor, full_path))
321
322 def print_outimage_info(self):
323 """
324 Print the image(s) and artifacts used, for the user.
325 """
326 msg = "The new image(s) can be found here:\n"
327
328 parts = self._get_parts()
329
330 for disk_name in self.__image.disks:
331 extension = "direct" + {"gzip": ".gz",
332 "bzip2": ".bz2",
333 "xz": ".xz",
334 "": ""}.get(self.compressor)
335 full_path = self._full_path(self.__imgdir, disk_name, extension)
336 msg += ' %s\n\n' % full_path
337
338 msg += 'The following build artifacts were used to create the image(s):\n'
339 for part in parts:
340 if part.get_rootfs() is None:
341 continue
342 if part.mountpoint == '/':
343 suffix = ':'
344 else:
345 suffix = '["%s"]:' % (part.mountpoint or part.label)
346 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.get_rootfs())
347
348 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
349 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
350 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
351
352 msger.info(msg)
353
354 @property
355 def rootdev(self):
356 """
357 Get root device name to use as a 'root' parameter
358 in kernel command line.
359
360 Assume partition order same as in wks
361 """
362 parts = self._get_parts()
363 for num, part in enumerate(parts, 1):
364 if part.mountpoint == "/":
365 if part.uuid:
366 return "PARTUUID=%s" % part.uuid
367 else:
368 suffix = 'p' if part.disk.startswith('mmcblk') else ''
369 pnum = self.__get_part_num(num, parts)
370 return "/dev/%s%s%-d" % (part.disk, suffix, pnum)
371
372 def _cleanup(self):
373 if not self.__image is None:
374 try:
375 self.__image.cleanup()
376 except ImageError, err:
377 msger.warning("%s" % err)
378