The ADP Anaconda Patches, 7/9/2009 Edition
$Id: README /main/10 2009/07/10 00:12:59 alfords Exp $ Copyright (c) 2001-2009 Automatic Data Processing Inc.
NOTICE:
This is a README which describes a series of anaconda patches which ADP is redistributing.
We are publishing this software to fulfill the requirements of the GPL. This software is offered with NO WARRANTY and NO SUPPORT of any kind.
If you are reading this and you are not an ADP employee, see subsection 1.2, "Licensing, Intended Audience, and a Note for Non-ADP Readers", below.
CHANGELOG:
6/30/2009
Completely rewrote this README from a previous version. I deleted a lot of extraneous information, including syslinux changes, and some suggestions for how to debug anaconda. The syslinux changes are described in a separate posting I made to the syslinux mailing list. Other features or debugging techniques are described in documentation from RedHat. I also included information specific to individual patches with those specific patches, rather than having all that documentation here. That makes this README much shorter. Since it's shorter, people might actually read it. Since this is a complete rewrite, I restarted the CHANGELOG.
TABLE OF CONTENTS:
1. INTRODUCTION 1.1 What is this? 1.2 Licensing, Intended Audience, and a Note for Non-ADP Readers 1.3 What ADP Needed versus Anaconda's Capabilities
2. HELPFUL RESOURCES 2.1 What to Read 2.2 Preparing for Anaconda Development
1. INTRODUCTION
1.1 What is this?
Some years ago, ADP modified the anaconda which came with RedHat 7.0, and, later, RedHat 8.0. To comply with the GPL, we redistributed changes that we made to those versions of anaconda. To explain what we did, we wrote a large README which we posted with the modifications.
ADP has now modified the anaconda which came with CentOS 5.3. The newer version of anaconda is more powerful than the older versions. The newer anaconda is much closer to what ADP needs. But, we still had to modify anaconda itself to get it to do what we wanted.
We're now sharing those modifications. That's so that:
1. We fulfill the requirements of the GPL 2. Our modifications might be picked up and included in the base code. That way we don't have to make the same modifications again, later.
Also, rather than produce a single large README, I split up the information between this smaller README and each patch.
1.2 Licensing, Intended Audience, and a Note for Non-ADP Readers
An intended audience for this README are developers at ADP, who might have to further modify anaconda in the future. Note that I said "might". ADP makes no promise that ADP will make any further modifications to this software. As I said above, this software is offered with NO WARRANTY and NO SUPPORT of any kind. A secondary intended audience for this README are people who are not at ADP.
We could have written two versions of this README file: one for ADP readers, and one for non-ADP readers. But, we did not want to have to maintain internal and external versions of this README and/or the accompanying software. Hence these patches may contain some references to non-GPL'd software, or ADP's build environment, which are "independent and separate works" under section 2 of the GPL, and therefore not covered by the GPL. THEREFORE, WE ARE NOT PUBLISHING ANYTHING ELSE UNDER THE GPL, INCLUDING BUT NOT LIMITED TO:
* files which do not appear in the patches, * ADP's proprietary backup and restore software, * our build environment, * other ADP proprietary software referenced or included by this README, including but not limited to DSDA.
That means:
* this README may contain some references which won't make sense to non-ADP readers. These include, but are not limited to, our build environment, and build process. We don't provide explanation for these references, either. * other parts of anaconda, as modified, may also be non-functional. * some readers may find themselves saying, "yes, I know about that already." Other readers may say, "I don't understand that reference, it must be an ADP-ism."
1.3 What ADP Needed versus Anaconda's Capabilities
For ADP's purposes, anaconda, even with a customized kickstart file, lacked several features, including:
* simplified messages for the user * completely automatic partitioning on a variety of disk configurations, and support for some specific-to-ADP disk partitioning requirements * support for tape restore * --userinput flag to pre and post install scripts * -s flag passed to pre and post install scripts when running on a serial console * support for a restore install type
2. HELPFUL RESOURCES
2.1 What to Read
Here is an incomplete list of resources that I think will help you in learning how to modify anaconda and kickstart:
* Beazley's _Python Essential Reference_. Remember, anaconda is written in Python. Check which version of Python comes with your version of anaconda. Make sure that your version of Beazley addresses a matching version of Python. Otherwise, you may find yourself trying to take advantage of a new Python feature that doesn't exist in your environment. Or, you might find you are puzzled by a Python feature in one of the anaconda sources that Beazley does not explain.
* The RedHat Enterprise Linux Installation Guide, available at http://www.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/5/html/Installatio...
* The kickstart-list and anaconda-dev mailing lists. You can join at www.redhat.com. The list archives are located there, too.
* Most importantly, read Chris Lumens' pykickstart documentation. You should be able to get this by installing the pykickstart rpm then looking in /usr/share/doc/pykickstart*/programmers-guide .
2.2 Preparing for Anaconda Development
Install at least the python, python-devel, pykickstart, anaconda, and anaconda-runtime packages and their dependencies on your Linux development system.
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
$Id: patch1.in /main/4 2009/07/10 00:35:49 alfords Exp $ Copyright 2009 Automatic Data Processing, Inc.
See the README file in the first patch of this series for important details.
adppre.py is a Python script. It queries the system through the anaconda libraries, queries the user, and determines partition information, including "mass storage" disks. It outputs the partition information in a file. That file is later included by our kickstart scripts. adppre.py lets ADP customize disk partitioning based on what's on a particular system. See adppre.py itself for more details.
I'm not sure that adppre.py is useful to anyone but ADP. It does illustrate using the snack widgets. It's LGPL because it was originally based, way back when, on snack widget code which RedHat provided which was LGPL.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----adppre.py follows---- #!/usr/bin/python # $Id: adppre.py /main/79 2009/07/01 21:51:47 alfords Exp $ # # Copyright (c) 2001-2009 Automatic Data Processing Inc. Since this code # is based on code which we originally obtained from RedHat under the GNU # library public license, this code 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. # # We are publishing this software as part of fulfilling our requirements # under the GPL. This software is offered with NO WARRANTY and NO SUPPORT # of any kind. # # This script examines: # # * the hard drive dictionary defined in the isys module, # * the /var/log/dmesg file, # * and /proc/scsi. # # Based on what it finds, and the flags with which it was invoked, the # script may ask the user questions about: # # * how much additional swap space to reserve # * which disk(s) to reserve for mass storage # # "Mass storage" is an ADP-specific term which describes a disk with a # single partition that takes up the entire disk. Having disk storage # organized in this way is useful because, among other uses, it allows ADP # or ADP clients a means to have some disks backed up on a different # schedule, or have some disk storage be included in a SAN in a data # center, etc. # # Note that a mass storage "disk" can be a logical disk which is actually # part of a hardware RAID array. # # There can be more than one mass storage disk. Mass storage disks appear # in the filesystem under /msN, where N is an integer. The user must leave # at least one disk as non-mass storage so that there will be a place to # put /, /usr, /var, and /adp. If there is only one disk on the system, # or, if hardware RAID makes multiple disks appear as one disk, then the # user cannot allocate any disks as mass storage. # # One possible way to use mass storage disks is to save time during a # system restore. If the mass storage disks survived a system crash, then # they do not need to be restored from tape. To accommodate re-using mass # storage or virtual server disks, the user can specify that the disk will # not be "initialized" (ADP speak for having mke2fs run on it.) However, # this introduces the possibility for conflicting mass storage mount # points, e.g., two /ms3's, if the user tries to initialize a new disk and # then preserve an existing disk. MSD attempts to detect and prevent these # situations. # # The script outputs a file for inclusion in the kickstart pre script. The # file contains kickstart directives which encapsulate the user's answers. # # This script uses several standard python modules as well as some of the # anaconda and the snack modules. The comments in this file assume you # know something about anaconda, snack and/or newt. # # I could have patched this script into anaconda itself. By making it a # separate script, though, I can debug it on a system in multi-user mode. # # Originally, this file was created with tabstops=4. Later, I used # "expand -t4" to replace tabs with spaces. #
# Stolen from the latest anaconda main script, March, 2009: # Set up logging as early as possible. import logging from anaconda_log import logger, logLevelMap
log = logging.getLogger("anaconda") stdoutLog = logging.getLogger("anaconda.stdout")
import optparse import isys from snack import *
# I am unsure if all these imports need to be present, --setha 3/4/2009 import re import sys import time import parted import partedUtils import os import copy import string
# Prompts user for possible additional swap space. # # Parameters are the amount of memory detected on the system and # the swap currently allocated. # def askAboutSwap(screen, curr_mem, curr_swap): swap_string = ( "Detected memory is " + str(curr_mem) + " MB. " + "Default swap allocation is " + str(curr_swap) + " MB. ") ok_string = "Is this OK?" rc = ButtonChoiceWindow(screen, 'Swap Allocation', swap_string + ok_string, buttons = [ ("Yes"), ("No") ], width = 65) if( rc == "yes" ): return curr_swap
swap_string += ('Please input the total swap space you want. ' + 'You might want more swap on a system with less memory. ' + 'You might want less swap on a system with more memory. ' + '\n\nNote: in previous versions of this installer, ' + 'you entered ADDITIONAL swap. Now, please just enter ' + 'the TOTAL swap that you want.')
text = TextboxReflowed( 50, swap_string )
total_swap = 0 rc = ""
entry = Entry(6, "") blankLabel = Label(" ") grid = GridForm( screen, "Swap Allocation", 1, 5) grid.add(text, 0, 0) grid.add(blankLabel, 0, 1) grid.add(entry, 0, 2) grid.add(blankLabel, 0, 3) grid.add(Button("Ok"), 0, 4) done = 0
# Keep looping until they say "Ok" and they give us a reasonable # value. while( done == 0 ): done = 1 entry.set("") grid.run() try: total_swap = int(entry.value()) except: done = 0 continue if( total_swap < 0 ): done = 0 total_swap = 0 continue
rc = ButtonChoiceWindow( screen, 'Total Swap', 'Total swap space is now ' + str(total_swap) + ' MB.', buttons = [ ("Ok"), ("Back")] ) if( rc == "back" ): done = 0
return total_swap
# Puts up a default "Please wait..." window in case all the other windows # disappear. Usually will hide under the other windows. Caller initializes # SnackScreen. def pleaseWait(screen): l = Label("Please wait...") grid = GridForm(screen, "", 2,3) grid.add(l, 0, 1) grid.draw()
# # Fabricates a C number (which will be overwritten later on in the install) #
def getCNumber(screen): try: hostname_file = open("/tmp/staging", "w") except: print "Internal error: could not open /tmp/staging for writing, giving up" sys.exit(-1) hostname_file.write("hostname=C000000\n") hostname_file.close() return
# # getDSD creates the disk size dictionary. This is a modified version # of /usr/lib/anaconda/list-harddrives-stub
def getDSD(hdd):
dsd={}
for drive in hdd.keys(): # Insert default value for dsd entry sizeGb_string="N/A" cyl = 0 dsd[drive] = ( sizeGb_string, cyl )
if not isys.mediaPresent(drive): continue
# try to open and get size skip = 0 deviceFile = "/tmp/%s" % (drive,) isys.makeDevInode(drive, deviceFile) try: dev = parted.PedDevice.get(deviceFile) except: skip = 1 os.remove(deviceFile)
if skip: pass # dictionary already has default values else: sizeGb = (float(dev.heads * dev.cylinders * dev.sectors) / (1024 * 1024 * 1024) * dev.sector_size) sizeGb_string=("%4.0f") % ( sizeGb ) cyl = dev.cylinders dsd[drive] = ( sizeGb_string, cyl ) return dsd
class MSD:
# The MSD class manages snack widgets. The user interacts with the # widgets to determine which disks will become mass storage disks. # Formerly, MSD also allowed the user to choose a disk to allocate to a # virtual machine. That feature was dropped since we no longer support # on-site VM's. Hypothetical future on-site VM support will not be # tied to a separate disk. I tried to delete references to virtual # server disks, VM disks, or virtual machines. #
# makeLabelString is a helper function for init. Here are the # parameters and what it does with them: # # cols number of columns in the table which are displayed. # Columns greater than cols are not displayed. # col_widths list of column widths, should have cols entries # col_labels list of labels for those columns, should have cols # entries # col_pad number of spaces to pad between columns. This is just # an integer, not an array. # # makeLabelString assembles the col_labels into a string with the # proper spacing, based on col_widths and col_pad. Returns that # string. The string is suitable for use as a header above the # CListBox widget.
def makeLabelString(self, cols, col_widths, col_labels, col_pad): i = 0 the_label="" while i < cols: this_width=col_widths[i] + col_pad this_string="%-*.*s" % (col_widths[i]+col_pad, col_widths[i], col_labels[i]) the_label=the_label + this_string i += 1 return the_label
# MSD init asks the user whether to reserve any of the disks for mass # storage. The caller is responsible for retrieving the isys.HardDrive # dictionary and passing it in the hdd parameter. The caller is # responsible for calling getDSD, which creates the disk size # dictionary, which gets passed in as dsd. getMSD assumes that the # caller has determined that there is more than one disk. # # MSD init creates the self.msd dictionary. The new dictionary uses # disk drives as keys. The values of the corresponding dictionary # entries are lists. The lists contain: # # Index: Description: # 0 "[M]" if disk is allocated for mass storage # "[ ]" if disk is not allocated for mass storage # 1 "[*]" disk should be initialized # "[ ]" disk should not be initialized (but note that # all non-mass storage disks will be initialized anyway.) # 2 Device name. E.g., "sda". Redundant with the key for the # row. Has to be duplicated so that the information can be # displayed in CListbox. # 3 Size, in Gbyte. # 4 abbreviated disk controller information # 5 abbreviated existing labels on the disk # 6 New mount point, only filled in for mass storage disks. # 7 Full controller information(a) # 8 the label dictionary for this disk.(a) # # (a) only displayed in the Info sub-screen. # # MSD init is probably far too large. # # Note also that MSD init, and class MSD in general, does not support # migrating disks from one system to another. That is, disks can be # initialized, or continue to be used, on the same system. But MSD # does not support randomly moving mass storage between one system and # another. Doing so would potentially mean renaming mass storage # partitions to eliminate conflicting mount points. #
def __init__(self, screen, hdd, dsd): self.screen=screen self.msd = {} rc = ButtonChoiceWindow(self.screen, 'Multiple Disks Detected', 'Note: if you were not expecting to see ' + 'multiple disks on this system, maybe someone ' + 'left a thumb drive (also known as a flash drive, '+ 'memory stick, or USB drive) or other removable ' + 'disk installed. In that case, please remove ' + 'the extra drive(s) and/or media, reset the ' + 'system, and try again.' + '\n\nDo you want to reserve any disk(s) for Mass Storage? ', buttons = [ ("Yes"), ("No") ], width = 65)
# If they do not want any mass storage, then just return an empty # msd dictionary if ( rc == "no" ): return #NOTREACHED
#ASSERT: rc=="yes"
# (Re-)create a new grid try: del grid except: pass
# grid is a snack GridFormHelp widget. grid will hold other # widgets, including a CListbox, labels, buttons. Most of the user # interaction with MSD will be through grid. grid = GridFormHelp(self.screen, "Choose Mass Storage Disks", "", 60, 40)
# Set up various constants used in the display col_widths=[5,4,4,4,25,11,9] col_pad=1 # 2 originally width=75 cols=7 row_align=[LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT]
# Set up a line of text which tells the user what to do t = TextboxReflowed(width-5, "Please choose which disks, if any, you want to use as mass storage (M) devices. WARNING: ALL DISKS WHICH ARE NOT MASS STORAGE WILL BE INITIALIZED, INCLUDING THUMB DRIVES OR REMOVABLE MEDIA.") grid.add(t, 0, 0, (0,0,0,1))
# Use makeLabelString multiple times to create a multi-line header # for the CListbox. the_label_string = self.makeLabelString(cols=cols, col_widths=col_widths, col_labels=[('MS'), ('Init'), ('Disk'), ('Size'), ('Description'), ('All'), ('New MS')], col_pad=col_pad) l=Label(the_label_string) grid.add(l, 0, 1, (0,0,0,0), anchorLeft=1)
the_label_string = self.makeLabelString(cols=cols, col_widths=col_widths, col_labels=[('Dev?'), (' ?'), ('Dev'), (' Gb'), (''), ('existing'), ('labels') ], col_pad=col_pad) l=Label(the_label_string) grid.add(l, 0, 2, (0,0,0,0), anchorLeft=1)
# Finally, set up the CListbox itself. Use empty strings for # column labels that are only 2 rows long and which are already # displayed in the previous rows. clb = CListbox(height=7,cols=7, col_widths=col_widths, scroll=1, returnExit = 1, width=width, col_pad=col_pad, col_labels=[(''), (''), ('Name'), (''), (''), ('labels'), ('only')], col_label_align=row_align)
# Retrieve the disk partition label information
ds = partedUtils.DiskSet() ds.refreshDevices()
# Set up the initial row information to display in the CListbox. # Copy it from the various dictionaries. At the same time, also # initialize entries in the msd that will not be displayed in # CListbox.
for i in hdd.keys(): self.checkbox = r"[ ]" if dsd[i][0]=="N/A": self.checkbox = "N/A"
full_all_part_labels="" label2node = {} if ds.disks.has_key(i): pedparts=partedUtils.get_all_partitions(ds.disks[i]) else: pedparts=[] for part in pedparts: node=partedUtils.get_partition_name(part) this_part_label = isys.readExt2Label(node) if this_part_label: label2node[this_part_label] = node
full_all_part_labels = string.join(label2node.keys()) if len(full_all_part_labels) > col_widths[5]: all_part_labels = "%.*s..." % (col_widths[5]-3, full_all_part_labels) else: all_part_labels = full_all_part_labels
full_controller_info = hdd[i] if len(full_controller_info) > col_widths[4]: controller_info = "%.*s..." % (col_widths[4]-3, full_controller_info) else: controller_info = full_controller_info
new_labels = "" new_row=[self.checkbox, self.checkbox, i, list(dsd[i])[0], controller_info, all_part_labels, new_labels, full_controller_info, label2node]
# Add the new row to BOTH the self.msd AND to the CListbox # array. self.msd[i]=new_row clb.append(new_row, i, row_align)
# Finally, add the CListbox to the grid grid.add(clb,0, 3, (0,0,0,0), anchorLeft=1)
# Set up the button bar which will go along the button of the grid bb = ButtonBar(self.screen, (("Add", "add"), ("Remove", "remove"), ("Init", "initialize"), ("Info", "info"), ("Ok", "ok"), ("Help", "help")))
grid.add(bb, 0, 4, (0,1,0,0))
# Now, run the grid. The user will interact with the CListbox and # the ButtonBar. The loop will only exit when they press "Ok", and # they have specified a valid disk configuration.
# Start of the big while loop in msd.__init__ while 1: res=grid.run()
# Figure out which button they pressed. the_button=bb.buttonPressed(res) the_row = clb.current()
# Now do something based on which button they pressed. if the_button == "ok" or the_button==None: if self.len() >= len(self.msd): # They reserved all the disks for mass # storage or virtual disk rc = ButtonChoiceWindow(self.screen, 'Disk Selection Error', 'You cannot reserve all the disks for mass storage. You must have at least 1 disk for regular system use.', buttons = [ ("Ok") ] ) continue # the big while loop in msd.__init__ #NOTREACHED
# ASSERT: they reserved some of the disks for mass storage # and left a few for other uses. Have them verify their # choice. mass_reserved_disks = "(none.)" for d in self.msd: if self.is_ms(d): if mass_reserved_disks == "(none.)": mass_reserved_disks = d + "." else: mass_reserved_disks = d + ", " + mass_reserved_disks
rc = ButtonChoiceWindow(self.screen, 'Are you sure?', 'You reserved the following disks for mass storage: ' + mass_reserved_disks + '\n\nIs this correct?', buttons = [ ("Ok"), ("Go back") ], width=60 ) if rc == "ok": self.screen.popWindow() return # end of the_button = "ok" elif the_button == "add": if self.msd[the_row][0] == "N/A": rc = ButtonChoiceWindow(self.screen, 'Device Unavailable', 'This device is unavailable for use for mass storage.', buttons = [ ("Ok") ], width=65) continue # the big while loop in msd.__init__ #NOTREACHED
# Things to do now: # * See if the disk has a previous msN label. # * If it does, see if the disk could be mounted again # using the same msN label. Test if there is another msN # already allocated. If so, offer the user to init this # disk, and use the next available msN. Or, offer the user # the option to remove the other disk. If not, allocate # msN to this disk, don't init. # * No previous msN label? Then just allocate this disk # with the next available msN. and mark the disk for # initialization.
# Is there an msN label on this disk? if re.match("^/ms[1-9]\d*$", self.msd[the_row][5]):
# Is that label also in use elsewhere? other_row = self.existMountByRow(the_row)
if other_row: # There already is a disk which has this particular # msN allocated. Generate an error and suggested # courses of action. rc = ButtonChoiceWindow(self.screen, "Conflicting Mass Storage Mount Points", 'This disk, ' + the_row + ', was previously used for ' + 'mass storage, with label ' + self.msd[the_row][5] + '. ' + 'I want to re-use that label ' + 'on this disk. I would do that, ' + 'except ' + self.msd[the_row][5] + ' is already allocated to ' + 'another disk, ' + other_row + '. ' + 'You can either initialize ' + the_row + ' and I will give ' + the_row + ' a new label ' + '(press Init, below.) ' + 'If you initialize, though, you ' + 'will lose all ' + 'existing data on ' + the_row + '. Or, you can press ' + 'the Cancel button, then Remove ' + other_row + ' as a mass storage disk, then ' + 'try again to add ' + the_row + ' as a mass storage disk. ' + 'Press Help for more information.', buttons = [ ("Init"), ("Cancel"), ("Help") ], width=65)
# Do something based on the button they pressed in # this sub-screen. if rc == "init": self.msd[the_row][0] = "[M]" self.msd[the_row][1] = "[*]" self.msd[the_row][6] = self.nextMount(the_row, 'ms') # elif rc == "cancel": # self.msd[the_row][0] = "[ ]" # self.msd[the_row][1] = "[ ]"
elif rc == "help": rc=ButtonChoiceWindow(self.screen, "Help for Conflicting Mass Storage Mount Points", 'If a disk ' + 'has only one pre-existing label of ' + 'the form msN (N ' + 'is a number greater ' + 'than 0,) and there is not ' + 'already another msN allocated, ' + 'you can skip initialization, ' + 'and re-use ' + 'that disk for mass storage. ' + 'Skipping initialization means ' + 'you can skip a restore, and ' + 'maybe save some time. But, if ' + 'there already is another msN ' + 'allocated, you cannot create ' + 'a second msN.\n\n' + 'Suggestion: first add the ms ' + 'disks which are NOT to be ' + 'initialized, then add ' + 'the ms disks ' + 'which are to be initialized.', buttons = [ ("Ok") ], width=65) else: # The previous msN on this disk has not # been allocated elsewhere. self.msd[the_row][6] = self.msd[the_row][5] self.msd[the_row][0] = "[M]"
else: # No previous msN on this disk; so initialize this disk # and give it the next available msN self.msd[the_row][0] = "[M]" self.msd[the_row][1] = "[*]" self.msd[the_row][6] = self.nextMount(the_row, 'ms') # end of the button == add branch
# If it is ever needed to support disk migration, the "rename" # branch should go here. The help messages will also have to # be appropriately modified.
elif the_button == "remove": self.msd[the_row][0] = "[ ]" self.msd[the_row][1] = "[ ]" self.msd[the_row][6] = "" # end of button = "remove" branch
elif the_button == "initialize": # Check that the current disk is a mass storage first. if self.is_ms(the_row): # it is part of mass storage. See if it is marked for init if self.msd[the_row][1] == "[ ]": self.msd[the_row][1] = "[*]" else: # They may be trying to illegally skip # initialization. Give an error message and # suggest what to do next. rc=ButtonChoiceWindow(self.screen, 'Not Initializing a MS Disk', 'This disk has been allocated as a mass storage disk, and is already marked for initialization. If that is what you want, then press Ok, below.\n\nOn the other hand, are you trying to skip initialization for this disk? In that case, was this disk used as a mass storage disk previously?\n\nYES:\n\nPress Remove. Then press Add. But skip pressing Init.\n\nNO:\n\nThe only way to skip initialization of a mass storage disk is if the disk was used as a mass storage disk previously. If the disk was used as a mass storage disk previously, then there will be only one label of the form /msN, where N is greater than 0. So, if there is more than one label, or a label not of the form /msN, or N is 0, then the installation software will insist on initializing the disk.', buttons = [ ("Ok") ], width=65) else: # if self.is.ms(the_row): rc=ButtonChoiceWindow(self.screen, 'Disk not Allocated for Mass Storage or Virtual Server', 'First you have to include ' + the_row + ' for use as a mass storage disk, then you can Init it.', buttons = [ ("Ok") ] ) # end of button = "initialize" branch
elif the_button == "info": # Show the user some information about the disk. rc=ButtonChoiceWindow(self.screen, 'Information about %s' % (the_row), 'Allocated for use as a\n' + 'mass storage [M]? ' + self.msd[the_row][0] + '\n' + 'Will be initialized? ' + self.msd[the_row][1] + '\n' + 'Device file: /dev/' + the_row + '\n' + 'Size: ' + '%d' % string.atoi(self.msd[the_row][3]) + 'Gbyte\n\n' + 'Disk controller information: ' + self.msd[the_row][7] + '\n' + 'Existing labels (may be blank): ' + string.join(self.msd[the_row][8]) + '\n' + 'New mount point (may be blank): ' + self.msd[the_row][6], buttons = [ ("Ok") ], width=65) # end of button = "info" branch
elif the_button == "help": # I split help up into 3 sub-screens. Yes, it would be # simpler scripting to let them scroll one help screen. # But I was concerned that they might not know how, or be # able to scroll. Remember, they are reading this help at # 2AM during a tape restore. show_help=1 while show_help: if show_help==1: rc=ButtonChoiceWindow(self.screen, 'Allocating Mass Storage Disks Help, part 1', 'You can allocate zero or more mass ' + 'storage disks on the system. Mass storage ' + 'disks each contain a single partition. ' + 'Mass storage partitions get labels of ' + 'the form msN, where N is a number greater ' + 'than 0, such as ms1, ms2, ... ms7, etc. ' + 'Each msN partition gets mounted under ' + '/msN. There can only be one particular ' + 'msN partition on the system. That is, you ' + 'cannot have two ms3 partitions, for ' + 'example. By default, the load software ' + 'does NOT allocate any mass storage ' + 'partitions. You HAVE to specify which ' + 'partitions will be used for mass storage, ' + 'if any.', buttons = [ ("More"), ("Cancel") ], width=60) if rc=="cancel": show_help=0 continue #NOTREACHED show_help=2 continue #NOTREACHED elif show_help==2: rc=ButtonChoiceWindow(self.screen, 'Allocating Mass Storage Disks Help, part 2', 'If you are restoring a system, and you are adding a new disk for use as a mass storage disk, you should choose to also initialize the disk.\n\nIf you are restoring a system, and you have a disk that was used for mass storage previously, and if you know FOR SURE that the disk still has valid data, you can choose to not initialize that disk, avoid reading another save tape, and save some time. The load software will re-use the existing mass storage disk partition label on that disk. So, if the disk previously held /ms1, for example, it will still hold /ms1.', buttons = [ ("More"), ("Back"), ("Cancel") ], width=60) if rc=="cancel": show_help=0 continue #NOTREACHED elif rc=="back": show_help=1 continue #NOTREACHED show_help=3 continue #NOTREACHED elif show_help==3: rc=ButtonChoiceWindow(self.screen, 'Allocating Mass Storage Disks Help, part 3', 'If you are restoring, and you are both keeping previously used mass storage disks, and adding new disks, first allocate the previously used disks. Then allocate the new disks. That way you will avoid a new disk trying to use the same msN as an old disk.\n\nNote: If there is a vertical bar on the right, it means that there is additional text in the message. Use the arrow keys to scroll down so you can see that additional text.', buttons = [ ("Done"), ("Back") ], width=60) if rc=="done": show_help=0 continue #NOTREACHED show_help=2 continue #NOTREACHED else: self.internal_error("Internal error: show_help has invalid value %d" % (show_help)) #NOTREACHED # end of the_button = "help" branch else: self.internal_error("Internal error: invalid value for the_button: %s, giving up" % (the_button)) #NOTREACHED
# Since they possibly modified the data in the current row in # self.msd, replace the corresponding row in the CListbox. clb.replace(self.msd[the_row], the_row, row_align) clb.setCurrent(the_row) grid.setCurrent(clb.listbox) grid.draw()
# # msd2part_command outputs the part command from an entry from the MSD. # The output can be used in a kickstart file. Parameter the_key is the # name of one of the disks. # def msd2part_command(self, the_key): if self.msd=={}: # Nothing in the dictionary, nothing to do, return. return "" if not self.msd.has_key(the_key): self.internal_error("Internal error, msd2part_command given invalid key, giving up") #NOTREACHED
# If they did not want mass storage on this disk, then there is # nothing to write to the kickstart file. Return an empty string. if not self.is_ms(the_key): # The disk is not part of mass storage and not used by a virtual server return "" #NOTREACHED
# ASSERT: this disk is part of mass storage
# Specify ext2 the_fstype = " --fstype ext2"
# Do they want to initialize it? if self.msd[the_key][1] == "[ ]": # No, they do not want it initialized partname = self.msd[the_key][5] label2node = self.msd[the_key][8] mountpoint = label2node[partname] return "part " + partname + the_fstype + " --noformat --onpart=" + mountpoint + "\n" #NOTREACHED
# ASSERT: disk is for use by mass storage They do want to # initialize it.
# There is a tuple stored at dsd[i]. The tuple should have # 2 elements: size in gbyte and number of cylinders. # Previously, I used the number of cylinders in an --end # option of a part directive for a mass storage partition. # However, I have since changed the directive to use a # --size=1 --grow, and to allocate all partitions to a # specific disk. The end cylinder is still at dsd[i][1:] , # in case I decide to go back to using --end, as in: # return "partition " + self.msd[the_key][6] + " --start=1 --end=%d " % dsd[i][1:] + "--ondisk=" + i + "\n"
# Now I return the part command which uses --grow: return "part " + self.msd[the_key][6] + the_fstype + " --size=1 --grow --ondisk=" + the_key + "\n" #
# msd2perm_command outputs the permissions command for the ks.perm # file. This command means that the mass storage disk will get # allocated with DSDA's desired permissions. # def msd2perm_command(self, the_key): if self.msd=={}: # Nothing in the dictionary, nothing to do, return. return "" if not self.msd.has_key(the_key): self.internal_error("Internal error, msd2perm_command given invalid key, giving up") #NOTREACHED
# If they did not want to include this disk in mass storage, then # there is nothing to write to the ks.perm file. Return an empty # string. if not self.is_ms(the_key): # The disk is not part of mass storage return "" #NOTREACHED
# ASSERT: this disk is part of mass storage. partname = self.msd[the_key][6]
return "chmod 775 %s; chown adp.clients %s; umount %s; chmod 700 %s; chown root.root %s; mount %s\n" % ( partname, partname, partname, partname, partname, partname)
# # getClearpart tells the caller whether the partition should be # cleared. It returns 1 for yes, 0 for no. The parameter is the # name of a disk. # def getClearpart(self, the_key): if self.msd=={}: # Nothing in the dictionary, nothing to do, return. return 0 if not self.msd.has_key(the_key): self.internal_error("Internal error, getClearpart given invalid key, giving up") # in case it gets past internal_error return 0 #NOTREACHED
# If they did not want mass storage to use this disk, then # they do want it clearparted. if not self.is_ms(the_key): return 1 #NOTREACHED
# ASSERT: mass storage use this disk. # Do they want to initialize it? if self.msd[the_key][1] == "[*]": # Yes, they want to initialize it. return 1 #NOTREACHED
# ASSERT: mass storage use this disk. # They do not want to initialize it. return 0 #NOTREACHED
# # Tell if a disk is reserved for mass storage. # def is_ms(self, key): return self.msd[key][0] == "[M]"
# # How many disks are reserved for mass storage? def len(self): i = 0 for key in self.msd: if self.is_ms(key): i += 1 return i
# # Determine the next available mass storage mount. Only looks at the # mount points that are actually allocated in the 6th column of # self.msd. Does not worry about mount points that might want to be # allocated in the 5th column. The design intends that the user will # allocate the disks that s/he wants to re-use first, then allocate the # new disks. That way the new disks will just use the next available # msN, and old disks will be able to re-use a previous msN, for various # values of N. I did try doing something fancier in which nextMount # tried to recognize the 5th column in msd. But it got too confusing # and I gave up on it. # # Before you implement a fancier automatic msN assignment system, # answer the question: what happens if they salvage 2 disks from # another system, and they both have ms2 labels? #
def nextMount(self, the_row, label_letters): m = 1 mount_point_prefix = "/" + label_letters while m < 1000: mount_point = mount_point_prefix + "%d" % (m) flag = 0 for key in self.msd: if key == the_row: continue # NOTREACHED # If another disk already wants to use a particular msN, # then set the flag. Figure out what to do next, shortly. if self.msd[key][6] == mount_point: flag = 1 break # out of the "for key in self.msd" loop
if flag!=0: # Someone wants to use the msN in mount_point. Try # the next one. m += 1 continue # continues the "while m < 1000" loop # NOTREACHED
# If we get here, we found an unused msN. Tell the caller. return mount_point # NOTREACHED
# if we get here then things are seriously broken. We tried 1000 # mount points and could find one that worked. Time to give up. self.internal_error("Could not allocate a mount point, tried up to %s, giving up." % (mount_point)) #NOTREACHED
# See if a proposed mount point is Ok. # # The caller passes the_row, a key of a row that is already in the msd # list. existMountByRow assumes that column 5 of the_row has a # proposed mount point. existMountByRow checks if that mount point has # been allocated elsewhere in the msd list. "Allocated" means someone # has put that mount point in column 6 of another row. # # existMountByRow does not bother checking column 6 of the_row. That # way the caller can provisionally allocate a mount point for a row, # and then ask if that mount point has been allocated elsewhere. That # might seem silly, but it's useful for the case where the user adds # the same disk twice to mass storage. # # Returns the key of the other row which has the mount point already # allocated. Or returns None if no such row exists.
def existMountByRow(self, the_row): for key in self.msd: if key == the_row: # skip the current row continue if self.msd[key][6] == self.msd[the_row][5]: return key # if we get here then the mount point is not already allocated. return None
# See if a proposed mount point is Ok. # # The caller passes the_name, a mount point the caller wants to use. # existMountByName checks if that mount point has been allocated # elsewhere in the msd list. "Allocated" means someone has put that # mount point in column 6 in a row. # # Returns the key of the row which has the mount point already # allocated. Or returns None if no such row exists. # # The caller must handle the situation of a user trying to re-reserve a # disk, two or more times. In that case, existMountByName will return # a non-None value the second time it is called. That non-None value # will be the row of the disk the user previously reserved.
def existMountByName(self, the_name): for key in self.msd: if self.msd[key][6] == the_name: return key # if we get here then the mount point is not already allocated. return None
# Yet another helper function. Turns off the snack screen, outputs the # error message in text, and exits. Used for handling internal # errrors. def internal_error(self, error_message): self.screen.finish() print error_message sys.exit(1) #NOTREACHED
# # Main processing starts here #
# Initialize a blank label variables blankLabel=Label(" ")
# Process command line arguments: p = optparse.OptionParser(description="Asks the user about swap space allocation and whether the system should reserve a disk for mass storage.", prog = "adppre.py", usage = "[ --swapmult N ] [ --adp restore|install] [ --output output_file ] [ --permfile permissions_file ] ", version = "$Revision: /main/79 $", )
p.add_option("--swapmult", default=2, type='int') p.add_option("--swapmax", default=16384, type='int') p.add_option("--adp", choices=["restore", "install"], default="install", type='choice') p.add_option("--output", default="/tmp/ks.adpparts", type='string') p.add_option("--permfile", default="/tmp/ks.perms", type='string')
(opt, extra) = p.parse_args()
swapmult = opt.swapmult swapmax = opt.swapmax adparg = opt.adp outfile = opt.output permfile = opt.permfile
# Open the kickstart output file, as specified in the outfile variable. # Leave the file open for the rest of the script. That way, adppre.py can # write debugging information as comments to the output file as adppre.py # runs. # try: ks_adpparts = open(outfile, "w") except: print "Internal error: could not open " + outfile + " for writing, giving up" sys.exit(-1)
# Open the kickstart permissions file, as specified in the permfile # variable. Leave the file open for the rest of the script. That way, # adppre.py can write debugging information as comments to the output file # as adppre.py runs. # try: ks_perms = open(permfile, "w") except: print "Internal error: could not open " + permfile + " for writing, giving up" sys.exit(-1)
# Get e820 information, or if that does not work, look into iutil. try: import e820 meminfo = e820.e820Info('/tmp/syslog') memInstalled = meminfo.memtotal ks_adpparts.write ("# /proc/e820info memory size is %d\n" % memInstalled) except: import iutil memInstalled = iutil.memInstalled() / 1024 ks_adpparts.write ("# iutil.memInstalled() reports %dM\n" % memInstalled)
screen = SnackScreen() screen.pushHelpLine("<Tab> and arrows move around | <Space> de-/selects | <Enter> presses button")
# Put up the default message, which should hide under other windows. pleaseWait(screen)
# Ask the user for the C number, if we are not doing a tape restore. if (adparg == 'install'): getCNumber(screen)
# If there is more than 1 disk present, ask the user if s/he wants to # reserve 1 or more disks for mass storage. # # But I don't want to do _anything_ with removable media today, so before I # even check to see if there's more than one disk, weed out anything that # isys says has no media present. # # FYI, what I _used_ to do here is ask isys if the disk was removable, # regardless of whether it had a media inserted. Unfortunately, I have not # come up with a good way to handle thumb drives, aka memory sticks, aka # flash drives. # hdd = isys.hardDriveDict()
for disk in hdd.keys(): if not isys.mediaPresent(disk): ks_adpparts.write ("# %s has no media; don't use it\n" % disk) del hdd[disk]
num_disks = len(hdd) ks_adpparts.write ("# isys.hardDriveDict number of logical disks is %d\n" % num_disks)
# getDSD creates the disk size dictionary. dsd = getDSD(hdd)
# Initialize num_disks_for_swap to the total number of disks, at least by # default. num_disks_for_swap = len(hdd) msd_len = 0
# ### Uncomment one of the following: if(num_disks > 1): # the correct version # ### if(num_disks > 0): # the development version
# Initialize the mass storage dictionary, msd, using the MSD class. # msd=MSD(screen, hdd, dsd)
# Reduce the number of disks for swap by the number of disks that # will be used for mass storage. And update msd_len msd_len = msd.len() num_disks_for_swap -= msd_len
# Figure out the swap to allocate, and what spindles to put it on. def_swap = memInstalled * swapmult if( def_swap > swapmax ): def_swap = swapmax
final_swap = askAboutSwap( screen, memInstalled, def_swap )
# Spread out the final swap area size over the num_disks_for_swap each_swap_size = final_swap/num_disks_for_swap screen.finish()
# print "adppre.py: called screen.finish()"
ks_adpparts.write ("# each swap partition size is %d\n" % each_swap_size)
# For purposes of the volgroup directive, pesize is in kilobytes. # That is despite vgcreate treating numbers with no letter as megabytes. j = 0 vg="volgroup --pesize 32768 adp_vg "
# Create the clearpart_directive. First, set up a default directive # to clear all partitions. if msd_len > 0: clearpart_directive = "clearpart --all --initlabel --drives=" else: clearpart_directive = "clearpart --all --initlabel," # comma gets stripped below
# Create the string to hold the partition commands part_directives = ""
# adppre.py does not bother creating a string for writing information to # ks.perm. Rather, adppre.py just writes out the information that needs # to be written to ks.perm. That works because the ks.perm file is a shell # script, and not a kickstart file. If that changes, initialize that # string here. ks_perms.write("#!/bin/bash\n")
# Fill in details in clearpart_directive and part_directives. Once # the directives are complete, output them to the kickstart file. # for i in hdd: if msd_len > 0: if msd.getClearpart(i) == 1: clearpart_directive = clearpart_directive + i + "," this_part_directive = msd.msd2part_command(i) if this_part_directive != "": part_directives += this_part_directive this_part_perm = msd.msd2perm_command(i) ks_perms.write(this_part_perm + "\n") continue #NOTREACHED
# disk i is not mass storage, or there are no mass storage drives part_directives += "part pv.%.2d --size=1 --grow --ondisk=%s\n" % (j, i) part_directives += "part swap --size=%d --ondisk=%s\n" % (each_swap_size, i) this_pv = "pv.%.2d " % j vg = vg + this_pv j = j + 1
# Now allocate partitions that do not use up an entire disk. Modify this # list if you want to change the sizes of these partitions
system_parts=[['/boot', '128'], ['/', '2000'], ['/var', '4000'], ['/usr', '4000']]
if msd_len == 0: # No mass storage disks, allocate the partitions the regular way. for sp in system_parts: # Yes, you could combine the following 2 lines into a single line and # eliminate the use of the this_part_directive variable. But then you # would not have a place to set a breakpoint and a variable to examine. # this_part_directive = "part " + sp[0] + " --size=" + sp[1] + " --fstype ext2\n" part_directives += this_part_directive
else: # There are mass storage/virtual server disks. Allocate the system # partitions over the non-mass storage disks. This works because MSD # will not allocate all disks to mass storage. while system_parts != []: # iterate over all the hard disks on the system for i in hdd: # Skip this disk if it is in use by mass storage/virtual server if msd.is_ms(i): continue # NOTREACHED # ASSERT: this disk is not used for mass storage. Try to put # the first of the remaining system_parts on it. this_part_directive = "part " + system_parts[0][0] + " --size=" + system_parts[0][1] + " --fstype ext2 --ondisk=" + i + "\n" part_directives += this_part_directive del system_parts[0] if system_parts == []: break
# Strip off the trailing comma from the clearpart directive clearpart_directive = clearpart_directive[0:-1]
# First output the clearpart directive... ks_adpparts.write("%s\n\n" % clearpart_directive)
# then output the partition directives ks_adpparts.write("%s\n" % part_directives)
# and output the logical volume group directive which references the pv's ks_adpparts.write("%s\nlogvol /adp --percent 100 --fstype ext2 --name=adp_lv --vgname=adp_vg\n" % vg)
ks_adpparts.close() sys.exit(0)
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
On Friday, July 10 2009, Alford, Seth said:
adppre.py is a Python script. It queries the system through the anaconda libraries, queries the user, and determines partition information, including "mass storage" disks. It outputs the partition information in a file. That file is later included by our kickstart scripts. adppre.py lets ADP customize disk partitioning based on what's on a particular system. See adppre.py itself for more details.
I'm not sure that adppre.py is useful to anyone but ADP. It does illustrate using the snack widgets. It's LGPL because it was originally based, way back when, on snack widget code which RedHat provided which was LGPL.
This is cool and probably worth linking to the post from the anaconda wiki[1] or even attaching it there, but as you say, I'm not sure it's directly applicable or useful in the source tree.
Jeremy
$Id: patch2.in /main/3 2009/07/10 00:35:51 alfords Exp $ Copyright 2009 Automatic Data Processing Inc.
See the README file in the first patch of this series for important details.
We patched one of the Makefiles within anaconda so that re-making anaconda is quieter.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----anaconda-adp-wlite-depend.patch follows---- diff -Naur anaconda-11.1.2.113/wlite/GNUmakefile anaconda-11.1.2.113.adp/wlite/GNUmakefile --- anaconda-11.1.2.113/wlite/GNUmakefile 2008-04-16 02:34:50.000000000 -0700 +++ anaconda-11.1.2.113.adp/wlite/GNUmakefile 2009-01-09 10:16:30.000000000 -0800 @@ -135,3 +135,4 @@ $(RM) $(archives) TAGS tags $(wildcard core core.*) $(wildcard *.tmp) mostlyclean: $(RM) $(objects) +depend:
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
$Id: patch3.in /main/2 2009/07/10 00:35:52 alfords Exp $ Copyright 2009 Automatic Data Processing Inc.
See the README file in the first patch of this series for important details.
We had to modify the anaconda script itself to include the AnacondaKSParser module. We did these modifications based on Chris Lumens' "pykickstart Programmer's Guide", which currently lives in /usr/share/doc/pykickstart-0.43.3/programmers-guide and comes with pykickstart.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----anaconda.patch follows---- --- a_naconda-11.1.2.113.orig/anaconda 2009-03-10 10:47:41.000000000 -0700 +++ anaconda-11.1.2.113/anaconda 2009-03-10 10:49:17.000000000 -0700 @@ -251,6 +251,7 @@ from kickstart import VNCHandlers from pykickstart.data import KickstartData from pykickstart.parser import KickstartParser + from kickstart import AnacondaKSParser
global vncpassword, vncconnecthost, vncconnectport
@@ -269,7 +270,7 @@ # line options for password, connect host and port override values in # kickstart file vncksdata = KickstartData() - ksparser = KickstartParser(vncksdata, VNCHandlers(vncksdata), + ksparser = AnacondaKSParser(vncksdata, VNCHandlers(vncksdata), missingIncludeIsFatal=False) ksparser.readKickstart(opts.ksfile)
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
On Friday, July 10 2009, Alford, Seth said:
We had to modify the anaconda script itself to include the AnacondaKSParser module. We did these modifications based on Chris Lumens' "pykickstart Programmer's Guide", which currently lives in /usr/share/doc/pykickstart-0.43.3/programmers-guide and comes with pykickstart.
The current code in git master is very different here, so what are you trying to achieve with this so we can see if it's done or if it's something that's worth considering there
Jeremy
On Fri, Jul 10, 2009 at 02:53:28PM -0500, Jeremy Katz wrote:
On Friday, July 10 2009, Alford, Seth said:
We had to modify the anaconda script itself to include the AnacondaKSParser module. We did these modifications based on Chris Lumens' "pykickstart Programmer's Guide", which currently lives in /usr/share/doc/pykickstart-0.43.3/programmers-guide and comes with pykickstart.
The current code in git master is very different here, so what are you trying to achieve with this so we can see if it's done or if it's something that's worth considering there
Jeremy
I'm trying to add support for the --interactive and -s flags for pre/post install scripts.
To do that, I needed to modify either AnacondaKSParser in kickstart.py, or KickstartParser in /usr/lib/python2.4/site-packages/pykickstart/parser.py.
If I modified pykickstart.py, I have to rebuild another rpm package, pykickstart. If I modify kickstart.py, I have to rebuild anaconda, which I'm doing already. It seemed simpler to jus modify AnacondaKSParser.
--Seth
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
$Id: patch4.in /main/3 2009/07/10 00:35:54 alfords Exp $ Copyright 2009 Automatic Data Processing Inc.
See the README file in the first patch of this series for important details.
We had to modify the anaconda.spec file to get rpmbuild to rebuild anaconda with our additional patches.
We also had to keep the rebuilt anaconda package's version and release numbers correct. Currently, our rebuilt anaconda is named anaconda-11.1.2.168-1.el5.centos.adp2.i386.rpm.
If we don't, there's a chance our build will reference an anaconda package without our modifications.
So we have an input file for sed, anaconda.spec.patch.in, which creates an anaconda.spec.patch, which in turn gets applied to the anaconda.spec file.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----anaconda.spec.patch.in follows---- --- a_naconda.spec 2009-03-05 02:56:52.000000000 -0800 +++ anaconda.spec 2009-05-12 15:28:34.000000000 -0700 @@ -1,10 +1,11 @@ Name: anaconda -Version: 11.1.2.168 -Release: 1.el5.centos +Version: AvErSiOn +Release: ArElEaSe License: GPL Summary: Graphical system installer Group: Applications/System Source: anaconda-%{PACKAGE_VERSION}.tar.bz2 +Source2: anaconda-%{PACKAGE_VERSION}-adp.tar.gz BuildPreReq: kudzu-devel >= 1.2.57.1.18, pciutils-devel BuildPreReq: bzip2-devel, e2fsprogs-devel, python-devel, gtk2-devel BuildPreReq: rpm-python >= 4.2-0.61, newt-devel, rpm-devel, gettext >= 0.11 @@ -45,6 +46,11 @@ Patch8: anaconda-centos-splittree_allow_missing_RPMSdir.patch Patch9: anaconda-centos-pkgorder.patch Patch10: anaconda-centos-additional-repos.patch +Patch11: anaconda-adp-wlite-depend.patch +Patch12: kickstart.py.patch +Patch13: anaconda.patch +Patch14: complete_text.py.patch +Patch15: mknod-stub.patch
BuildRoot: %{_tmppath}/anaconda-%{PACKAGE_VERSION} @@ -81,6 +87,8 @@
%setup -q
+tar xzf %{SOURCE2} + %patch1 -p1 %patch2 -p1 %patch3 -p1 @@ -91,6 +99,11 @@ %patch8 -p1 #%patch9 -p1 %patch10 -p1 +%patch11 -p1 +%patch12 -p1 +%patch13 -p1 +%patch14 -p1 +%patch15 -p1
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
$Id: patch5.in /main/4 2009/07/10 00:35:55 alfords Exp $ Copyright 2009 Automatic Data Processing, Inc.
See the README file in the first patch of this series for important details.
To clarify when our users are supposed to eject the CD at the end of the install or restore process, I modified the message in complete_text.py. You can see it below. Or here it is formatted to make it easier for you to read:
If you have a "laptop" style CD-ROM drive with a non-motorized CD tray: press <Enter> to reboot your system. The system will eject the CD before rebooting.
If you have a CD-ROM with a fully motorized CD tray, or this is a virtual machine: first remove the CD-ROM, then press <Enter> to reboot your system.
Now, I hope, our users will know when to remove the CD and under what circumstances.
Even though the change is just to the text that the user sees, since complete_text.py is GPL'd, the GPL requires us to re-distrbute the change.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----complete_text.py.patch follows---- --- a_naconda-11.1.2.168/textw/complete_text.py 2008-12-18 02:21:19.000000000 -0800 +++ anaconda-11.1.2.168/textw/complete_text.py 2009-05-12 11:42:11.000000000 -0700 @@ -27,9 +27,13 @@ floppystr = _("Press <Enter> to end the installation process.\n\n") bottomstr = _("<Enter> to exit") else: - floppystr = _("Remove any media used during the installation " - "process and press <Enter> to reboot your system." - "\n\n") + floppystr = _("If you have a "laptop" style CD-ROM drive with " + "a non-motorized CD tray: press <Enter> to reboot " + "your system. The system will eject " + "the CD before rebooting.\n\nIf you have " + "a CD-ROM with a fully motorized CD tray, or this is a virtual machine: " + "first remove the CD-ROM, then press <Enter> to " + "reboot your system.\n\n") bottomstr = _("<Enter> to reboot")
screen.pushHelpLine (string.center(bottomstr, screen.width))
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
$Id: patch6.in /main/7 2009/07/10 16:36:05 alfords Exp $ Copyright 2009 Automatic Data Processing, Inc.
See the README file in the first patch of this series for important details.
e820.py is a Python script. It defines the e820Info class. The e820Info class reads the BIOS 15h/e820 memory map. We found that map to be a somewhat more reliable indicator of how much memory was actually on the system than /proc/meminfo. That map can can be found in /tmp/syslog while ramdisk Linux runs, or in /var/log/dmesg when regular Linux runs. The e820Info class can provide a sum of the memory reported found in the e820 map. The e820Info class recognizes the e820 map formats produced by the 2.4.* and 2.6* kernels with which we have tried it.
e820.py has not changed since 2002, since I distributed it the first time. For completeness, I'm sending it out again. The "COPYING" file it refers to is, of course, the file that holds the text of the GNU General Public License Version 2.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----e820.py follows---- #! /usr/bin/python # $Id: e820.py /main/8 2002/07/10 19:53:47 setha Exp $ # # Reads the e820 map near the start of the input file. # # Copyright (c) 2001-2002 Automatic Data Processing Inc. The distribution # terms are as noted in the file COPYING.
import sys import re import string
class e820Info:
# Get information about memory from the memory map printed # by the 15h/e820 BIOS call, which is presumed to be in the # file supplied in parameter file. That file is either one or # another form of syslog output; or /proc/e820info in later 2.4 # series kernels with e820 proc info enabled in .config. # # By default, this class assumes /proc/e820info. Pass a file # parameter to look in one or more of the syslog output files. def __init__(self, file="/proc/e820info"):
try: f=open(file, "r") except: print "Could not open", file sys.exit(1)
e820map=0 accum=0
# This class recognizes 4 forms of the e820 map. Forms # 1-3 are found in one of the syslog output files. # Form 4 is found only in /proc/e820info. # Form 1: # BIOS-e820: start - end (usable) # Form 2: # <4> BIOS-e820: size @ start (usable) # Form 3: # <4> BIOS-e820: start - end (usable) # Form 4, only in /proc/e820info: # size @ start (usable) # # I have only seen form 2 in the ramdisk environment # with the 2.4.2 kernel. Form 2 is therefore older. # I believe that syslog in the ramdisk environment # prepends the "<4>". We first saw form 3 in Martin's # PC with the 2.4.10 kernel.
if file == "/proc/e820info": e820map=4
while 1: l=f.readline() if not l: break fields=string.split(l)
if e820map==1 and fields[0] != "BIOS-e820:": # Done reading the map break
if (e820map==2 or e820map==3) and fields[1] != "BIOS-e820:": # Done reading the map break
if fields[0] == "BIOS-e820:" and e820map==0: e820map=1
if fields[0] == "<4>" and fields[1] == "BIOS-e820:" and e820map==0: if fields[3] == "@": e820map=2 elif fields[3] == "-": e820map=3 else: print "Unknown e820 log file format (form 2 or 3,) giving up" sys.exit(1)
if not e820map: continue
if e820map == 1 or e820map == 3: # Convert the type 3 into a type 1 if e820map == 3: fields=fields[1:] # Form 1 e820 map sanity check: if fields[2] != "-": print "Unknown e820 log file format (form 1 or 3,) giving up" sys.exit(1) start=string.atol(fields[1],16) end=string.atol(fields[3],16) diff = end - start if fields[4] == "(usable)" or fields[4] == "(ACPI data)": accum = accum + diff
if e820map == 2 or e820map == 4: # Convert the type 2 information into a type 4 if e820map == 2: fields=fields[2:] # Treat fields as a form 4 e820 map # Sanity check: if fields[1] != "@": print "Unknown log file format (form 2 or 4,) giving up" sys.exit(1) diff = string.atol(fields[0],16) if fields[3] == "(usable)" or fields[3] == "(ACPI data)": accum = accum + diff
# If we get here, and e820map is still 0, then we could not find # an e820 map and so it is time to give up. if not e820map: print "Could not find e820 map, giving up" sys.exit(1)
megabyte = 1024.0*1024.0 self.memtotal = int(round(accum/megabyte))
if __name__ == '__main__':
if len(sys.argv) != 2: print "Usage: %s log_file" % sys.argv[0] sys.exit(1) #NOTREACHED
e820info = e820Info(file=sys.argv[1])
print e820info.memtotal sys.exit(0)
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
On Friday, July 10 2009, Alford, Seth said:
e820.py is a Python script. It defines the e820Info class. The e820Info class reads the BIOS 15h/e820 memory map. We found that map to be a somewhat more reliable indicator of how much memory was actually on the system than /proc/meminfo. That map can can be found in /tmp/syslog while ramdisk Linux runs, or in /var/log/dmesg when regular Linux runs. The e820Info class can provide a sum of the memory reported found in the e820 map. The e820Info class recognizes the e820 map formats produced by the 2.4.* and 2.6* kernels with which we have tried it.
So the only thing that we were really using the memory information for was a) letting you know you don't have enough memory and b) deciding whether or not to install a PAE kernel
For a), I don't think that anything with low enough memory isn't going to have a difference to the degree of caring for e820 vs /proc/meminfo. For the latter, we now install the PAE kernel on all pae capable systems instead of caring how much memory is available.
Any other need you have for having this available? If so, we can look at doing something like this. Although note, to use this in current anaconda, the licensing needs to be GPLv2+ instead of just GPLv2 so we'd either need sign-off to use under those terms or we could reimplement similar if we have to
Jeremy
$Id: patch7.in /main/4 2009/07/10 01:14:52 alfords Exp $ Copyright 2009 Automatic Data Processing, Inc.
See the README file in the first patch of this series for important details.
kickstart.py.patch has most of our changes to the anaconda code. These changes include:
* an --interactive flag for pre- and post-install scripts. That causes anaconda to allow regular "cooked" output on a serial console terminal or the regular keyboard, video monitor and mouse (KVM.) * support for tape restore. We introduce a new install type, "restore", which skips many of the regular kickstart install steps. This enables a post-install script to re-install the system from a backup tape. * passing the -s command line flag for post-install scripts when they were running on a serial console. We needed to be prepared for a user restoring a tape from a KVM system onto a serial console system, and vice versa. This way the post restore script knows it may need to adjust system configuration files for the other kind of console. This change also may result in pre-install scripts being passed a -s flag. We only ever tested or used -s in post-install scripts. We cannot say for sure because our pre-install scripts do not try to process command line flags.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----kickstart.py.patch follows---- --- a_naconda-11.1.2.168/kickstart.py 2008-12-18 02:21:19.000000000 -0800 +++ anaconda-11.1.2.168/kickstart.py 2009-05-11 12:54:42.000000000 -0700 @@ -12,6 +12,7 @@ #
import iutil +import subprocess import isys import os from installclass import BaseInstallClass, availableClasses, getBaseInstallClass @@ -37,6 +38,13 @@ from anaconda_log import logger, logLevelMap
class AnacondaKSScript(Script): + def __init__(self, script, interp = "/bin/sh", inChroot = False, + logfile = None, errorOnFail = False, type = KS_SCRIPT_PRE, + userinput = False): + Script.__init__(self, script, interp, inChroot, logfile, + errorOnFail, type) + self.userinput = userinput + def run(self, chroot, serial, intf = None): import tempfile import os.path @@ -56,14 +64,30 @@ messages = self.logfile elif serial: messages = "%s.log" % path + sflag = "-s" else: messages = "/dev/tty3" + sflag = "" + + tmppath = "/tmp/%s" % (os.path.basename(path))
if intf: intf.suspend() - rc = iutil.execWithRedirect(self.interp, ["/tmp/%s" % os.path.basename(path)], - stdin = messages, stdout = messages, stderr = messages, - root = scriptRoot) + + if self.userinput: + try: + proc = subprocess.Popen([self.interp, tmppath, sflag], + cwd=scriptRoot) + rc = proc.wait() + except OSError, (errno, msg): + raise RuntimeError, "Error running " + tmppath + sflag + ": " + msg + #NOTREACHED + + else: + rc = iutil.execWithRedirect(self.interp, [tmppath, sflag], + stdin = messages, stdout = messages, stderr = messages, + root = scriptRoot) + if intf: intf.resume()
@@ -86,11 +110,16 @@ os.unlink(path)
if serial or self.logfile is not None: - os.chmod("%s" % messages, 0600) + if os.path.exists("%s" % messages): + os.chmod("%s" % messages, 0600)
class AnacondaKSHandlers(KickstartHandlers): def __init__ (self, ksdata, anaconda): KickstartHandlers.__init__(self, ksdata) + self.ksdata.reboot["eject"] = True + self.handlers["restore"] = self.doRestore + log.info ("Added restore to handlers in AnacondaKSHandlers") + self.permanentSkipSteps = [] self.skipSteps = [] self.showSteps = [] @@ -107,6 +136,20 @@ self.lineno = 0 self.currentCmd = ""
+ def doRestore(self, args): + self.skipSteps.extend([ + "bootloader", + "bootloaderadvanced", + "bootloadersetup", + "firstboot", + "instbootloader", + "install", + "installpackages", + "postinstallconfig", + "writeconfig", + "writeksconfig", + ]) + def doAuthconfig(self, args): KickstartHandlers.doAuthconfig(self, args) self.id.auth = self.ksdata.authconfig @@ -703,7 +746,7 @@ KickstartHandlers.__init__(self, ksdata) self.resetHandlers() self.handlers["vnc"] = self.doVnc - + self.handlers["restore"] = None # ADP self.handlers["text"] = self.doDisplayMode self.handlers["cmdline"] = self.doDisplayMode self.handlers["graphical"] = self.doDisplayMode @@ -718,7 +761,9 @@ if self.state == STATE_PRE: s = AnacondaKSScript (self.script["body"], self.script["interp"], self.script["chroot"], self.script["log"], - self.script["errorOnFail"]) + self.script["errorOnFail"], + self.script["type"], + self.script["userinput"]) self.ksdata.scripts.append(s)
def addPackages (self, line): @@ -739,6 +784,8 @@ default=False) op.add_option("--interpreter", dest="interpreter", default="/bin/sh") op.add_option("--log", "--logfile", dest="log") + op.add_option("--userinput", dest="userInput", action="store_true", + default=False)
(opts, extra) = op.parse_args(args=args[1:])
@@ -746,11 +793,15 @@ self.script["log"] = opts.log self.script["errorOnFail"] = opts.errorOnFail self.script["chroot"] = False + self.script["userinput"] = opts.userInput
class AnacondaKSParser(KickstartParser): - def __init__ (self, ksdata, kshandlers): + def __init__ (self, ksdata, kshandlers, followIncludes=True, + errorsAreFatal=True, missingIncludeIsFatal=True): self.sawPackageSection = False - KickstartParser.__init__(self, ksdata, kshandlers) + KickstartParser.__init__(self, ksdata, kshandlers, + followIncludes, errorsAreFatal, + missingIncludeIsFatal)
# Map old broken Everything group to the new futuristic package globs def addPackages (self, line): @@ -766,7 +817,8 @@
s = AnacondaKSScript (self.script["body"], self.script["interp"], self.script["chroot"], self.script["log"], - self.script["errorOnFail"], self.script["type"]) + self.script["errorOnFail"], self.script["type"], + self.script["userinput"])
self.ksdata.scripts.append(s)
@@ -789,6 +841,31 @@ self.handler.lineno = lineno self.handler.handlers[cmd](cmdArgs)
+ def handleScriptHdr (self, lineno, args): + op = KSOptionParser(lineno=lineno) + op.add_option("--erroronfail", dest="errorOnFail", action="store_true", + default=False) + op.add_option("--interpreter", dest="interpreter", default="/bin/sh") + op.add_option("--log", "--logfile", dest="log") + op.add_option("--userinput", dest="userInput", action="store_true", + default=False) + + if args[0] == "%pre" or args[0] == "%traceback": + self.script["chroot"] = False + elif args[0] == "%post": + self.script["chroot"] = True + op.add_option("--nochroot", dest="nochroot", action="store_true", + default=False) + + (opts, extra) = op.parse_args(args=args[1:]) + + self.script["interp"] = opts.interpreter + self.script["log"] = opts.log + self.script["errorOnFail"] = opts.errorOnFail + if hasattr(opts, "nochroot"): + self.script["chroot"] = not opts.nochroot + self.script["userinput"] = opts.userInput + cobject = getBaseInstallClass()
# The anaconda kickstart processor.
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
$Id: patch8.in /main/4 2009/07/10 17:42:52 alfords Exp $ Copyright 2009 Automatic Data Processing Inc.
See the README file in the first patch of this series for important details.
We had to modify mknod-stub.patch. As I recall, it was somehow getting called twice. That led it to complain that the device node already existed. The patch silences the complaint, but only for the case where the node already exists, with the right major and minor numbers, device type (character or block,) and modes. Otherwise, mknod-stub will still complain.
--Seth Alford ADP Dealer Services seth_alford@adp.com
----mknod-stub.patch follows---- --- a_naconda-11.1.2.168/command-stubs/mknod-stub 2008-12-18 02:21:19.000000000 -0800 +++ anaconda-11.1.2.168/command-stubs/mknod-stub 2009-05-12 15:22:36.000000000 -0700 @@ -6,6 +6,7 @@ import string import stat import os +import errno
def usage(): sys.stderr.write("Usage: %s <path> [b|c] <major> <minor> or\n" %(sys.argv[0],)) @@ -48,7 +49,18 @@ minor = int(sys.argv[4]) path = sys.argv[1]
- os.mknod(path, 0644 | type, os.makedev(major, minor)) + try: + os.mknod(path, 0644 | type, os.makedev(major, minor)) + except OSError, e: + statbuf = os.stat(path) + if (errno.errorcode[e.errno] == "EEXIST" and + os.major(statbuf.st_rdev) == major and + os.minor(statbuf.st_rdev) == minor and + ((type == stat.S_IFCHR and stat.S_ISCHR(statbuf.st_mode)) or + (type == stat.S_IFBLK and stat.S_ISBLK(statbuf.st_mode)))): + pass + else: + raise OSError, e
if __name__ == "__main__": main()
This message and any attachments are intended only for the use of the addressee and may contain information that is privileged and confidential. If the reader of the message is not the intended recipient or an authorized representative of the intended recipient, you are hereby notified that any dissemination of this communication is strictly prohibited. If you have received this communication in error, please notify us immediately by e-mail and delete the message and any attachments from your system.
On Friday, July 10 2009, Alford, Seth said:
We had to modify mknod-stub.patch. As I recall, it was somehow getting called twice. That led it to complain that the device node already existed. The patch silences the complaint, but only for the case where the node already exists, with the right major and minor numbers, device type (character or block,) and modes. Otherwise, mknod-stub will still complain.
It's probably worth fixing whatever the caller is to only call once -- we don't use the stub anymore, using regular mknod instead and I'm pretty sure regular mknod will complain if you try to recreate an existing device node in a similar fashion
Jeremy
Some years ago, ADP modified the anaconda which came with RedHat 7.0, and, later, RedHat 8.0. To comply with the GPL, we redistributed changes that we made to those versions of anaconda. To explain what we did, we wrote a large README which we posted with the modifications.
ADP has now modified the anaconda which came with CentOS 5.3. The newer version of anaconda is more powerful than the older versions. The newer anaconda is much closer to what ADP needs. But, we still had to modify anaconda itself to get it to do what we wanted.
FWIW, sharing these is much appreciated. A lot has changed since the rhel5 branch on the master branch, and I'm going to try to look at these in that context to try to reduce what you have to do in the future.
Jeremy
anaconda-devel@lists.fedoraproject.org