1
# Copyright 2012 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Install a PXE pre-boot loader for TFTP download."""
6
from __future__ import (
18
from optparse import make_option
20
from shutil import copyfile
22
from celeryconfig import TFTPROOT
23
from django.core.management.base import BaseCommand
24
from provisioningserver.pxe.tftppath import (
25
compose_bootloader_path,
30
def make_destination(tftproot, arch, subarch):
31
"""Locate a loader's destination. Create containing directory if needed.
33
:param tftproot: The root directory served up by the TFTP server,
34
e.g. /var/lib/tftpboot/.
35
:param arch: Main architecture to locate the destination for.
36
:param subarch: Sub-architecture of the main architecture.
37
:return: Full path describing the filename that the installed loader
38
should end up having. For example, the loader for i386 (with
39
sub-architecture "generic") should install at
40
/maas/i386/generic/pxelinux.0.
42
path = locate_tftp_path(
43
compose_bootloader_path(arch, subarch),
45
directory = os.path.dirname(path)
46
if not os.path.isdir(directory):
47
os.makedirs(directory)
51
def are_identical_files(old, new):
52
"""Are `old` and `new` identical?
54
If `old` does not exist, the two are considered different (`new` is
57
if os.path.isfile(old):
58
return filecmp.cmp(old, new, shallow=False)
63
def install_bootloader(loader, destination):
64
"""Install bootloader file at path `loader` as `destination`.
66
Installation will be atomic. If an identical loader is already
67
installed, it will be left untouched.
69
However it is still conceivable, depending on the TFTP implementation,
70
that a download that is already in progress may suddenly start receiving
71
data from the new file instead of the one it originally started
74
:param loader: Name of loader to install.
75
:param destination: Loader's intended filename, including full path,
76
where it will become available over TFTP.
78
if are_identical_files(destination, loader):
81
# Copy new loader next to the old one, to ensure that it is on the
82
# same filesystem. Once it is, we can replace the old one with an
83
# atomic rename operation.
84
temp_file = '%s.new' % destination
85
if os.path.exists(temp_file):
87
copyfile(loader, temp_file)
88
os.rename(temp_file, destination)
91
class Command(BaseCommand):
92
"""Install a PXE pre-boot loader into the TFTP directory structure.
94
This won't overwrite an existing loader if its contents are unchanged.
95
However the new loader you give it will be deleted regardless.
98
option_list = BaseCommand.option_list + (
100
'--arch', dest='arch', default=None,
101
help="Main system architecture that the bootloader is for."),
103
'--subarch', dest='subarch', default='generic',
104
help="Sub-architecture of the main architecture."),
106
'--loader', dest='loader', default=None,
107
help="PXE pre-boot loader to install."),
109
'--tftproot', dest='tftproot', default=TFTPROOT,
110
help="Store to this TFTP directory tree instead of the default."),
113
def handle(self, arch=None, subarch=None, loader=None, tftproot=None,
118
install_bootloader(loader, make_destination(tftproot, arch, subarch))
120
if os.path.exists(loader):