1 |
wellsi |
1.1 |
# |
2 |
|
|
# bootloaderInfo.py - bootloader config object used in creation of new |
3 |
|
|
# bootloader configs. Originally from anaconda |
4 |
|
|
# |
5 |
|
|
# Jeremy Katz <katzj@redhat.com> |
6 |
|
|
# Erik Troan <ewt@redhat.com> |
7 |
|
|
# Peter Jones <pjones@redhat.com> |
8 |
|
|
# |
9 |
|
|
# Copyright 2005-2008 Red Hat, Inc. |
10 |
|
|
# |
11 |
|
|
# This software may be freely redistributed under the terms of the GNU |
12 |
|
|
# library public license. |
13 |
|
|
# |
14 |
|
|
# You should have received a copy of the GNU Library Public License |
15 |
|
|
# along with this program; if not, write to the Free Software |
16 |
|
|
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
17 |
|
|
# |
18 |
|
|
|
19 |
|
|
import os, sys |
20 |
|
|
import collections |
21 |
|
|
import crypt |
22 |
|
|
import random |
23 |
|
|
import shutil |
24 |
|
|
import string |
25 |
|
|
import struct |
26 |
|
|
from copy import copy |
27 |
|
|
|
28 |
|
|
import gettext |
29 |
|
|
_ = lambda x: gettext.ldgettext("anaconda", x) |
30 |
|
|
N_ = lambda x: x |
31 |
|
|
|
32 |
|
|
from lilo import LiloConfigFile |
33 |
|
|
|
34 |
|
|
from flags import flags |
35 |
|
|
import iutil |
36 |
|
|
import isys |
37 |
|
|
from product import * |
38 |
|
|
|
39 |
|
|
import booty |
40 |
|
|
import checkbootloader |
41 |
|
|
from util import getDiskPart |
42 |
|
|
|
43 |
|
|
if not iutil.isS390(): |
44 |
|
|
import block |
45 |
|
|
|
46 |
|
|
dosFilesystems = ('FAT', 'fat16', 'fat32', 'ntfs', 'hpfs') |
47 |
|
|
|
48 |
|
|
def doesDualBoot(): |
49 |
|
|
if iutil.isX86(): |
50 |
|
|
return 1 |
51 |
|
|
return 0 |
52 |
|
|
|
53 |
|
|
def checkForBootBlock(device): |
54 |
|
|
fd = os.open(device, os.O_RDONLY) |
55 |
|
|
buf = os.read(fd, 512) |
56 |
|
|
os.close(fd) |
57 |
|
|
if len(buf) >= 512 and \ |
58 |
|
|
struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,): |
59 |
|
|
return True |
60 |
|
|
return False |
61 |
|
|
|
62 |
|
|
# hack and a half |
63 |
|
|
# there's no guarantee that data is written to the disk and grub |
64 |
|
|
# reads both the filesystem and the disk. suck. |
65 |
|
|
def syncDataToDisk(dev, mntpt, instRoot = "/"): |
66 |
|
|
isys.sync() |
67 |
|
|
isys.sync() |
68 |
|
|
isys.sync() |
69 |
|
|
|
70 |
|
|
# and xfs is even more "special" (#117968) |
71 |
|
|
if isys.readFSType(dev) == "xfs": |
72 |
|
|
iutil.execWithRedirect("/usr/sbin/xfs_freeze", |
73 |
|
|
["-f", mntpt], |
74 |
|
|
stdout = "/dev/tty5", |
75 |
|
|
stderr = "/dev/tty5", |
76 |
|
|
root = instRoot) |
77 |
|
|
iutil.execWithRedirect("/usr/sbin/xfs_freeze", |
78 |
|
|
["-u", mntpt], |
79 |
|
|
stdout = "/dev/tty5", |
80 |
|
|
stderr = "/dev/tty5", |
81 |
|
|
root = instRoot) |
82 |
|
|
|
83 |
|
|
def rootIsDevice(dev): |
84 |
|
|
if dev.startswith("LABEL=") or dev.startswith("UUID="): |
85 |
|
|
return False |
86 |
|
|
return True |
87 |
|
|
|
88 |
|
|
class KernelArguments: |
89 |
|
|
|
90 |
|
|
def _merge_ip(self, args): |
91 |
|
|
""" |
92 |
|
|
Find ip= arguments targetting the same interface and merge them. |
93 |
|
|
""" |
94 |
|
|
# partition the input |
95 |
|
|
def partition_p(arg): |
96 |
|
|
# we are only interested in ip= parameters that use some kind of |
97 |
|
|
# automatic network setup: |
98 |
|
|
return arg.startswith("ip=") and arg.count(":") == 1 |
99 |
|
|
ip_params = filter(partition_p, args) |
100 |
|
|
rest = set(filter(lambda p: not partition_p(p), args)) |
101 |
|
|
# split at the colon: |
102 |
|
|
ip_params = map(lambda p: p.split(":"), ip_params) |
103 |
|
|
# create mapping from nics to their configurations |
104 |
|
|
config = collections.defaultdict(list) |
105 |
|
|
for (nic, cfg) in ip_params: |
106 |
|
|
config[nic].append(cfg) |
107 |
|
|
|
108 |
|
|
# output the new parameters: |
109 |
|
|
ip_params = set() |
110 |
|
|
for nic in config: |
111 |
|
|
ip_params.add("%s:%s" % (nic, ",".join(sorted(config[nic])))) |
112 |
|
|
rest.update(ip_params) |
113 |
|
|
|
114 |
|
|
return rest |
115 |
|
|
|
116 |
|
|
def _sort_args(self, args): |
117 |
|
|
ordering_dict = {"rhgb": 99, "quiet": 100} |
118 |
|
|
|
119 |
|
|
# sort the elements according to their values in ordering_dict. The |
120 |
|
|
# higher the number the closer to the final string the argument |
121 |
|
|
# gets. The default is 50. |
122 |
|
|
lst = sorted(args, key=lambda s: ordering_dict.get(s, 50)) |
123 |
|
|
return " ".join(lst) |
124 |
|
|
|
125 |
|
|
def getDracutStorageArgs(self, devices): |
126 |
|
|
args = set() |
127 |
|
|
types = {} |
128 |
|
|
for device in devices: |
129 |
|
|
for d in self.id.storage.devices: |
130 |
|
|
if d is not device and not device.dependsOn(d): |
131 |
|
|
continue |
132 |
|
|
|
133 |
|
|
s = d.dracutSetupArgs() |
134 |
|
|
for setup_arg in s: |
135 |
|
|
types[setup_arg.split("=")[0]] = True |
136 |
|
|
args.update(s) |
137 |
|
|
|
138 |
|
|
import storage |
139 |
|
|
if isinstance(d, storage.devices.NetworkStorageDevice): |
140 |
|
|
s = self.id.network.dracutSetupArgs(d) |
141 |
|
|
args.update(s) |
142 |
|
|
|
143 |
|
|
for i in [ [ "rd_LUKS_UUID", "rd_NO_LUKS" ], |
144 |
|
|
[ "rd_LVM_LV", "rd_NO_LVM" ], |
145 |
|
|
[ "rd_MD_UUID", "rd_NO_MD" ], |
146 |
|
|
[ "rd_DM_UUID", "rd_NO_DM" ] ]: |
147 |
|
|
if not types.has_key(i[0]): |
148 |
|
|
args.add(i[1]) |
149 |
|
|
|
150 |
|
|
# This is needed for bug #743784. The case: |
151 |
|
|
# We discover LUN on an iface which is part of multipath setup. |
152 |
|
|
# If the iface is disconnected after discovery anaconda doesn't |
153 |
|
|
# write dracut ifname argument for the disconnected iface path |
154 |
|
|
# (in Network.dracutSetupArgs). |
155 |
|
|
# Dracut needs the explicit ifname= because biosdevname |
156 |
|
|
# fails to rename the iface (because of BFS booting from it). |
157 |
|
|
import storage.fcoe |
158 |
|
|
for nic, dcb, auto_vlan in storage.fcoe.fcoe().nics: |
159 |
|
|
hwaddr = self.id.network.netdevices[nic].get("HWADDR") |
160 |
|
|
args.add("ifname=%s:%s" % (nic, hwaddr.lower())) |
161 |
|
|
|
162 |
|
|
return args |
163 |
|
|
|
164 |
|
|
def get(self): |
165 |
|
|
bootArgs = set() |
166 |
|
|
rootDev = self.id.storage.rootDevice |
167 |
|
|
neededDevs = [ rootDev ] + self.id.storage.swaps |
168 |
|
|
|
169 |
|
|
if flags.cmdline.get("fips") == "1": |
170 |
|
|
bootDev = self.id.storage.mountpoints.get("/boot", rootDev) |
171 |
|
|
if bootDev is not rootDev: |
172 |
|
|
bootArgs.add("boot=%s" % bootDev.fstabSpec) |
173 |
|
|
neededDevs = [ rootDev, bootDev ] |
174 |
|
|
|
175 |
|
|
if self.id.storage.fsset.swapDevices: |
176 |
|
|
neededDevs.append(self.id.storage.fsset.swapDevices[0]) |
177 |
|
|
|
178 |
|
|
all_args = set() |
179 |
|
|
all_args.update(bootArgs) |
180 |
|
|
all_args.update(self.getDracutStorageArgs(neededDevs)) |
181 |
|
|
all_args.update(self.id.instLanguage.dracutSetupArgs()) |
182 |
|
|
all_args.add(self.id.keyboard.dracutSetupString()) |
183 |
|
|
all_args.update(self.args) |
184 |
|
|
all_args.update(self.appendArgs) |
185 |
|
|
|
186 |
|
|
all_args = self._merge_ip(all_args) |
187 |
|
|
|
188 |
|
|
return self._sort_args(all_args) |
189 |
|
|
|
190 |
|
|
def set(self, args): |
191 |
|
|
self.args = args |
192 |
|
|
self.appendArgs = set() |
193 |
|
|
|
194 |
|
|
def getNoDracut(self): |
195 |
|
|
args = " ".join(self.args) + " " + " ".join(self.appendArgs) |
196 |
|
|
return self._sort_args(args.split()) |
197 |
|
|
|
198 |
|
|
def chandevget(self): |
199 |
|
|
return self.cargs |
200 |
|
|
|
201 |
|
|
def chandevset(self, args): |
202 |
|
|
self.cargs = args |
203 |
|
|
|
204 |
|
|
def append(self, arg): |
205 |
|
|
self.appendArgs.add(arg) |
206 |
|
|
|
207 |
|
|
def __init__(self, instData): |
208 |
|
|
newArgs = [] |
209 |
|
|
|
210 |
|
|
if iutil.isS390(): |
211 |
|
|
self.cargs = [] |
212 |
|
|
|
213 |
|
|
# look for kernel arguments we know should be preserved and add them |
214 |
|
|
ourargs = ["speakup_synth", "apic", "noapic", "apm", "ide", "noht", |
215 |
|
|
"acpi", "video", "pci", "nodmraid", "nompath", "nomodeset", |
216 |
|
|
"noiswmd", "fips", "rdloaddriver"] |
217 |
|
|
|
218 |
|
|
if iutil.isS390(): |
219 |
|
|
ourargs.append("cio_ignore") |
220 |
|
|
|
221 |
|
|
for arg in ourargs: |
222 |
|
|
if not flags.cmdline.has_key(arg): |
223 |
|
|
continue |
224 |
|
|
|
225 |
|
|
val = flags.cmdline.get(arg, "") |
226 |
|
|
if val: |
227 |
|
|
newArgs.append("%s=%s" % (arg, val)) |
228 |
|
|
else: |
229 |
|
|
newArgs.append(arg) |
230 |
|
|
|
231 |
|
|
self.args = set(newArgs) |
232 |
|
|
self.appendArgs = set() |
233 |
|
|
self.id = instData |
234 |
|
|
|
235 |
|
|
|
236 |
|
|
class BootImages: |
237 |
|
|
"""A collection to keep track of boot images available on the system. |
238 |
|
|
Examples would be: |
239 |
|
|
('linux', 'Red Hat Linux', 'ext2'), |
240 |
|
|
('Other', 'Other', 'fat32'), ... |
241 |
|
|
""" |
242 |
|
|
def __init__(self): |
243 |
|
|
self.default = None |
244 |
|
|
self.images = {} |
245 |
|
|
|
246 |
|
|
def getImages(self): |
247 |
|
|
"""returns dictionary of (label, longlabel, devtype) pairs |
248 |
|
|
indexed by device""" |
249 |
|
|
# return a copy so users can modify it w/o affecting us |
250 |
|
|
return copy(self.images) |
251 |
|
|
|
252 |
|
|
def setDefault(self, default): |
253 |
|
|
# default is a device |
254 |
|
|
self.default = default |
255 |
|
|
|
256 |
|
|
def getDefault(self): |
257 |
|
|
return self.default |
258 |
|
|
|
259 |
|
|
# Construct a dictionary mapping device names to (OS, product, type) |
260 |
|
|
# tuples. |
261 |
|
|
def setup(self, storage): |
262 |
|
|
devices = {} |
263 |
|
|
bootDevs = self.availableBootDevices(storage) |
264 |
|
|
|
265 |
|
|
for (dev, type) in bootDevs: |
266 |
|
|
devices[dev.name] = 1 |
267 |
|
|
|
268 |
|
|
# These partitions have disappeared |
269 |
|
|
for dev in self.images.keys(): |
270 |
|
|
if not devices.has_key(dev): |
271 |
|
|
del self.images[dev] |
272 |
|
|
|
273 |
|
|
# These have appeared |
274 |
|
|
for (dev, type) in bootDevs: |
275 |
|
|
if not self.images.has_key(dev.name): |
276 |
|
|
if type in dosFilesystems and doesDualBoot(): |
277 |
|
|
self.images[dev.name] = ("Other", "Other", type) |
278 |
|
|
elif type in ("hfs", "hfs+") and iutil.getPPCMachine() == "PMac": |
279 |
|
|
self.images[dev.name] = ("Other", "Other", type) |
280 |
|
|
else: |
281 |
|
|
self.images[dev.name] = (None, None, type) |
282 |
|
|
|
283 |
|
|
if not self.images.has_key(self.default): |
284 |
|
|
self.default = storage.rootDevice.name |
285 |
|
|
(label, longlabel, type) = self.images[self.default] |
286 |
|
|
if not label: |
287 |
|
|
self.images[self.default] = ("linux", productName, type) |
288 |
|
|
|
289 |
|
|
# Return a list of (storage.Device, string) tuples that are bootable |
290 |
|
|
# devices. The string is the type of the device, which is just a string |
291 |
|
|
# like "vfat" or "swap" or "lvm". |
292 |
|
|
def availableBootDevices(self, storage): |
293 |
|
|
import parted |
294 |
|
|
retval = [] |
295 |
|
|
foundDos = False |
296 |
|
|
foundAppleBootstrap = False |
297 |
|
|
|
298 |
|
|
for part in [p for p in storage.partitions if p.exists]: |
299 |
|
|
# Skip extended, metadata, freespace, etc. |
300 |
|
|
if part.partType not in (parted.PARTITION_NORMAL, parted.PARTITION_LOGICAL) or not part.format: |
301 |
|
|
continue |
302 |
|
|
|
303 |
|
|
type = part.format.type |
304 |
|
|
|
305 |
|
|
if type in dosFilesystems and not foundDos and doesDualBoot() and \ |
306 |
|
|
not part.getFlag(parted.PARTITION_DIAG): |
307 |
|
|
try: |
308 |
|
|
bootable = checkForBootBlock(part.path) |
309 |
|
|
retval.append((part, type)) |
310 |
|
|
foundDos = True |
311 |
|
|
except: |
312 |
|
|
pass |
313 |
|
|
elif type in ["ntfs", "hpfs"] and not foundDos and \ |
314 |
|
|
doesDualBoot() and not part.getFlag(parted.PARTITION_DIAG): |
315 |
|
|
retval.append((part, type)) |
316 |
|
|
# maybe questionable, but the first ntfs or fat is likely to |
317 |
|
|
# be the correct one to boot with XP using ntfs |
318 |
|
|
foundDos = True |
319 |
|
|
elif type == "appleboot" and iutil.getPPCMachine() == "PMac" and part.bootable: |
320 |
|
|
foundAppleBootstrap = True |
321 |
|
|
elif type in ["hfs", "hfs+"] and foundAppleBootstrap: |
322 |
|
|
# questionable for same reason as above, but on mac this time |
323 |
|
|
retval.append((part, type)) |
324 |
|
|
|
325 |
|
|
rootDevice = storage.rootDevice |
326 |
|
|
|
327 |
|
|
if not rootDevice or not rootDevice.format: |
328 |
|
|
raise ValueError, ("Trying to pick boot devices but do not have a " |
329 |
|
|
"sane root partition. Aborting install.") |
330 |
|
|
|
331 |
|
|
retval.append((rootDevice, rootDevice.format.type)) |
332 |
|
|
retval.sort() |
333 |
|
|
return retval |
334 |
|
|
|
335 |
|
|
class bootloaderInfo(object): |
336 |
|
|
def getConfigFileName(self): |
337 |
|
|
if not self._configname: |
338 |
|
|
raise NotImplementedError |
339 |
|
|
return self._configname |
340 |
|
|
configname = property(getConfigFileName, None, None, \ |
341 |
|
|
"bootloader config file name") |
342 |
|
|
|
343 |
|
|
def getConfigFileDir(self): |
344 |
|
|
if not self._configdir: |
345 |
|
|
raise NotImplementedError |
346 |
|
|
return self._configdir |
347 |
|
|
configdir = property(getConfigFileDir, None, None, \ |
348 |
|
|
"bootloader config file directory") |
349 |
|
|
|
350 |
|
|
def getConfigFilePath(self): |
351 |
|
|
return "%s/%s" % (self.configdir, self.configname) |
352 |
|
|
configfile = property(getConfigFilePath, None, None, \ |
353 |
|
|
"full path and name of the real config file") |
354 |
|
|
|
355 |
|
|
def setUseGrub(self, val): |
356 |
|
|
pass |
357 |
|
|
|
358 |
|
|
def useGrub(self): |
359 |
|
|
return self.useGrubVal |
360 |
|
|
|
361 |
|
|
def setPassword(self, val, isCrypted = 1): |
362 |
|
|
pass |
363 |
|
|
|
364 |
|
|
def getPassword(self): |
365 |
|
|
pass |
366 |
|
|
|
367 |
|
|
def getDevice(self): |
368 |
|
|
return self.device |
369 |
|
|
|
370 |
|
|
def setDevice(self, device): |
371 |
|
|
self.device = device |
372 |
|
|
|
373 |
|
|
(dev, part) = getDiskPart(device, self.storage) |
374 |
|
|
if part is None: |
375 |
|
|
self.defaultDevice = "mbr" |
376 |
|
|
else: |
377 |
|
|
self.defaultDevice = "partition" |
378 |
|
|
|
379 |
|
|
def makeInitrd(self, kernelTag, instRoot): |
380 |
|
|
initrd = "initrd%s.img" % kernelTag |
381 |
|
|
if os.access(instRoot + "/boot/" + initrd, os.R_OK): |
382 |
|
|
return initrd |
383 |
|
|
|
384 |
|
|
initrd = "initramfs%s.img" % kernelTag |
385 |
|
|
if os.access(instRoot + "/boot/" + initrd, os.R_OK): |
386 |
|
|
return initrd |
387 |
|
|
|
388 |
|
|
return None |
389 |
|
|
|
390 |
|
|
def getBootloaderConfig(self, instRoot, bl, kernelList, |
391 |
|
|
chainList, defaultDev): |
392 |
|
|
images = bl.images.getImages() |
393 |
|
|
|
394 |
|
|
confFile = instRoot + self.configfile |
395 |
|
|
|
396 |
|
|
# on upgrade read in the lilo config file |
397 |
|
|
lilo = LiloConfigFile () |
398 |
|
|
self.perms = 0600 |
399 |
|
|
if os.access (confFile, os.R_OK): |
400 |
|
|
self.perms = os.stat(confFile)[0] & 0777 |
401 |
|
|
lilo.read(confFile) |
402 |
|
|
os.rename(confFile, confFile + ".rpmsave") |
403 |
|
|
# if it's an absolute symlink, just get it out of our way |
404 |
|
|
elif (os.path.islink(confFile) and os.readlink(confFile)[0] == '/'): |
405 |
|
|
os.rename(confFile, confFile + ".rpmsave") |
406 |
|
|
|
407 |
|
|
# Remove any invalid entries that are in the file; we probably |
408 |
|
|
# just removed those kernels. |
409 |
|
|
for label in lilo.listImages(): |
410 |
|
|
(fsType, sl, path, other) = lilo.getImage(label) |
411 |
|
|
if fsType == "other": continue |
412 |
|
|
|
413 |
|
|
if not os.access(instRoot + sl.getPath(), os.R_OK): |
414 |
|
|
lilo.delImage(label) |
415 |
|
|
|
416 |
|
|
lilo.addEntry("prompt", replace = 0) |
417 |
|
|
lilo.addEntry("timeout", self.timeout or "20", replace = 0) |
418 |
|
|
|
419 |
|
|
rootDev = self.storage.rootDevice |
420 |
|
|
|
421 |
|
|
if rootDev.name == defaultDev.name: |
422 |
|
|
lilo.addEntry("default", kernelList[0][0]) |
423 |
|
|
else: |
424 |
|
|
lilo.addEntry("default", chainList[0][0]) |
425 |
|
|
|
426 |
|
|
for (label, longlabel, version) in kernelList: |
427 |
|
|
kernelTag = "-" + version |
428 |
|
|
kernelFile = self.kernelLocation + "vmlinuz" + kernelTag |
429 |
|
|
|
430 |
|
|
try: |
431 |
|
|
lilo.delImage(label) |
432 |
|
|
except IndexError, msg: |
433 |
|
|
pass |
434 |
|
|
|
435 |
|
|
sl = LiloConfigFile(imageType = "image", path = kernelFile) |
436 |
|
|
|
437 |
|
|
initrd = self.makeInitrd(kernelTag, instRoot) |
438 |
|
|
|
439 |
|
|
sl.addEntry("label", label) |
440 |
|
|
if initrd: |
441 |
|
|
sl.addEntry("initrd", "%s%s" %(self.kernelLocation, initrd)) |
442 |
|
|
|
443 |
|
|
sl.addEntry("read-only") |
444 |
|
|
|
445 |
|
|
append = "%s" %(self.args.get(),) |
446 |
|
|
realroot = rootDev.fstabSpec |
447 |
|
|
if rootIsDevice(realroot): |
448 |
|
|
sl.addEntry("root", rootDev.path) |
449 |
|
|
else: |
450 |
|
|
if len(append) > 0: |
451 |
|
|
append = "%s root=%s" %(append,realroot) |
452 |
|
|
else: |
453 |
|
|
append = "root=%s" %(realroot,) |
454 |
|
|
|
455 |
|
|
if len(append) > 0: |
456 |
|
|
sl.addEntry('append', '"%s"' % (append,)) |
457 |
|
|
|
458 |
|
|
lilo.addImage (sl) |
459 |
|
|
|
460 |
|
|
for (label, longlabel, device) in chainList: |
461 |
|
|
if ((not label) or (label == "")): |
462 |
|
|
continue |
463 |
|
|
try: |
464 |
|
|
(fsType, sl, path, other) = lilo.getImage(label) |
465 |
|
|
lilo.delImage(label) |
466 |
|
|
except IndexError: |
467 |
|
|
sl = LiloConfigFile(imageType = "other", |
468 |
|
|
path = "/dev/%s" %(device)) |
469 |
|
|
sl.addEntry("optional") |
470 |
|
|
|
471 |
|
|
sl.addEntry("label", label) |
472 |
|
|
lilo.addImage (sl) |
473 |
|
|
|
474 |
|
|
# Sanity check #1. There could be aliases in sections which conflict |
475 |
|
|
# with the new images we just created. If so, erase those aliases |
476 |
|
|
imageNames = {} |
477 |
|
|
for label in lilo.listImages(): |
478 |
|
|
imageNames[label] = 1 |
479 |
|
|
|
480 |
|
|
for label in lilo.listImages(): |
481 |
|
|
(fsType, sl, path, other) = lilo.getImage(label) |
482 |
|
|
if sl.testEntry('alias'): |
483 |
|
|
alias = sl.getEntry('alias') |
484 |
|
|
if imageNames.has_key(alias): |
485 |
|
|
sl.delEntry('alias') |
486 |
|
|
imageNames[alias] = 1 |
487 |
|
|
|
488 |
|
|
# Sanity check #2. If single-key is turned on, go through all of |
489 |
|
|
# the image names (including aliases) (we just built the list) and |
490 |
|
|
# see if single-key will still work. |
491 |
|
|
if lilo.testEntry('single-key'): |
492 |
|
|
singleKeys = {} |
493 |
|
|
turnOff = 0 |
494 |
|
|
for label in imageNames.keys(): |
495 |
|
|
l = label[0] |
496 |
|
|
if singleKeys.has_key(l): |
497 |
|
|
turnOff = 1 |
498 |
|
|
singleKeys[l] = 1 |
499 |
|
|
if turnOff: |
500 |
|
|
lilo.delEntry('single-key') |
501 |
|
|
|
502 |
|
|
return lilo |
503 |
|
|
|
504 |
|
|
def write(self, instRoot, bl, kernelList, chainList, defaultDev): |
505 |
|
|
rc = 0 |
506 |
|
|
|
507 |
|
|
if len(kernelList) >= 1: |
508 |
|
|
config = self.getBootloaderConfig(instRoot, bl, |
509 |
|
|
kernelList, chainList, |
510 |
|
|
defaultDev) |
511 |
|
|
rc = config.write(instRoot + self.configfile, perms = self.perms) |
512 |
|
|
else: |
513 |
|
|
raise booty.BootyNoKernelWarning |
514 |
|
|
|
515 |
|
|
return rc |
516 |
|
|
|
517 |
|
|
def getArgList(self): |
518 |
|
|
args = [] |
519 |
|
|
|
520 |
|
|
if self.defaultDevice is None: |
521 |
|
|
args.append("--location=none") |
522 |
|
|
return args |
523 |
|
|
|
524 |
|
|
args.append("--location=%s" % (self.defaultDevice,)) |
525 |
|
|
args.append("--driveorder=%s" % (",".join(self.drivelist))) |
526 |
|
|
|
527 |
|
|
if self.args.getNoDracut(): |
528 |
|
|
args.append("--append=\"%s\"" %(self.args.getNoDracut())) |
529 |
|
|
|
530 |
|
|
return args |
531 |
|
|
|
532 |
|
|
def writeKS(self, f): |
533 |
|
|
f.write("bootloader") |
534 |
|
|
for arg in self.getArgList(): |
535 |
|
|
f.write(" " + arg) |
536 |
|
|
f.write("\n") |
537 |
|
|
|
538 |
|
|
def updateDriveList(self, sortedList=[]): |
539 |
|
|
# bootloader is unusual in that we only want to look at disks that |
540 |
|
|
# have disklabels -- no partitioned md or unpartitioned disks |
541 |
|
|
disks = self.storage.disks |
542 |
|
|
partitioned = self.storage.partitioned |
543 |
|
|
self._drivelist = [d.name for d in disks if d in partitioned] |
544 |
|
|
self._drivelist.sort(self.storage.compareDisks) |
545 |
|
|
|
546 |
|
|
# If we're given a sort order, make sure the drives listed in it |
547 |
|
|
# are put at the head of the drivelist in that order. All other |
548 |
|
|
# drives follow behind in whatever order they're found. |
549 |
|
|
if sortedList != []: |
550 |
|
|
revSortedList = sortedList |
551 |
|
|
revSortedList.reverse() |
552 |
|
|
|
553 |
|
|
for i in revSortedList: |
554 |
|
|
try: |
555 |
|
|
ele = self._drivelist.pop(self._drivelist.index(i)) |
556 |
|
|
self._drivelist.insert(0, ele) |
557 |
|
|
except: |
558 |
|
|
pass |
559 |
|
|
|
560 |
|
|
def _getDriveList(self): |
561 |
|
|
if self._drivelist is not None: |
562 |
|
|
return self._drivelist |
563 |
|
|
self.updateDriveList() |
564 |
|
|
return self._drivelist |
565 |
|
|
def _setDriveList(self, val): |
566 |
|
|
self._drivelist = val |
567 |
|
|
drivelist = property(_getDriveList, _setDriveList) |
568 |
|
|
|
569 |
|
|
def _getTrustedBoot(self): |
570 |
|
|
return self._trusted_boot |
571 |
|
|
def _setTrustedBoot(self, val): |
572 |
|
|
self._trusted_boot = val |
573 |
|
|
trusted_boot = property(_getTrustedBoot, _setTrustedBoot) |
574 |
|
|
|
575 |
|
|
def __init__(self, instData): |
576 |
|
|
self.args = KernelArguments(instData) |
577 |
|
|
self.images = BootImages() |
578 |
|
|
self.device = None |
579 |
|
|
self.defaultDevice = None # XXX hack, used by kickstart |
580 |
|
|
self.useGrubVal = 0 # only used on x86 |
581 |
|
|
self._configdir = None |
582 |
|
|
self._configname = None |
583 |
|
|
self.kernelLocation = "/boot/" |
584 |
|
|
self.password = None |
585 |
|
|
self.pure = None |
586 |
|
|
self.above1024 = 0 |
587 |
|
|
self.timeout = 5 |
588 |
|
|
self.storage = instData.storage |
589 |
|
|
self.serial = 0 |
590 |
|
|
self.serialDevice = None |
591 |
|
|
self.serialOptions = None |
592 |
|
|
self._trusted_boot = False |
593 |
|
|
|
594 |
|
|
# this has somewhat strange semantics. if 0, act like a normal |
595 |
|
|
# "install" case. if 1, update lilo.conf (since grubby won't do that) |
596 |
|
|
# and then run lilo or grub only. |
597 |
|
|
# XXX THIS IS A HACK. implementation details are only there for x86 |
598 |
|
|
self.doUpgradeOnly = 0 |
599 |
|
|
self.kickstart = 0 |
600 |
|
|
|
601 |
|
|
self._drivelist = None |
602 |
|
|
|
603 |
|
|
if flags.serial != 0: |
604 |
|
|
self.serial = 1 |
605 |
|
|
self.timeout = 5 |
606 |
|
|
|
607 |
|
|
console = flags.cmdline.get("console", "") |
608 |
|
|
if console: |
609 |
|
|
# the options are everything after the comma |
610 |
|
|
comma = console.find(",") |
611 |
|
|
if comma != -1: |
612 |
|
|
self.serialDevice = console[:comma] |
613 |
|
|
self.serialOptions = console[comma + 1:] |
614 |
|
|
else: |
615 |
|
|
self.serialDevice = console |
616 |
|
|
self.serialOptions = "" |
617 |
|
|
else: |
618 |
|
|
self.serialDevice = "ttyS0" |
619 |
|
|
self.serialOptions = "" |
620 |
|
|
|
621 |
|
|
if self.serialOptions: |
622 |
|
|
self.args.append("console=%s,%s" %(self.serialDevice, |
623 |
|
|
self.serialOptions)) |
624 |
|
|
else: |
625 |
|
|
self.args.append("console=%s" % self.serialDevice) |
626 |
|
|
|
627 |
|
|
if flags.virtpconsole is not None: |
628 |
|
|
if flags.virtpconsole.startswith("/dev/"): |
629 |
|
|
con = flags.virtpconsole[5:] |
630 |
|
|
else: |
631 |
|
|
con = flags.virtpconsole |
632 |
|
|
self.args.append("console=%s" %(con,)) |
633 |
|
|
|
634 |
|
|
class efiBootloaderInfo(bootloaderInfo): |
635 |
|
|
def getBootloaderName(self): |
636 |
|
|
return self._bootloader |
637 |
|
|
bootloader = property(getBootloaderName, None, None, \ |
638 |
|
|
"name of the bootloader to install") |
639 |
|
|
|
640 |
|
|
# XXX wouldn't it be nice to have a real interface to use efibootmgr from? |
641 |
|
|
def removeOldEfiEntries(self, instRoot): |
642 |
|
|
p = os.pipe() |
643 |
|
|
rc = iutil.execWithRedirect('efibootmgr', [], |
644 |
|
|
root = instRoot, stdout = p[1]) |
645 |
|
|
os.close(p[1]) |
646 |
|
|
if rc: |
647 |
|
|
return rc |
648 |
|
|
|
649 |
|
|
c = os.read(p[0], 1) |
650 |
|
|
buf = c |
651 |
|
|
while (c): |
652 |
|
|
c = os.read(p[0], 1) |
653 |
|
|
buf = buf + c |
654 |
|
|
os.close(p[0]) |
655 |
|
|
lines = string.split(buf, '\n') |
656 |
|
|
for line in lines: |
657 |
|
|
fields = string.split(line) |
658 |
|
|
if len(fields) < 2: |
659 |
|
|
continue |
660 |
|
|
if string.join(fields[1:], " ") == productName: |
661 |
|
|
entry = fields[0][4:8] |
662 |
|
|
rc = iutil.execWithRedirect('efibootmgr', |
663 |
|
|
["-b", entry, "-B"], |
664 |
|
|
root = instRoot, |
665 |
|
|
stdout="/dev/tty5", stderr="/dev/tty5") |
666 |
|
|
if rc: |
667 |
|
|
return rc |
668 |
|
|
|
669 |
|
|
return 0 |
670 |
|
|
|
671 |
|
|
def addNewEfiEntry(self, instRoot): |
672 |
|
|
try: |
673 |
|
|
bootdev = self.storage.mountpoints["/boot/efi"].name |
674 |
|
|
except: |
675 |
|
|
bootdev = "sda1" |
676 |
|
|
|
677 |
|
|
link = "%s%s/%s" % (instRoot, "/etc/", self.configname) |
678 |
|
|
if not os.access(link, os.R_OK): |
679 |
|
|
os.symlink("../%s" % (self.configfile), link) |
680 |
|
|
|
681 |
|
|
(bootdisk, bootpart) = getDiskPart(bootdev, self.storage) |
682 |
|
|
if bootpart == None: |
683 |
|
|
log.error("No partition for bootdev '%s'" % (bootdev,)) |
684 |
|
|
return 1 |
685 |
|
|
# getDiskPart returns a 0 indexed partition number |
686 |
|
|
bootpart += 1 |
687 |
|
|
|
688 |
|
|
bootdev = self.storage.devicetree.getDeviceByName(bootdisk) |
689 |
|
|
if not bootdev: |
690 |
|
|
log.error("bootdev not found for '%s'" % (bootdisk,)) |
691 |
|
|
return 1 |
692 |
|
|
|
693 |
|
|
# if the bootdev is multipath, we need to call efibootmgr on all it's |
694 |
|
|
# member devices |
695 |
|
|
from storage.devices import MultipathDevice |
696 |
|
|
|
697 |
|
|
if isinstance(bootdev, MultipathDevice): |
698 |
|
|
bootdevlist = bootdev.parents |
699 |
|
|
else: |
700 |
|
|
bootdevlist = [bootdev] |
701 |
|
|
|
702 |
|
|
for d in bootdevlist: |
703 |
|
|
argv = [ "efibootmgr", "-c" , "-w", "-L", |
704 |
|
|
productName, "-d", "%s" % (d.path,), |
705 |
|
|
"-p", "%s" % (bootpart,), |
706 |
|
|
"-l", "\\EFI\\redhat\\" + self.bootloader ] |
707 |
|
|
rc = iutil.execWithRedirect(argv[0], argv[1:], root = instRoot, |
708 |
|
|
stdout = "/dev/tty5", |
709 |
|
|
stderr = "/dev/tty5") |
710 |
|
|
|
711 |
|
|
# return last rc, the API doesn't provide anything better than this |
712 |
|
|
return rc |
713 |
|
|
|
714 |
|
|
def getEfiProductPath(self, productName, force=False): |
715 |
|
|
""" Return the full EFI path of the installed product. |
716 |
|
|
eg. HD(4,2c8800,64000,902c1655-2677-4455-b2a5-29d0ce835610) |
717 |
|
|
|
718 |
|
|
pass force=True to skip the cache and rerun efibootmgr |
719 |
|
|
""" |
720 |
|
|
if not force and self._efiProductPath: |
721 |
|
|
return self._efiProductPath |
722 |
|
|
|
723 |
|
|
argv = [ "efibootmgr", "-v" ] |
724 |
|
|
buf = iutil.execWithCapture(argv[0], argv[1:], |
725 |
|
|
stderr="/dev/tty5") |
726 |
|
|
|
727 |
|
|
efiProductPath = None |
728 |
|
|
for line in buf.splitlines(): |
729 |
|
|
line = line.strip() |
730 |
|
|
if not line: |
731 |
|
|
continue |
732 |
|
|
if productName in line: |
733 |
|
|
efiProductPath = line[line.rfind(productName)+len(productName):].strip() |
734 |
|
|
break |
735 |
|
|
|
736 |
|
|
if efiProductPath: |
737 |
|
|
# Grab just the drive path |
738 |
|
|
import re |
739 |
|
|
m = re.match("(.*?\(.*?\)).*", efiProductPath) |
740 |
|
|
if m: |
741 |
|
|
efiProductPath = m.group(1) |
742 |
|
|
else: |
743 |
|
|
efiProductPath = None |
744 |
|
|
|
745 |
|
|
self._efiProductPath = efiProductPath |
746 |
|
|
return self._efiProductPath |
747 |
|
|
|
748 |
|
|
def installGrub(self, instRoot, bootDev, grubTarget, grubPath, cfPath): |
749 |
|
|
if not iutil.isEfi(): |
750 |
|
|
raise EnvironmentError |
751 |
|
|
rc = self.removeOldEfiEntries(instRoot) |
752 |
|
|
if rc: |
753 |
|
|
return rc |
754 |
|
|
return self.addNewEfiEntry(instRoot) |
755 |
|
|
|
756 |
|
|
def __init__(self, instData, initialize = True): |
757 |
|
|
if initialize: |
758 |
|
|
bootloaderInfo.__init__(self, instData) |
759 |
|
|
else: |
760 |
|
|
self.storage = instData.storage |
761 |
|
|
|
762 |
|
|
self._efiProductPath = None |
763 |
|
|
|
764 |
|
|
if iutil.isEfi(): |
765 |
|
|
self._configdir = "/boot/efi/EFI/redhat" |
766 |
|
|
self._configname = "grub.conf" |
767 |
|
|
self._bootloader = "grub.efi" |
768 |
|
|
self.useGrubVal = 1 |
769 |
|
|
self.kernelLocation = "" |