556 lines
22 KiB
Python
556 lines
22 KiB
Python
import os
|
|
import string
|
|
|
|
from booty import BootyNoKernelWarning
|
|
from util import getDiskPart
|
|
from bootloaderInfo import *
|
|
from flags import flags
|
|
import checkbootloader
|
|
import iutil
|
|
|
|
class x86BootloaderInfo(efiBootloaderInfo):
|
|
def setPassword(self, val, isCrypted = 1):
|
|
if not val:
|
|
self.password = val
|
|
self.pure = val
|
|
return
|
|
|
|
if isCrypted and self.useGrubVal == 0:
|
|
self.pure = None
|
|
return
|
|
elif isCrypted:
|
|
self.password = val
|
|
self.pure = None
|
|
else:
|
|
salt = "$1$"
|
|
saltLen = 8
|
|
|
|
saltchars = string.letters + string.digits + './'
|
|
for i in range(saltLen):
|
|
salt += random.choice(saltchars)
|
|
|
|
self.password = crypt.crypt(val, salt)
|
|
self.pure = val
|
|
|
|
def getPassword (self):
|
|
return self.pure
|
|
|
|
def setUseGrub(self, val):
|
|
self.useGrubVal = val
|
|
|
|
def getPhysicalDevices(self, device):
|
|
# This finds a list of devices on which the given device name resides.
|
|
# Accepted values for "device" are raid1 md devices (i.e. "md0"),
|
|
# physical disks ("hda"), and real partitions on physical disks
|
|
# ("hda1"). Volume groups/logical volumes are not accepted.
|
|
dev = self.storage.devicetree.getDeviceByName(device)
|
|
path = dev.path[5:]
|
|
|
|
if device in map (lambda x: x.name, self.storage.lvs + self.storage.vgs):
|
|
return []
|
|
|
|
if path.startswith("mapper/luks-"):
|
|
return []
|
|
|
|
if dev.type == "mdarray":
|
|
bootable = 0
|
|
parts = checkbootloader.getRaidDisks(device, self.storage,
|
|
raidLevel=1, stripPart=0)
|
|
parts.sort()
|
|
return parts
|
|
|
|
return [device]
|
|
|
|
def runGrubInstall(self, instRoot, bootDev, cmds, cfPath):
|
|
if cfPath == "/":
|
|
syncDataToDisk(bootDev, "/boot", instRoot)
|
|
else:
|
|
syncDataToDisk(bootDev, "/", instRoot)
|
|
|
|
# copy the stage files over into /boot
|
|
rc = iutil.execWithRedirect("/sbin/grub-install",
|
|
["--just-copy"],
|
|
stdout = "/dev/tty5", stderr = "/dev/tty5",
|
|
root = instRoot)
|
|
if rc:
|
|
return rc
|
|
|
|
# really install the bootloader
|
|
for cmd in cmds:
|
|
p = os.pipe()
|
|
os.write(p[1], cmd + '\n')
|
|
os.close(p[1])
|
|
|
|
# FIXME: hack to try to make sure everything is written
|
|
# to the disk
|
|
if cfPath == "/":
|
|
syncDataToDisk(bootDev, "/boot", instRoot)
|
|
else:
|
|
syncDataToDisk(bootDev, "/", instRoot)
|
|
|
|
rc = iutil.execWithRedirect('/sbin/grub' ,
|
|
[ "--batch", "--no-floppy",
|
|
"--device-map=/boot/grub/device.map" ],
|
|
stdin = p[0],
|
|
stdout = "/dev/tty5", stderr = "/dev/tty5",
|
|
root = instRoot)
|
|
os.close(p[0])
|
|
|
|
if rc:
|
|
return rc
|
|
|
|
def matchingBootTargets(self, stage1Devs, bootDevs):
|
|
matches = []
|
|
for stage1Dev in stage1Devs:
|
|
for mdBootPart in bootDevs:
|
|
if getDiskPart(stage1Dev, self.storage)[0] == getDiskPart(mdBootPart, self.storage)[0]:
|
|
matches.append((stage1Dev, mdBootPart))
|
|
return matches
|
|
|
|
def addMemberMbrs(self, matches, bootDevs):
|
|
updatedMatches = list(matches)
|
|
bootDevsHavingStage1Dev = [match[1] for match in matches]
|
|
for mdBootPart in bootDevs:
|
|
if mdBootPart not in bootDevsHavingStage1Dev:
|
|
updatedMatches.append((getDiskPart(mdBootPart, self.storage)[0], mdBootPart))
|
|
return updatedMatches
|
|
|
|
def installGrub(self, instRoot, bootDev, grubTarget, grubPath, cfPath):
|
|
if iutil.isEfi():
|
|
return efiBootloaderInfo.installGrub(self, instRoot, bootDev, grubTarget,
|
|
grubPath, cfPath)
|
|
|
|
args = "--stage2=/boot/grub/stage2 "
|
|
|
|
stage1Devs = self.getPhysicalDevices(grubTarget)
|
|
bootDevs = self.getPhysicalDevices(bootDev.name)
|
|
|
|
installs = [(None,
|
|
self.grubbyPartitionName(stage1Devs[0]),
|
|
self.grubbyPartitionName(bootDevs[0]))]
|
|
|
|
if bootDev.type == "mdarray":
|
|
|
|
matches = self.matchingBootTargets(stage1Devs, bootDevs)
|
|
|
|
# If the stage1 target disk contains member of boot raid array (mbr
|
|
# case) or stage1 target partition is member of boot raid array
|
|
# (partition case)
|
|
if matches:
|
|
# 1) install stage1 on target disk/partiton
|
|
stage1Dev, mdMemberBootPart = matches[0]
|
|
installs = [(None,
|
|
self.grubbyPartitionName(stage1Dev),
|
|
self.grubbyPartitionName(mdMemberBootPart))]
|
|
firstMdMemberDiskGrubbyName = self.grubbyDiskName(getDiskPart(mdMemberBootPart, self.storage)[0])
|
|
|
|
# 2) and install stage1 on other members' disks/partitions too
|
|
# NOTES:
|
|
# - the goal is to be able to boot after a members' disk removal
|
|
# - so we have to use grub device names as if after removal
|
|
# (i.e. the same disk name (e.g. (hd0)) for both member disks)
|
|
# - if member partitions have different numbers only removal of
|
|
# specific one of members will work because stage2 containing
|
|
# reference to config file is shared and therefore can contain
|
|
# only one value
|
|
|
|
# if target is mbr, we want to install also to mbr of other
|
|
# members, so extend the matching list
|
|
matches = self.addMemberMbrs(matches, bootDevs)
|
|
for stage1Target, mdMemberBootPart in matches[1:]:
|
|
# prepare special device mapping corresponding to member removal
|
|
mdMemberBootDisk = getDiskPart(mdMemberBootPart, self.storage)[0]
|
|
# It can happen due to ks --driveorder option, but is it ok?
|
|
if not mdMemberBootDisk in self.drivelist:
|
|
continue
|
|
mdRaidDeviceRemap = (firstMdMemberDiskGrubbyName,
|
|
mdMemberBootDisk)
|
|
|
|
stage1TargetGrubbyName = self.grubbyPartitionName(stage1Target)
|
|
rootPartGrubbyName = self.grubbyPartitionName(mdMemberBootPart)
|
|
|
|
# now replace grub disk name part according to special device
|
|
# mapping
|
|
old = self.grubbyDiskName(mdMemberBootDisk).strip('() ')
|
|
new = firstMdMemberDiskGrubbyName.strip('() ')
|
|
rootPartGrubbyName = rootPartGrubbyName.replace(old, new)
|
|
stage1TargetGrubbyName = stage1TargetGrubbyName.replace(old, new)
|
|
|
|
installs.append((mdRaidDeviceRemap,
|
|
stage1TargetGrubbyName,
|
|
rootPartGrubbyName))
|
|
|
|
# This is needed for case when /boot member partitions have
|
|
# different numbers. Shared stage2 can contain only one reference
|
|
# to grub.conf file, so let's ensure that it is reference to partition
|
|
# on disk which we will boot from - that is, install grub to
|
|
# this disk as last so that its reference is not overwritten.
|
|
installs.reverse()
|
|
|
|
cmds = []
|
|
for mdRaidDeviceRemap, stage1Target, rootPart in installs:
|
|
if mdRaidDeviceRemap:
|
|
cmd = "device (%s) /dev/%s\n" % tuple(mdRaidDeviceRemap)
|
|
else:
|
|
cmd = ''
|
|
cmd += "root %s\n" % (rootPart,)
|
|
cmd += "install %s%s/stage1 d %s %s/stage2 p %s%s/grub.conf" % \
|
|
(args, grubPath, stage1Target, grubPath, rootPart, grubPath)
|
|
cmds.append(cmd)
|
|
return self.runGrubInstall(instRoot, bootDev.name, cmds, cfPath)
|
|
|
|
def writeGrub(self, instRoot, bl, kernelList, chainList,
|
|
defaultDev, upgrade=False):
|
|
|
|
rootDev = self.storage.rootDevice
|
|
grubTarget = bl.getDevice()
|
|
|
|
try:
|
|
bootDev = self.storage.mountpoints["/boot"]
|
|
grubPath = "/grub"
|
|
cfPath = "/"
|
|
except KeyError:
|
|
bootDev = rootDev
|
|
grubPath = "/boot/grub"
|
|
cfPath = "/boot/"
|
|
|
|
if not upgrade:
|
|
self.writeGrubConf(instRoot, bootDev, rootDev, defaultDev, kernelList,
|
|
chainList, grubTarget, grubPath, cfPath)
|
|
|
|
# keep track of which devices are used for the device.map
|
|
usedDevs = set()
|
|
usedDevs.update(self.getPhysicalDevices(grubTarget))
|
|
usedDevs.update(self.getPhysicalDevices(rootDev.name))
|
|
usedDevs.update(self.getPhysicalDevices(bootDev.name))
|
|
usedDevs.update([dev for (label, longlabel, dev) in chainList if longlabel])
|
|
|
|
if not upgrade:
|
|
self.writeDeviceMap(instRoot, usedDevs, upgrade)
|
|
self.writeSysconfig(instRoot, grubTarget, upgrade)
|
|
|
|
return self.installGrub(instRoot, bootDev, grubTarget, grubPath, cfPath)
|
|
|
|
def writeGrubConf(self, instRoot, bootDev, rootDev, defaultDev, kernelList,
|
|
chainList, grubTarget, grubPath, cfPath):
|
|
|
|
bootDevs = self.getPhysicalDevices(bootDev.name)
|
|
|
|
# XXX old config file should be read here for upgrade
|
|
|
|
cf = "%s%s" % (instRoot, self.configfile)
|
|
self.perms = 0600
|
|
if os.access (cf, os.R_OK):
|
|
self.perms = os.stat(cf)[0] & 0777
|
|
os.rename(cf, cf + '.rpmsave')
|
|
|
|
f = open(cf, "w+")
|
|
|
|
f.write("# grub.conf generated by anaconda\n")
|
|
f.write("#\n")
|
|
f.write("# Note that you do not have to rerun grub "
|
|
"after making changes to this file\n")
|
|
|
|
if grubPath == "/grub":
|
|
f.write("# NOTICE: You have a /boot partition. This means "
|
|
"that\n")
|
|
f.write("# all kernel and initrd paths are relative "
|
|
"to /boot/, eg.\n")
|
|
else:
|
|
f.write("# NOTICE: You do not have a /boot partition. "
|
|
"This means that\n")
|
|
f.write("# all kernel and initrd paths are relative "
|
|
"to /, eg.\n")
|
|
|
|
f.write('# root %s\n' % self.grubbyPartitionName(bootDevs[0]))
|
|
f.write("# kernel %svmlinuz-version ro root=%s\n" % (cfPath, rootDev.path))
|
|
f.write("# initrd %sinitrd-[generic-]version.img\n" % (cfPath))
|
|
f.write("#boot=/dev/%s\n" % (grubTarget))
|
|
|
|
# get the default image to boot... we have to walk and find it
|
|
# since grub indexes by where it is in the config file
|
|
if defaultDev.name == rootDev.name:
|
|
default = 0
|
|
else:
|
|
# if the default isn't linux, it's the first thing in the
|
|
# chain list
|
|
default = len(kernelList)
|
|
|
|
|
|
f.write('default=%s\n' % (default))
|
|
|
|
if self.serial == 1:
|
|
# Set the global timeout in serial case
|
|
f.write('timeout=%d\n' % (self.timeout or 5))
|
|
# grub the 0-based number of the serial console device
|
|
unit = self.serialDevice[-1]
|
|
|
|
# and we want to set the speed too
|
|
speedend = 0
|
|
for char in self.serialOptions:
|
|
if char not in string.digits:
|
|
break
|
|
speedend = speedend + 1
|
|
if speedend != 0:
|
|
speed = self.serialOptions[:speedend]
|
|
else:
|
|
# reasonable default
|
|
speed = "9600"
|
|
|
|
f.write("serial --unit=%s --speed=%s\n" %(unit, speed))
|
|
f.write("terminal --timeout=%s serial console\n" % (self.timeout or 5))
|
|
else:
|
|
# Default to 0 timeout in the non-serial case
|
|
f.write('timeout=%d\n' % (self.timeout or 0))
|
|
# we only want splashimage if they're not using a serial console
|
|
if os.access("%s/boot/grub/splash.xpm.gz" %(instRoot,), os.R_OK):
|
|
f.write('splashimage=%s%sgrub/splash.xpm.gz\n'
|
|
% (self.grubbyPartitionName(bootDevs[0]), cfPath))
|
|
f.write("hiddenmenu\n")
|
|
|
|
|
|
if self.password:
|
|
f.write('password --md5 %s\n' %(self.password))
|
|
|
|
for (label, longlabel, version) in kernelList:
|
|
kernelTag = "-" + version
|
|
kernelFile = "%svmlinuz%s" % (cfPath, kernelTag)
|
|
|
|
initrd = self.makeInitrd(kernelTag, instRoot)
|
|
|
|
f.write('title %s (%s)\n' % (longlabel, version))
|
|
f.write('\troot %s\n' % self.grubbyPartitionName(bootDevs[0]))
|
|
|
|
realroot = " root=%s" % rootDev.fstabSpec
|
|
|
|
if version.endswith("xen0") or (version.endswith("xen") and not os.path.exists("/proc/xen")):
|
|
# hypervisor case
|
|
sermap = { "ttyS0": "com1", "ttyS1": "com2",
|
|
"ttyS2": "com3", "ttyS3": "com4" }
|
|
if self.serial and sermap.has_key(self.serialDevice) and \
|
|
self.serialOptions:
|
|
hvs = "%s=%s" %(sermap[self.serialDevice],
|
|
self.serialOptions)
|
|
else:
|
|
hvs = ""
|
|
if version.endswith("xen0"):
|
|
hvFile = "%sxen.gz-%s %s" %(cfPath,
|
|
version.replace("xen0", ""),
|
|
hvs)
|
|
else:
|
|
hvFile = "%sxen.gz-%s %s" %(cfPath,
|
|
version.replace("xen", ""),
|
|
hvs)
|
|
f.write('\tkernel %s\n' %(hvFile,))
|
|
f.write('\tmodule %s ro%s' %(kernelFile, realroot))
|
|
if self.args.get():
|
|
f.write(' %s' % self.args.get())
|
|
f.write('\n')
|
|
|
|
if initrd:
|
|
f.write('\tmodule %s%s\n' % (cfPath, initrd))
|
|
elif version.find("qubes") >= 0:
|
|
# Qubes kernel
|
|
hvFile = "%sxen.gz" %(cfPath)
|
|
f.write('\tkernel %s console=com1\n' %(hvFile,))
|
|
f.write('\tmodule %s ro%s' %(kernelFile, realroot))
|
|
if self.args.get():
|
|
f.write(' %s' % self.args.get())
|
|
f.write(' max_loop=255')
|
|
f.write('\n')
|
|
if initrd:
|
|
f.write('\tmodule %s%s\n' % (cfPath, initrd))
|
|
else: # normal kernel
|
|
f.write('\tkernel %s ro%s' % (kernelFile, realroot))
|
|
if self.args.get():
|
|
f.write(' %s' % self.args.get())
|
|
f.write('\n')
|
|
|
|
if initrd:
|
|
f.write('\tinitrd %s%s\n' % (cfPath, initrd))
|
|
|
|
for (label, longlabel, device) in chainList:
|
|
if ((not longlabel) or (longlabel == "")):
|
|
continue
|
|
f.write('title %s\n' % (longlabel))
|
|
f.write('\trootnoverify %s\n' % self.grubbyPartitionName(device))
|
|
# f.write('\tmakeactive\n')
|
|
f.write('\tchainloader +1')
|
|
f.write('\n')
|
|
|
|
f.close()
|
|
|
|
if not "/efi/" in cf:
|
|
os.chmod(cf, self.perms)
|
|
|
|
try:
|
|
# make symlink for menu.lst (default config file name)
|
|
menulst = "%s%s/menu.lst" % (instRoot, self.configdir)
|
|
if os.access (menulst, os.R_OK):
|
|
os.rename(menulst, menulst + ".rpmsave")
|
|
os.symlink("./grub.conf", menulst)
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
# make symlink for /etc/grub.conf (config files belong in /etc)
|
|
etcgrub = "%s%s" % (instRoot, "/etc/grub.conf")
|
|
if os.access (etcgrub, os.R_OK):
|
|
os.rename(etcgrub, etcgrub + ".rpmsave")
|
|
os.symlink(".." + self.configfile, etcgrub)
|
|
except:
|
|
pass
|
|
|
|
def writeDeviceMap(self, instRoot, usedDevs, upgrade=False):
|
|
|
|
if os.access(instRoot + "/boot/grub/device.map", os.R_OK):
|
|
# For upgrade, we want also e.g. devs that has been added
|
|
# to file during install for chainloading.
|
|
if upgrade:
|
|
f = open(instRoot + "/boot/grub/device.map", "r")
|
|
for line in f:
|
|
if line.startswith('(hd'):
|
|
(grubdisk, dev) = line.split()[:2]
|
|
dev = dev[5:]
|
|
if dev in self.drivelist:
|
|
usedDevs.add(dev)
|
|
f.close()
|
|
os.rename(instRoot + "/boot/grub/device.map",
|
|
instRoot + "/boot/grub/device.map.rpmsave")
|
|
|
|
f = open(instRoot + "/boot/grub/device.map", "w+")
|
|
f.write("# this device map was generated by anaconda\n")
|
|
usedDiskDevs = set()
|
|
for dev in usedDevs:
|
|
drive = getDiskPart(dev, self.storage)[0]
|
|
usedDiskDevs.add(drive)
|
|
devs = list(usedDiskDevs)
|
|
devs.sort()
|
|
for drive in devs:
|
|
# XXX hack city. If they're not the sort of thing that'll
|
|
# be in the device map, they shouldn't still be in the list.
|
|
dev = self.storage.devicetree.getDeviceByName(drive)
|
|
if not dev.type == "mdarray":
|
|
f.write("(%s) %s\n" % (self.grubbyDiskName(drive), dev.path))
|
|
f.close()
|
|
|
|
def writeSysconfig(self, instRoot, grubTarget, upgrade):
|
|
sysconf = '/etc/sysconfig/grub'
|
|
if os.access (instRoot + sysconf, os.R_OK):
|
|
if upgrade:
|
|
return
|
|
self.perms = os.stat(instRoot + sysconf)[0] & 0777
|
|
os.rename(instRoot + sysconf,
|
|
instRoot + sysconf + '.rpmsave')
|
|
# if it's an absolute symlink, just get it out of our way
|
|
elif (os.path.islink(instRoot + sysconf) and
|
|
os.readlink(instRoot + sysconf)[0] == '/'):
|
|
if upgrade:
|
|
return
|
|
os.rename(instRoot + sysconf,
|
|
instRoot + sysconf + '.rpmsave')
|
|
f = open(instRoot + sysconf, 'w+')
|
|
f.write("boot=/dev/%s\n" %(grubTarget,))
|
|
f.write("forcelba=0\n")
|
|
f.close()
|
|
|
|
def grubbyDiskName(self, name):
|
|
return "hd%d" % self.drivelist.index(name)
|
|
|
|
def grubbyPartitionName(self, dev):
|
|
(name, partNum) = getDiskPart(dev, self.storage)
|
|
if partNum != None:
|
|
return "(%s,%d)" % (self.grubbyDiskName(name), partNum)
|
|
else:
|
|
return "(%s)" %(self.grubbyDiskName(name))
|
|
|
|
|
|
def getBootloaderConfig(self, instRoot, bl, kernelList,
|
|
chainList, defaultDev):
|
|
config = bootloaderInfo.getBootloaderConfig(self, instRoot,
|
|
bl, kernelList, chainList,
|
|
defaultDev)
|
|
|
|
liloTarget = bl.getDevice()
|
|
|
|
config.addEntry("boot", '/dev/' + liloTarget, replace = 0)
|
|
config.addEntry("map", "/boot/map", replace = 0)
|
|
config.addEntry("install", "/boot/boot.b", replace = 0)
|
|
message = "/boot/message"
|
|
|
|
if self.pure is not None and not self.useGrubVal:
|
|
config.addEntry("restricted", replace = 0)
|
|
config.addEntry("password", self.pure, replace = 0)
|
|
|
|
if self.serial == 1:
|
|
# grab the 0-based number of the serial console device
|
|
unit = self.serialDevice[-1]
|
|
# FIXME: we should probably put some options, but lilo
|
|
# only supports up to 9600 baud so just use the defaults
|
|
# it's better than nothing :(
|
|
config.addEntry("serial=%s" %(unit,))
|
|
else:
|
|
# message screws up serial console
|
|
if os.access(instRoot + message, os.R_OK):
|
|
config.addEntry("message", message, replace = 0)
|
|
|
|
if not config.testEntry('lba32'):
|
|
if bl.above1024 and not iutil.isX86(bits=32):
|
|
config.addEntry("lba32", replace = 0)
|
|
|
|
return config
|
|
|
|
def write(self, instRoot, bl, kernelList, chainList,
|
|
defaultDev):
|
|
if self.timeout is None and chainList:
|
|
self.timeout = 5
|
|
|
|
# XXX HACK ALERT - see declaration above
|
|
if self.doUpgradeOnly:
|
|
if self.useGrubVal:
|
|
return self.writeGrub(instRoot, bl, kernelList,
|
|
chainList, defaultDev,
|
|
upgrade = True)
|
|
return 0
|
|
|
|
if len(kernelList) < 1:
|
|
raise BootyNoKernelWarning
|
|
|
|
rc = self.writeGrub(instRoot, bl, kernelList,
|
|
chainList, defaultDev,
|
|
not self.useGrubVal)
|
|
if rc:
|
|
return rc
|
|
|
|
# XXX move the lilo.conf out of the way if they're using GRUB
|
|
# so that /sbin/installkernel does a more correct thing
|
|
if self.useGrubVal and os.access(instRoot + '/etc/lilo.conf', os.R_OK):
|
|
os.rename(instRoot + "/etc/lilo.conf",
|
|
instRoot + "/etc/lilo.conf.anaconda")
|
|
|
|
return 0
|
|
|
|
def getArgList(self):
|
|
args = bootloaderInfo.getArgList(self)
|
|
|
|
if self.password:
|
|
args.append("--md5pass=%s" %(self.password))
|
|
|
|
return args
|
|
|
|
def __init__(self, anaconda):
|
|
bootloaderInfo.__init__(self, anaconda)
|
|
|
|
# these have to be set /before/ efiBootloaderInfo.__init__(), or
|
|
# they'll be overwritten.
|
|
self._configdir = "/boot/grub"
|
|
self._configname = "grub.conf"
|
|
|
|
efiBootloaderInfo.__init__(self, anaconda, initialize=False)
|
|
|
|
# XXX use checkbootloader to determine what to default to
|
|
self.useGrubVal = 1
|
|
self.kernelLocation = "/boot/"
|
|
self.password = None
|
|
self.pure = None
|