#
# silo.py - SILO configuration and set up
#
# Jakub Jelinek <jakub@redhat.com>
#
# Copyright 2001 Red Hat, Inc.
#
# This software may be freely redistributed under the terms of the GNU
# library public license.
#
# You should have received a copy of the GNU Library Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

import string
import os
from lilo import LiloConfigFile
import _silo
import iutil
import isys
import rpm

class SiloInstall:
    def allowSiloLocationConfig(self, fstab):
	bootDevice = fstab.getBootDevice()
	if bootDevice[0:2] == "md":
	    self.setDevice(("raid", bootDevice))
	    return None
	return 1

    def checkUFS(self, dev):
	f = open("/proc/mounts","r")
	lines = f.readlines ()
	f.close ()
	mounted = None
	ufstype = None
	for line in lines:
	    fields = string.split (line)
	    if fields[0] == '/dev/' + dev and fields[2] == 'ufs':
		mounted = fields[1]
	if not mounted:
	    try:
		os.mkdir ("/tmp/ufsmntpoint")
		isys.makeDevInode (dev, "/tmp/" + dev)
		isys.mount ("/tmp/" + dev, "/tmp/ufsmntpoint", "ufs")
	    except:
		try:
		    os.remove ("/tmp/" + dev)
		except:
		    pass
		try:
		    os.rmdir ("/tmp/ufsmntpoint")
		except:
		    pass
		return None
	    root = "/tmp/ufsmntpoint"
	else:
	    root = mounted
	if os.access (root + "/etc/system", os.X_OK):
	    if os.access (root + "/kernel/unix", os.X_OK):
		ufstype = "Solaris"
	    elif os.access (root + "/kernel/genunix", os.X_OK):
		ufstype = "Solaris"
	if os.access (root + "/vmunix", os.X_OK) or os.access (root + "/stand", os.X_OK):
	    if os.access (root + "/bin/sh", os.X_OK):
		# Check if /bin/sh is a.out SPARC binary
		aout = None
		try:
		    f = open (root + "/bin/sh", "r")
		    aout = f.read(4)
		    f.close()
		except:
		    pass
		if aout and len(aout) == 4 and aout[1] == "\x03" and aout[2] == "\x01":
		    if aout[3] == "\x07" or aout[3] == "\x08" or aout[3] == "\x0b":
			ufstype = "SunOS"
	if not mounted:
	    try:
		isys.umount ("/tmp/ufsmntpoint")
	    except:
		pass
	    try:
		os.rmdir ("/tmp/ufsmntpoint")
	    except:
		pass
	    try:
		os.remove ("/tmp/" + dev)
	    except:
		pass
	return ufstype

    def setSiloImages(self, images):
	self.siloImages = images

    def getSiloImages(self, fstab):
	(drives, raid) = fstab.raidList()

	# rearrange the fstab so it's indexed by device
	mountsByDev = {}
	for (mntpoint, device, fsystem, doFormat, size) in \
		fstab.mountList():
	    mountsByDev[device] = mntpoint

	for (mntpoint, device, fstype, raidType, start, size, makeup) in raid:
	    mountsByDev[device] = mntpoint
	    drives.append(device, "", 2, 0, 0)

	for (device, mntpoint, fsystem, makeup) in fstab.existingRaidList():
	    mountsByDev[device] = mntpoint
	    drives.append(device, "", 2, 0, 0)

	oldImages = {}
	for dev in self.siloImages.keys():
	    oldImages[dev] = self.siloImages[dev]

	self.siloImages = {}
	nSolaris = 0
	nSunOS = 0
	for (dev, devName, type, start, size, maxcyl, preexist) in drives:
	    # ext2 and raid partitions get listed if 
	    #	    1) they're /
	    #	    2) they're not mounted
	    #	       and contain /boot of
	    #	       some Linux installation
	    # FIXME: For now only list / and UFS partitions,
	    # for 7.0 write code which will read and parse silo.conf from other
	    # Linux partitions and merge it in (after required device
	    # substitions etc.

	    # only list ext2 and ufs partitions
	    if type != 2 and type != 6:
		continue

	    if (mountsByDev.has_key(dev)):
		if mountsByDev[dev] == '/':
		    self.siloImages[dev] = ("linux", 2)
	    elif type == 6:
		if not oldImages.has_key(dev):
		    self.siloImages[dev] = ("", type)
		else:
		    self.siloImages[dev] = oldImages[dev]
		ostype = self.checkUFS(dev)
		if ostype == "Solaris":
		    if nSolaris == 0:
			self.siloImages[dev] = ("solaris", type)
		    else:
			self.siloImages[dev] = ("solaris%d" % nSolaris, type)
		    nSolaris = nSolaris + 1
		elif ostype == "SunOS":
		    if nSunOS == 0:
			self.siloImages[dev] = ("sunos", type)
		    else:
			self.siloImages[dev] = ("sunos%d" % nSunOS, type)
		    nSunOS = nSunOS + 1

	return (self.siloImages, self.default)

    def getSiloMbrDefault(self, fstab):
	# Check partition at cylinder 0 on the boot disk
	# is /, /boot or Linux swap
	bootpart = fstab.getBootDevice()
	if bootpart[:2] == "md":
	    return "mbr"
	i = len (bootpart) - 1
	while i > 0 and bootpart[i] in string.digits:
	    i = i - 1
	boothd = bootpart[:i+1]
	(drives, raid) = fstab.partitionList()
	for (dev, devName, type, start, size, maxcyl, preexist) in drives:
	    i = len (dev) - 1
	    while i > 0 and dev[i] in string.digits:
		i = i - 1
	    devhd = dev[:i+1]
	    if devhd == boothd and start == 0:
		if type == 5:
		    return "mbr"
		elif type == 2:
		    if dev == bootpart:
			return "mbr"
		    elif dev == fstab.getRootDevice()[0]:
			return "mbr"
		return "partition"
	return "partition"

    def hasUsableFloppy(self):
	try:
	    f = open("/proc/devices", "r")
	except:
	    return 0
	lines = f.readlines ()
	f.close ()
	for line in lines:
	    if string.strip (line) == "2 fd":
		name = _silo.promRootName()
		if name and name[0:10] == 'SUNW,Ultra' and string.find(name, "Engine") == -1:
		    # Seems like SMCC Ultra box. It is highly probable
		    # the floppies will be unbootable
		    return 1
		return 2
	return 0

    def setPROM(self, linuxAlias, bootDevice):
	self.linuxAlias = linuxAlias
	self.bootDevice = bootDevice

    def hasAliases(self):
	return _silo.hasAliases()

    def disk2PromPath(self,dev):
	return _silo.disk2PromPath(dev)

    def makeInitrd (self, kernelTag, instRoot):
	initrd = "/boot/initrd%s.img" % (kernelTag, )
	if not self.initrdsMade.has_key(initrd):
	    iutil.execWithRedirect("/sbin/mkinitrd",
				  [ "/sbin/mkinitrd",
				    "--ifneeded",
				    initrd,
				    kernelTag[1:] ],
				  stdout = None, stderr = None, searchPath = 1,
				  root = instRoot)
	    self.initrdsMade[kernelTag] = 1
	return initrd

    def getMbrDevices(self, fstab):
	bootpart = fstab.getBootDevice()
	mbrdevs = []
	if bootpart[:2] == "md":
	    (devices, raid) = fstab.raidList()
	    for (raidMntPoint, raidDevice, fsType, raidType, raidStart, raidSize, raidDevs) in raid:
		if raidDevice != bootpart: continue
		for raidDev in raidDevs:
		    for (device, name, type, start, size) in devices:
			if name == raidDev:
			    i = len(device) - 1
			    while i > 0 and device[i] in string.digits:
				i = i - 1
			    mbrdevs.append(device[:i+1])
	else:
	    # Do not use fstab.getMbrDevice() here
	    i = len (bootpart) - 1
	    while i > 0 and bootpart[i] in string.digits:
		i = i - 1
	    mbrdevs.append(bootpart[:i+1])
	return mbrdevs

    def getMbrDevice(self, fstab):
	return self.getMbrDevices(fstab)[0]

    def install(self, fstab, instRoot, hdList, upgrade):
	if not self.siloDevice: return

	silo = LiloConfigFile ()

	if not self.siloImages:
	    (images, default) = self.getSiloImages(fstab)
	    self.setSiloImages(images)

	bootpart = fstab.getBootDevice()
	boothd = self.getMbrDevice(fstab)
	smpInstalled = (hdList.has_key('kernel-smp') and 
			hdList['kernel-smp'].selected)

	rootDev = fstab.getRootDevice ()
	if rootDev:
	    # strip off the filesystem; we don't need it
	    rootDev = rootDev[0]
	else:
	    raise RuntimeError, "Installing silo, but there is no root device"

	args = [ "silo" ]

	if bootpart[:2] == "md":
	    self.siloDevice = "mbr"
	else:
	    if self.siloDevice != "mbr":
		args.append("-t")

	    i = len (bootpart) - 1
	    while i > 0 and bootpart[i] in string.digits:
		i = i - 1
            silo.addEntry("partition", bootpart[i+1:])

	silo.addEntry("timeout", "50")
	silo.addEntry("root", '/dev/' + rootDev)
	silo.addEntry("read-only")

	kernelList = []
	otherList = []

	main = "linux"

	for (drive, (label, siloType)) in self.siloImages.items ():
	    if (drive == rootDev) and label:
		main = label
	    elif label:
		i = len(drive) - 1
		while i > 0 and drive[i] in string.digits:
		    i = i - 1
		prompath = drive[:i+1]
		if bootpart[:2] != "md" and prompath == boothd:
		    prompath = drive[i+1:]
		else:
		    prompath = self.disk2PromPath(prompath)
		    if prompath:
			if prompath[:3] == 'sd(':
			    prompath = prompath + drive[i+1:]
			else:
			    prompath = prompath + ";" + drive[i+1:]
		if prompath:
		    otherList.append (label, prompath)

	silo.addEntry("default", main)

	label = main
	if (smpInstalled):
	    kernelList.append((main, hdList['kernel-smp'], "smp"))
	    label = main + "-up"

	kernelList.append((label, hdList['kernel'], ""))

	for (label, kernel, tag) in kernelList:
	    kernelTag = "-%s-%s%s" % (kernel[rpm.RPMTAG_VERSION],
                                      kernel[rpm.RPMTAG_RELEASE], tag)
	    initrd = self.makeInitrd (kernelTag, instRoot)
	    if rootDev == bootpart:
		kernelFile = "/boot/vmlinuz" + kernelTag
		initrdFile = initrd
	    else:
		kernelFile = "/vmlinuz" + kernelTag
		initrdFile = initrd[5:]

	    try:
		(fsType, sl) = silo.getImage(label)
		silo.delImage(label)
	    except IndexError, msg:
		sl = LiloConfigFile(imageType = "image", path = kernelFile)

	    sl.addEntry("label", label)
	    if os.access (instRoot + initrd, os.R_OK):
		sl.addEntry("initrd", initrdFile)

	    if self.siloAppend:
		sl.addEntry('append', '"%s"' % self.siloAppend)

	    silo.addImage (sl)

	for (label, device) in otherList:
	    try:
		(fsType, sl) = silo.getImage(label)
		silo.delImage(label)
	    except IndexError:
                sl = LiloConfigFile(imageType = "other", path = device)
	    sl.addEntry("label", label)
	    silo.addImage (sl)

	# for (siloType, name, config) in silo.images:
	#    # remove entries for missing kernels (upgrade)
	#    if siloType == "image":
	#	if not os.access (todo.instPath + name, os.R_OK):
	#	    silo.delImage (name)
	#    # remove entries for unbootable partitions
	#    elif siloType == "other":
	#	device = name[5:]
	#	isys.makeDevInode(device, '/tmp/' + device)
	#	if not isys.checkBoot ('/tmp/' + device):
	#	    lilo.delImage (name)
	#	os.remove ('/tmp/' + device)

	if fstab.getBootDevice() != fstab.getRootDevice()[0]:
	    silo.write(instRoot + "/boot/silo.conf")
	    try:
		os.remove(instRoot + "/etc/silo.conf")
	    except:
		pass
	    os.symlink("../boot/silo.conf", instRoot + "/etc/silo.conf")
	else:
	    silo.write(instRoot + "/etc/silo.conf")

	if self.serial:
	    messages = "/tmp/silo.log"
	else:
	    messages = "/dev/tty3"
	iutil.execWithRedirect('/sbin/silo',
			       args,
			       stdout = None,
			       root = instRoot)

	if bootpart[:2] == "md":
	    mbrdevs = self.getMbrDevices(fstab)
	    linuxAliases = []
	    for mbrdev in mbrdevs:
		device = mbrdev
		try:
		    num = _silo.zeroBasedPart(instRoot + "/dev/" + mbrdev)
		    if num:
			device = mbrdev + "%d" % num
		except:
		    pass
		linuxAliases.append(self.disk2PromPath(device))
	    bootDevice = linuxAliases[0]
	    linuxAlias = ""
	    for alias in linuxAliases:
		if alias and alias != "":
		    if linuxAlias != "":
			linuxAlias = linuxAlias + ";"
		    linuxAlias = linuxAlias + alias
	elif self.siloDevice == "mbr":
	    device = boothd
	    try:
		num = _silo.zeroBasedPart(instRoot + "/dev/" + boothd)
		if num:
		    device = boothd + "%d" % num
	    except:
		pass
	    linuxAlias = self.disk2PromPath(device)
	    bootDevice = linuxAlias
	else:
	    device = bootpart
	    linuxAlias = self.disk2PromPath(device)
	    bootDevice = linuxAlias

	if not (self.linuxAlias and self.hasAliases()):
	    linuxAlias = ""
	if not self.bootDevice:
	    bootDevice = ""
	if not linuxAlias:
	    linuxAlias = ""
	_silo.setPromVars(linuxAlias,bootDevice)

    def setDevice(self, device):
	if (type(device) == type((1,))):
	    self.siloDevice = device
	elif device != "mbr" and device != "partition" and device:
	    raise ValueError, "device must be raid, mbr, partition, or None"
	self.siloDevice = device

    def setAppend(self, append):
	self.siloAppend = append

    def setDefault(self, default):
	for (label, fsType) in self.siloImages.values():
	    if label == default:
		self.default = default
		return
	raise IndexError, "unknown silo label %s" % (default,)

    def getLinear(self):
	return self.siloLinear

    def getDevice(self):
	return self.siloDevice

    def getAppend(self):
	return self.siloAppend

    def __init__(self, serial = 0):
	self.siloImages = {}
	self.siloDevice = 'mbr'
	self.siloLinear = 1
	self.siloAppend = None
	self.default = None
	self.initrdsMade = {}
	self.serial = serial
	self.linuxAlias = 1
	self.bootDevice = 1
