1
# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
6
from __future__ import (
19
from itertools import repeat
22
from textwrap import dedent
25
from provisioningserver.boot import (
27
BootMethodInstallError,
31
from provisioningserver.boot.install_bootloader import (
35
from provisioningserver.utils import (
41
ARCHIVE_URL = "http://archive.ubuntu.com/ubuntu/dists/"
42
ARCHIVE_PATH = "/main/uefi/grub2-amd64/current/grubnetx64.efi.signed"
44
CONFIG_FILE = dedent("""
45
# MAAS GRUB2 pre-loader configuration file
47
# Load based on MAC address first.
48
configfile (pxe)/grub/grub.cfg-${net_default_mac}
50
# Failed to load based on MAC address.
51
# Load amd64 by default, UEFI only supported by 64-bit
52
configfile (pxe)/grub/grub.cfg-default-amd64
55
# GRUB EFINET represents a MAC address in IEEE 802 colon-seperated
56
# format. Required for UEFI as GRUB2 only presents the MAC address
57
# in colon-seperated format.
58
re_mac_address_octet = r'[0-9a-f]{2}'
59
re_mac_address = re.compile(
60
':'.join(repeat(re_mac_address_octet, 6)))
62
# Match the grub/grub.cfg-* request for UEFI (aka. GRUB2)
64
# Optional leading slash(es).
66
grub/grub[.]cfg # UEFI (aka. GRUB2) expects this.
69
(?P<mac>{re_mac_address.pattern}) # Capture UEFI MAC.
72
(?: # perhaps with specified arch, with a separator of '-'
73
[-](?P<arch>\w+) # arch
74
(?:-(?P<subarch>\w+))? # optional subarch
80
re_config_file = re_config_file.format(
81
re_mac_address=re_mac_address)
82
re_config_file = re.compile(re_config_file, re.VERBOSE)
85
def archive_grubnet_urls():
86
"""Paths to try to download grubnetx64.efi.signed."""
87
release = utils.get_distro_release()
88
# grubnetx64 will not work below version trusty, as efinet is broken
89
# when loading kernel, force trusty. Note: This is only the grub version
90
# this should not block any of the previous release from running.
91
if release in ['lucid', 'precise', 'quantal', 'saucy']:
93
for dist in ['%s-updates' % release, release]:
95
ARCHIVE_URL.rstrip("/"),
97
ARCHIVE_PATH.rstrip("/"))
100
def download_grubnet(destination):
101
"""Downloads grubnetx64.efi.signed from the archive."""
102
for url in archive_grubnet_urls():
104
response = urllib2.urlopen(url)
105
# Okay, if it fails as the updates area might not hold
107
except urllib2.URLError:
110
with open(destination, 'wb') as stream:
111
stream.write(response.read())
116
class UEFIBootMethod(BootMethod):
119
template_subdir = "uefi"
120
bootloader_path = "bootx64.efi"
121
arch_octet = "00:07" # AMD64 EFI
123
def match_config_path(self, path):
124
"""Checks path for the configuration file that needs to be
127
:param path: requested path
128
:returns: dict of match params from path, None if no match
130
match = re_config_file.match(path)
133
params = get_parameters(match)
135
# MAC address is in the wrong format, fix it
136
mac = params.get("mac")
138
params["mac"] = mac.replace(':', '-')
142
def render_config(self, kernel_params, **extra):
143
"""Render a configuration file as a unicode string.
145
:param kernel_params: An instance of `KernelParameters`.
146
:param extra: Allow for other arguments. This is a safety valve;
147
parameters generated in another component (for example, see
148
`TFTPBackend.get_config_reader`) won't cause this to break.
150
template = self.get_template(
151
kernel_params.purpose, kernel_params.arch,
152
kernel_params.subarch)
153
namespace = self.compose_template_namespace(kernel_params)
154
return template.substitute(namespace)
156
def install_bootloader(self, destination):
157
"""Installs the required files for UEFI booting into the
160
with tempdir() as tmp:
161
# Download the shim-signed package
162
data, filename = utils.get_updates_package(
163
'shim-signed', 'http://archive.ubuntu.com/ubuntu',
166
raise BootMethodInstallError(
167
'Failed to download shim-signed package from '
169
shim_output = os.path.join(tmp, filename)
170
with open(shim_output, 'wb') as stream:
173
# Extract the package with dpkg, and install the shim
174
call_and_check(["dpkg", "-x", shim_output, tmp])
176
os.path.join(tmp, 'usr', 'lib', 'shim', 'shim.efi.signed'),
177
os.path.join(destination, self.bootloader_path))
179
# Download grubnetx64 from the archive and install
180
grub_tmp = os.path.join(tmp, 'grubnetx64.efi.signed')
181
if download_grubnet(grub_tmp) is False:
182
raise BootMethodInstallError(
183
'Failed to download grubnetx64.efi.signed '
185
grub_dst = os.path.join(destination, 'grubx64.efi')
186
install_bootloader(grub_tmp, grub_dst)
188
config_path = os.path.join(destination, 'grub')
189
config_dst = os.path.join(config_path, 'grub.cfg')
190
make_destination(config_path)
191
with open(config_dst, 'wb') as stream:
192
stream.write(CONFIG_FILE.encode("utf-8"))