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) 2014, 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 'bootimg-efi' source plugin class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import logging
28import os
29import shutil
30
31from wic import WicError
32from wic.engine import get_custom_config
33from wic.pluginbase import SourcePlugin
34from wic.misc import (exec_cmd, exec_native_cmd,
35                      get_bitbake_var, BOOTDD_EXTRA_SPACE)
36
37logger = logging.getLogger('wic')
38
39class BootimgEFIPlugin(SourcePlugin):
40    """
41    Create EFI boot partition.
42    This plugin supports GRUB 2 and systemd-boot bootloaders.
43    """
44
45    name = 'bootimg-efi'
46
47    @classmethod
48    def do_configure_grubefi(cls, creator, cr_workdir):
49        """
50        Create loader-specific (grub-efi) config
51        """
52        configfile = creator.ks.bootloader.configfile
53        custom_cfg = None
54        if configfile:
55            custom_cfg = get_custom_config(configfile)
56            if custom_cfg:
57                # Use a custom configuration for grub
58                grubefi_conf = custom_cfg
59                logger.debug("Using custom configuration file "
60                             "%s for grub.cfg", configfile)
61            else:
62                raise WicError("configfile is specified but failed to "
63                               "get it from %s." % configfile)
64
65        if not custom_cfg:
66            # Create grub configuration using parameters from wks file
67            bootloader = creator.ks.bootloader
68
69            grubefi_conf = ""
70            grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
71            grubefi_conf += "default=boot\n"
72            grubefi_conf += "timeout=%s\n" % bootloader.timeout
73            grubefi_conf += "menuentry 'boot'{\n"
74
75            kernel = "/bzImage"
76
77            grubefi_conf += "linux %s root=%s rootwait %s\n" \
78                % (kernel, creator.rootdev, bootloader.append)
79            grubefi_conf += "}\n"
80
81        logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg",
82                     cr_workdir)
83        cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
84        cfg.write(grubefi_conf)
85        cfg.close()
86
87    @classmethod
88    def do_configure_systemdboot(cls, hdddir, creator, cr_workdir, source_params):
89        """
90        Create loader-specific systemd-boot/gummiboot config
91        """
92        install_cmd = "install -d %s/loader" % hdddir
93        exec_cmd(install_cmd)
94
95        install_cmd = "install -d %s/loader/entries" % hdddir
96        exec_cmd(install_cmd)
97
98        bootloader = creator.ks.bootloader
99
100        loader_conf = ""
101        loader_conf += "default boot\n"
102        loader_conf += "timeout %d\n" % bootloader.timeout
103
104        initrd = source_params.get('initrd')
105
106        if initrd:
107            # obviously we need to have a common common deploy var
108            bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
109            if not bootimg_dir:
110                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
111
112            cp_cmd = "cp %s/%s %s" % (bootimg_dir, initrd, hdddir)
113            exec_cmd(cp_cmd, True)
114        else:
115            logger.debug("Ignoring missing initrd")
116
117        logger.debug("Writing systemd-boot config "
118                     "%s/hdd/boot/loader/loader.conf", cr_workdir)
119        cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
120        cfg.write(loader_conf)
121        cfg.close()
122
123        configfile = creator.ks.bootloader.configfile
124        custom_cfg = None
125        if configfile:
126            custom_cfg = get_custom_config(configfile)
127            if custom_cfg:
128                # Use a custom configuration for systemd-boot
129                boot_conf = custom_cfg
130                logger.debug("Using custom configuration file "
131                             "%s for systemd-boots's boot.conf", configfile)
132            else:
133                raise WicError("configfile is specified but failed to "
134                               "get it from %s.", configfile)
135
136        if not custom_cfg:
137            # Create systemd-boot configuration using parameters from wks file
138            kernel = "/bzImage"
139
140            boot_conf = ""
141            boot_conf += "title boot\n"
142            boot_conf += "linux %s\n" % kernel
143            boot_conf += "options LABEL=Boot root=%s %s\n" % \
144                             (creator.rootdev, bootloader.append)
145
146            if initrd:
147                boot_conf += "initrd /%s\n" % initrd
148
149        logger.debug("Writing systemd-boot config "
150                     "%s/hdd/boot/loader/entries/boot.conf", cr_workdir)
151        cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
152        cfg.write(boot_conf)
153        cfg.close()
154
155
156    @classmethod
157    def do_configure_partition(cls, part, source_params, creator, cr_workdir,
158                               oe_builddir, bootimg_dir, kernel_dir,
159                               native_sysroot):
160        """
161        Called before do_prepare_partition(), creates loader-specific config
162        """
163        hdddir = "%s/hdd/boot" % cr_workdir
164
165        install_cmd = "install -d %s/EFI/BOOT" % hdddir
166        exec_cmd(install_cmd)
167
168        try:
169            if source_params['loader'] == 'grub-efi':
170                cls.do_configure_grubefi(creator, cr_workdir)
171            elif source_params['loader'] == 'systemd-boot':
172                cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params)
173            else:
174                raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader'])
175        except KeyError:
176            raise WicError("bootimg-efi requires a loader, none specified")
177
178
179    @classmethod
180    def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
181                             oe_builddir, bootimg_dir, kernel_dir,
182                             rootfs_dir, native_sysroot):
183        """
184        Called to do the actual content population for a partition i.e. it
185        'prepares' the partition to be incorporated into the image.
186        In this case, prepare content for an EFI (grub) boot partition.
187        """
188        if not kernel_dir:
189            kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
190            if not kernel_dir:
191                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
192
193        staging_kernel_dir = kernel_dir
194
195        hdddir = "%s/hdd/boot" % cr_workdir
196
197        install_cmd = "install -m 0644 %s/bzImage %s/bzImage" % \
198            (staging_kernel_dir, hdddir)
199        exec_cmd(install_cmd)
200
201
202        try:
203            if source_params['loader'] == 'grub-efi':
204                shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
205                                "%s/grub.cfg" % cr_workdir)
206                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]:
207                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:])
208                    exec_cmd(cp_cmd, True)
209                shutil.move("%s/grub.cfg" % cr_workdir,
210                            "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
211            elif source_params['loader'] == 'systemd-boot':
212                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]:
213                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:])
214                    exec_cmd(cp_cmd, True)
215            else:
216                raise WicError("unrecognized bootimg-efi loader: %s" %
217                               source_params['loader'])
218        except KeyError:
219            raise WicError("bootimg-efi requires a loader, none specified")
220
221        startup = os.path.join(kernel_dir, "startup.nsh")
222        if os.path.exists(startup):
223            cp_cmd = "cp %s %s/" % (startup, hdddir)
224            exec_cmd(cp_cmd, True)
225
226        du_cmd = "du -bks %s" % hdddir
227        out = exec_cmd(du_cmd)
228        blocks = int(out.split()[0])
229
230        extra_blocks = part.get_extra_block_count(blocks)
231
232        if extra_blocks < BOOTDD_EXTRA_SPACE:
233            extra_blocks = BOOTDD_EXTRA_SPACE
234
235        blocks += extra_blocks
236
237        logger.debug("Added %d extra blocks to %s to get to %d total blocks",
238                     extra_blocks, part.mountpoint, blocks)
239
240        # dosfs image, created by mkdosfs
241        bootimg = "%s/boot.img" % cr_workdir
242
243        dosfs_cmd = "mkdosfs -n efi -i %s -C %s %d" % \
244                    (part.fsuuid, bootimg, blocks)
245        exec_native_cmd(dosfs_cmd, native_sysroot)
246
247        mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
248        exec_native_cmd(mcopy_cmd, native_sysroot)
249
250        chmod_cmd = "chmod 644 %s" % bootimg
251        exec_cmd(chmod_cmd)
252
253        du_cmd = "du -Lbks %s" % bootimg
254        out = exec_cmd(du_cmd)
255        bootimg_size = out.split()[0]
256
257        part.size = int(bootimg_size)
258        part.source_file = bootimg
259