]>
git.ipfire.org Git - ipfire-3.x.git/blob - src/pomona/bootloader.py
2 # bootloader.py: anaconda bootloader shims
4 # Erik Troan <ewt@redhat.com>
5 # Jeremy Katz <katzj@redhat.com>
7 # Copyright 2001-2006 Red Hat, Inc.
9 # This software may be freely redistributed under the terms of the GNU
10 # general public license.
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 from flags
import flags
29 from constants
import *
32 _
= lambda x
: gettext
.ldgettext("pomona", x
)
35 log
= logging
.getLogger("pomona")
39 import pyfire
.executil
41 dosFilesystems
= ('FAT', 'fat16', 'fat32', 'ntfs', 'hpfs')
46 def checkForBootBlock(device
):
47 fd
= os
.open(device
, os
.O_RDONLY
)
48 buf
= os
.read(fd
, 512)
50 if len(buf
) >= 512 and struct
.unpack("H", buf
[0x1fe: 0x200]) == (0xaa55,):
54 # takes a line like #boot=/dev/hda and returns /dev/hda
55 # also handles cases like quoted versions and other nonsense
56 def getBootDevString(line
):
57 dev
= string
.split(line
, '=')[1]
58 dev
= string
.strip(dev
)
59 dev
= string
.replace(dev
, '"', '')
60 dev
= string
.replace(dev
, "'", "")
64 # there's no guarantee that data is written to the disk and grub
65 # reads both the filesystem and the disk. suck.
66 def syncDataToDisk(dev
, mntpt
, instRoot
= "/"):
72 # and xfs is even more "special" (#117968)
73 if fsset
.isValidXFS(dev
):
74 pyfire
.executil
.execWithRedirect("/usr/sbin/xfs_freeze",
75 ["/usr/sbin/xfs_freeze", "-f", mntpt
],
79 pyfire
.executil
.execWithRedirect("/usr/sbin/xfs_freeze",
80 ["/usr/sbin/xfs_freeze", "-u", mntpt
],
85 class BootyNoKernelWarning(Exception):
88 class KernelArguments
:
99 def chandevset(self
, args
):
102 def append(self
, args
):
104 # don't duplicate the addition of an argument (#128492)
105 if self
.args
.find(args
) != -1:
107 self
.args
= self
.args
+ " "
108 self
.args
= self
.args
+ "%s" % (args
,)
113 # look for kernel arguments we know should be preserved and add them
114 ourargs
= ["speakup_synth=", "apic", "noapic", "apm=", "ide=nodma",
115 "noht", "acpi=", "video=", "pci="]
116 f
= open("/proc/cmdline")
117 cmdline
= f
.read()[:-1]
119 cmdlineargs
= cmdline
.split(" ")
120 for arg
in cmdlineargs
:
121 for check
in ourargs
:
122 if arg
.startswith(check
):
125 self
.args
= " ".join(newArgs
)
128 """A collection to keep track of boot images available on the system.
130 ('linux', 'Red Hat Linux', 'ext2'),
131 ('Other', 'Other', 'fat32'), ...
138 """returns dictionary of (label, longlabel, devtype) pairs
140 # return a copy so users can modify it w/o affecting us
141 return copy(self
.images
)
143 def setImageLabel(self
, dev
, label
, setLong
= 0):
144 orig
= self
.images
[dev
]
146 self
.images
[dev
] = (orig
[0], label
, orig
[2])
148 self
.images
[dev
] = (label
, orig
[1], orig
[2])
150 def setDefault(self
, default
):
151 # default is a device
152 self
.default
= default
154 def getDefault(self
):
157 # XXX this has internal anaconda-ish knowledge. ick
158 def setup(self
, diskSet
, fsset
):
160 devs
= self
.availableBootDevices(diskSet
, fsset
)
161 for (dev
, type) in devs
:
164 # These partitions have disappeared
165 for dev
in self
.images
.keys():
166 if not devices
.has_key(dev
):
169 # These have appeared
170 for (dev
, type) in devs
:
171 if not self
.images
.has_key(dev
):
172 if type in dosFilesystems
and doesDualBoot():
173 self
.images
[dev
] = ("Other", "Other", type)
175 self
.images
[dev
] = (None, None, type)
177 if not self
.images
.has_key(self
.default
):
178 entry
= fsset
.getEntryByMountPoint('/')
179 self
.default
= entry
.device
.getDevice()
180 (label
, longlabel
, type) = self
.images
[self
.default
]
182 self
.images
[self
.default
] = ("linux", getProductName(), type)
184 # XXX more internal anaconda knowledge
185 def availableBootDevices(self
, diskSet
, fsset
):
188 for (dev
, type) in diskSet
.partitionTypes():
189 if type in dosFilesystems
and not foundDos
and doesDualBoot():
193 part
= partedUtils
.get_partition_by_name(diskSet
.disks
, dev
)
194 if part
.native_type
not in partedUtils
.dosPartitionTypes
:
198 bootable
= checkForBootBlock(dev
)
199 devs
.append((dev
, type))
202 #log("exception checking %s: %s" %(dev, e))
204 elif ((type == 'ntfs' or type =='hpfs') and not foundDos
and doesDualBoot()):
205 devs
.append((dev
, type))
206 # maybe questionable, but the first ntfs or fat is likely to
207 # be the correct one to boot with XP using ntfs
210 slash
= fsset
.getEntryByMountPoint('/')
211 if not slash
or not slash
.device
or not slash
.fsystem
:
212 raise ValueError,("Trying to pick boot devices but do not have a "
213 "sane root partition. Aborting install.")
214 devs
.append((slash
.device
.getDevice(), slash
.fsystem
.getName()))
219 class bootloaderInfo
:
221 return self
.useGrubVal
223 def setForceLBA(self
, val
):
226 def setPassword(self
, val
, isCrypted
= 1):
229 def getPassword(self
):
235 def setDevice(self
, device
):
238 (dev
, part
) = getDiskPart(device
)
240 self
.defaultDevice
= "mbr"
242 self
.defaultDevice
= "partition"
244 def createDriveList(self
):
245 # create a drive list that we can use for drive mappings
246 # XXX has pomona internals knowledge
248 drives
= isys
.hardDriveDict().keys()
249 drives
.sort(isys
.compareDrives
)
251 # now filter out all of the drives without media present
252 drives
= filter(lambda x
: isys
.mediaPresent(x
), drives
)
256 def updateDriveList(self
, sortedList
=[]):
257 self
._drivelist
= self
.createDriveList()
259 # If we're given a sort order, make sure the drives listed in it
260 # are put at the head of the drivelist in that order. All other
261 # drives follow behind in whatever order they're found.
263 revSortedList
= sortedList
264 revSortedList
.reverse()
266 for i
in revSortedList
:
268 ele
= self
._drivelist
.pop(self
._drivelist
.index(i
))
269 self
._drivelist
.insert(0, ele
)
273 def _getDriveList(self
):
274 if self
._drivelist
is not None:
275 return self
._drivelist
276 self
.updateDriveList()
277 return self
._drivelist
279 def _setDriveList(self
, val
):
280 self
._drivelist
= val
281 drivelist
= property(_getDriveList
, _setDriveList
)
284 self
.args
= KernelArguments()
285 self
.images
= BootImages()
287 self
.defaultDevice
= None # XXX hack, used by kickstart
288 self
.useGrubVal
= 0 # only used on x86
289 self
.configfile
= None
290 self
.kernelLocation
= "/boot/"
295 self
._drivelist
= None
297 class x86BootloaderInfo(bootloaderInfo
):
298 def setPassword(self
, val
, isCrypted
= 1):
304 if isCrypted
and self
.useGrubVal
== 0:
305 #log("requested crypted password with lilo; ignoring")
315 saltchars
= string
.letters
+ string
.digits
+ './'
316 for i
in range(saltLen
):
317 salt
+= random
.choice(saltchars
)
319 self
.password
= crypt
.crypt(val
, salt
)
322 def getPassword (self
):
325 def setForceLBA(self
, val
):
326 self
.forceLBA32
= val
328 def getPhysicalDevices(self
, device
):
329 # This finds a list of devices on which the given device name resides.
330 # Accepted values for "device" are physical disks ("hda"),
331 # and real partitions on physical disks ("hda1").
335 def writeGrub(self
, instRoot
, fsset
, bl
, kernelList
, chainList
,
338 images
= bl
.images
.getImages()
339 rootDev
= fsset
.getEntryByMountPoint("/").device
.getDevice()
341 if not os
.path
.isdir(instRoot
+ '/boot/grub/'):
342 os
.mkdir(instRoot
+ '/boot/grub', 0755)
344 cf
= '/boot/grub/grub.conf'
346 if os
.access (instRoot
+ cf
, os
.R_OK
):
347 self
.perms
= os
.stat(instRoot
+ cf
)[0] & 0777
348 os
.rename(instRoot
+ cf
, instRoot
+ cf
+ '.backup')
350 grubTarget
= bl
.getDevice()
352 if (grubTarget
.startswith('rd/') or grubTarget
.startswith('ida/') or
353 grubTarget
.startswith('cciss/') or
354 grubTarget
.startswith('sx8/') or
355 grubTarget
.startswith('mapper/')):
356 if grubTarget
[-1].isdigit():
357 if grubTarget
[-2] == 'p' or \
358 (grubTarget
[-2].isdigit() and grubTarget
[-3] == 'p'):
360 elif grubTarget
[-1].isdigit() and not grubTarget
.startswith('md'):
363 f
= open(instRoot
+ cf
, "w+")
365 f
.write("# grub.conf generated by pomona\n")
368 bootDev
= fsset
.getEntryByMountPoint("/boot")
372 bootDev
= fsset
.getEntryByMountPoint("/")
373 grubPath
= "/boot/grub"
375 f
.write("# NOTICE: You do not have a /boot partition. "
377 f
.write("# all kernel and initrd paths are relative "
380 f
.write("# NOTICE: You have a /boot partition. This means "
382 f
.write("# all kernel and initrd paths are relative "
385 bootDevs
= self
.getPhysicalDevices(bootDev
.device
.getDevice())
386 bootDev
= bootDev
.device
.getDevice()
388 f
.write('# root %s\n' % self
.grubbyPartitionName(bootDevs
[0]))
389 f
.write("# kernel %svmlinuz-version ro "
390 "root=/dev/%s\n" % (cfPath
, rootDev
))
391 f
.write("# initrd %sinitramfs-version.img\n" % (cfPath
))
392 f
.write("#boot=/dev/%s\n" % (grubTarget
))
394 # keep track of which devices are used for the device.map
397 # get the default image to boot... first kernel image here
400 f
.write('default=%s\n' % (default
))
402 # get the default timeout
404 f
.write('timeout=%d\n' %(timeout
,))
406 # we only want splashimage if they're not using a serial console
407 if os
.access("%s/boot/grub/splash.xpm.gz" %(instRoot
,), os
.R_OK
):
408 f
.write('splashimage=%s%sgrub/splash.xpm.gz\n'
409 % (self
.grubbyPartitionName(bootDevs
[0]), cfPath
))
410 f
.write("hiddenmenu\n")
412 for dev
in self
.getPhysicalDevices(grubTarget
):
416 f
.write('password --md5 %s\n' % (self
.password
))
418 for (kernelName
, kernelVersion
, kernelTag
, kernelDesc
) in kernelList
:
419 kernelFile
= "%s%skernel%s" % (cfPath
, sname
, kernelTag
,)
421 initrd
= "/boot/initramfs-%s%s.img" % (kernelVersion
, kernelTag
,)
423 f
.write('title %s (%s - %s)\n' % (name
, kernelDesc
, kernelVersion
))
424 f
.write('\troot %s\n' % self
.grubbyPartitionName(bootDevs
[0]))
426 realroot
= getRootDevName(initrd
, fsset
, rootDev
, instRoot
)
427 realroot
= " root=%s" %(realroot
,)
429 f
.write('\tkernel %s ro%s' % (kernelFile
, realroot
))
430 self
.args
.append("quiet")
432 f
.write(' %s' % self
.args
.get())
435 if os
.access (instRoot
+ initrd
, os
.R_OK
):
436 # initrd is built in backend.postInstall
437 f
.write('\tinitrd %sinitramfs-%s%s.img\n' % (cfPath
, kernelVersion
, kernelTag
,))
439 for (label
, longlabel
, device
) in chainList
:
440 if ((not longlabel
) or (longlabel
== "")):
442 f
.write('title %s\n' % (longlabel
))
443 f
.write('\trootnoverify %s\n' % self
.grubbyPartitionName(device
))
444 # f.write('\tmakeactive\n')
445 f
.write('\tchainloader +1')
450 os
.chmod(instRoot
+ "/boot/grub/grub.conf", self
.perms
)
453 # make symlink for /etc/grub.conf (config files belong in /etc)
454 if os
.access (instRoot
+ "/etc/grub.conf", os
.R_OK
):
455 os
.rename(instRoot
+ "/etc/grub.conf", instRoot
+ "/etc/grub.conf.backup")
456 os
.symlink("../boot/grub/grub.conf", instRoot
+ "/etc/grub.conf")
460 for dev
in self
.getPhysicalDevices(rootDev
) + bootDevs
:
463 if os
.access(instRoot
+ "/boot/grub/device.map", os
.R_OK
):
464 os
.rename(instRoot
+ "/boot/grub/device.map", instRoot
+ "/boot/grub/device.map.backup")
466 f
= open(instRoot
+ "/boot/grub/device.map", "w+")
467 f
.write("# this device map was generated by pomona\n")
468 devs
= usedDevs
.keys()
471 drive
= getDiskPart(dev
)[0]
472 if usedDevs
.has_key(drive
):
475 devs
= usedDevs
.keys()
478 # XXX hack city. If they're not the sort of thing that'll
479 # be in the device map, they shouldn't still be in the list.
480 if not drive
.startswith('md'):
481 f
.write("(%s) /dev/%s\n" % (self
.grubbyDiskName(drive
), drive
))
484 args
= "--stage2=/boot/grub/stage2 "
486 args
= "%s--force-lba " % (args
,)
488 sysconf
= '/etc/sysconfig/grub'
489 if os
.access (instRoot
+ sysconf
, os
.R_OK
):
490 self
.perms
= os
.stat(instRoot
+ sysconf
)[0] & 0777
491 os
.rename(instRoot
+ sysconf
, instRoot
+ sysconf
+ '.backup')
492 # if it's an absolute symlink, just get it out of our way
493 elif (os
.path
.islink(instRoot
+ sysconf
) and
494 os
.readlink(instRoot
+ sysconf
)[0] == '/'):
495 os
.rename(instRoot
+ sysconf
, instRoot
+ sysconf
+ '.backup')
496 f
= open(instRoot
+ sysconf
, 'w+')
497 f
.write("boot=/dev/%s\n" %(grubTarget
,))
498 # XXX forcelba never gets read back...
500 f
.write("forcelba=1\n")
502 f
.write("forcelba=0\n")
506 for bootDev
in bootDevs
:
507 gtPart
= self
.getMatchingPart(bootDev
, grubTarget
)
508 gtDisk
= self
.grubbyPartitionName(getDiskPart(gtPart
)[0])
509 bPart
= self
.grubbyPartitionName(bootDev
)
510 cmd
= "root %s\n" % (bPart
,)
512 stage1Target
= gtDisk
513 if target
== "partition":
514 stage1Target
= self
.grubbyPartitionName(gtPart
)
516 cmd
+= "install %s%s/stage1 d %s %s/stage2 p %s%s/grub.conf" % \
517 (args
, grubPath
, stage1Target
, grubPath
, bPart
, grubPath
)
520 log
.info("GRUB commands:")
522 log
.info("\t%s\n", cmd
)
525 syncDataToDisk(bootDev
, "/boot", instRoot
)
527 syncDataToDisk(bootDev
, "/", instRoot
)
529 # copy the stage files over into /boot
530 pyfire
.executil
.execWithRedirect("/usr/sbin/grub-install",
531 ["/usr/sbin/grub-install", "--just-copy"],
532 stdout
= "/dev/tty5", stderr
= "/dev/tty5",
535 # really install the bootloader
538 os
.write(p
[1], cmd
+ '\n')
542 # FIXME: hack to try to make sure everything is written
545 syncDataToDisk(bootDev
, "/boot", instRoot
)
547 syncDataToDisk(bootDev
, "/", instRoot
)
549 pyfire
.executil
.execWithRedirect("/usr/sbin/grub" ,
550 [ "grub", "--batch", "--no-floppy",
551 "--device-map=/boot/grub/device.map" ],
553 stdout
= "/dev/tty5", stderr
= "/dev/tty5",
559 def getMatchingPart(self
, bootDev
, target
):
560 bootName
, bootPartNum
= getDiskPart(bootDev
)
561 devices
= self
.getPhysicalDevices(target
)
562 for device
in devices
:
563 name
, partNum
= getDiskPart(device
)
568 def grubbyDiskName(self
, name
):
569 return "hd%d" % self
.drivelist
.index(name
)
571 def grubbyPartitionName(self
, dev
):
572 (name
, partNum
) = getDiskPart(dev
)
574 return "(%s,%d)" % (self
.grubbyDiskName(name
), partNum
)
576 return "(%s)" %(self
.grubbyDiskName(name
))
578 def write(self
, instRoot
, fsset
, bl
, kernelList
, chainList
, defaultDev
, intf
):
579 out
= self
.writeGrub(instRoot
, fsset
, bl
, kernelList
, chainList
, defaultDev
)
581 def getArgList(self
):
585 args
.append("--lba32")
587 args
.append("--md5pass=%s" %(self
.password
))
589 # XXX add location of bootloader here too
594 bootloaderInfo
.__init
__(self
)
596 self
.kernelLocation
= "/boot/"
597 self
.configfile
= "/etc/lilo.conf"
602 # end of boot loader objects... these are just some utility functions used
604 # return (disk, partition number) eg ('hda', 1)
605 def getDiskPart(dev
):
607 if (dev
.startswith('rd/') or dev
.startswith('ida/') or
608 dev
.startswith('cciss/') or dev
.startswith('sx8/') or
609 dev
.startswith('mapper/')):
615 if dev
[-2] in string
.digits
:
617 elif dev
[-1] in string
.digits
:
622 # hack off the trailing 'p' from /dev/cciss/*, for example
625 if letter
not in string
.letters
and letter
!= "/":
630 partNum
= int(dev
[cut
:]) - 1
634 return (name
, partNum
)
636 # hackery to determine if we should do root=LABEL=/ or whatnot
637 # as usual, knows too much about pomona
638 def getRootDevName(initrd
, fsset
, rootDev
, instRoot
):
639 if not os
.access(instRoot
+ initrd
, os
.R_OK
):
640 return "/dev/%s" % (rootDev
,)
642 rootEntry
= fsset
.getEntryByMountPoint("/")
643 if rootEntry
.getUuid() is not None:
644 return "UUID=%s" %(rootEntry
.getUuid(),)
645 elif rootEntry
.getLabel() is not None and rootEntry
.device
.doLabel
is not None:
646 return "LABEL=%s" %(rootEntry
.getLabel(),)
647 return "/dev/%s" %(rootDev
,)
649 return "/dev/%s" %(rootDev
,)
651 # returns a product name to use for the boot loader string
652 def getProductName():
653 # XXX Check /etc/ipfire-release here...
654 return "IPFire Linux"
656 def bootloaderSetupChoices(pomona
):
657 if pomona
.dir == DISPATCH_BACK
:
659 pomona
.id.bootloader
.updateDriveList()
661 choices
= pomona
.id.fsset
.bootloaderChoices(pomona
.id.diskset
, pomona
.id.bootloader
)
663 pomona
.id.bootloader
.images
.setup(pomona
.id.diskset
, pomona
.id.fsset
)
665 if pomona
.id.bootloader
.defaultDevice
!= None and choices
:
666 keys
= choices
.keys()
667 # there are only two possible things that can be in the keys
668 # mbr and boot. boot is ALWAYS present. so if the dev isn't
669 # listed, it was mbr and we should nicely fall back to boot
670 if pomona
.id.bootloader
.defaultDevice
not in keys
:
671 log
.warning("MBR not suitable as boot device; installing to partition")
672 pomona
.id.bootloader
.defaultDevice
= "boot"
673 pomona
.id.bootloader
.setDevice(choices
[pomona
.id.bootloader
.defaultDevice
][0])
674 elif choices
and choices
.has_key("mbr"):
675 pomona
.id.bootloader
.setDevice(choices
["mbr"][0])
676 elif choices
and choices
.has_key("boot"):
677 pomona
.id.bootloader
.setDevice(choices
["boot"][0])
679 bootDev
= pomona
.id.fsset
.getEntryByMountPoint("/")
681 bootDev
= pomona
.id.fsset
.getEntryByMountPoint("/boot")
682 part
= partedUtils
.get_partition_by_name(pomona
.id.diskset
.disks
,
683 bootDev
.device
.getDevice())
684 if part
and partedUtils
.end_sector_to_cyl(part
.geom
.dev
, part
.geom
.end
) >= 1024:
685 pomona
.id.bootloader
.above1024
= 1
687 def writeBootloader(pomona
):
693 if pomona
.id.bootloader
.defaultDevice
== -1:
694 log
.error("No default boot device set")
697 w
= pomona
.intf
.waitWindow(_("Bootloader"), _("Installing bootloader..."))
701 root
= pomona
.id.fsset
.getEntryByMountPoint('/')
703 rootDev
= root
.device
.getDevice()
706 defaultDev
= pomona
.id.bootloader
.images
.getDefault()
709 kernelLongLabel
= None
711 for (dev
, (label
, longlabel
, type)) in pomona
.id.bootloader
.images
.getImages().items():
712 if (dev
== rootDev
) or (rootDev
is None and kernelLabel
is None):
714 kernelLongLabel
= longlabel
715 elif dev
== defaultDev
:
716 otherList
= [(label
, longlabel
, dev
)] + otherList
718 otherList
.append((label
, longlabel
, dev
))
720 if kernelLabel
is None:
721 log
.error("unable to find default image, bailing")
724 for (kernelName
, kernelVersion
, kernelTag
, kernelDesc
) in pomona
.backend
.kernelVersionList(pomona
):
726 defkern
= "%s%s" % (kernelName
, kernelTag
)
728 if kernelTag
== "-smp" and isys
.smpAvailable():
729 defkern
= "%s%s" % (kernelName
, kernelTag
)
731 kernelList
.append((kernelName
, kernelVersion
, kernelTag
, kernelDesc
))
733 f
= open(pomona
.rootPath
+ "/etc/sysconfig/kernel", "w+")
734 f
.write("# DEFAULTKERNEL specifies the default kernel package type\n")
735 f
.write("DEFAULTKERNEL=%s\n" %(defkern
,))
740 pomona
.id.bootloader
.write(pomona
.rootPath
, pomona
.id.fsset
, pomona
.id.bootloader
,
741 kernelList
, otherList
, defaultDev
, pomona
.intf
)
743 except BootyNoKernelWarning
:
746 pomona
.intf
.messageWindow(_("Warning"),
747 _("No kernel packages were installed on your "
748 "system. Your boot loader configuration "
749 "will not be changed."))
752 # return instance of the appropriate bootloader for our arch
754 return x86BootloaderInfo()