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