Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1 | # |
| 2 | # parser.py: Kickstart file parser. |
| 3 | # |
| 4 | # Chris Lumens <clumens@redhat.com> |
| 5 | # |
| 6 | # Copyright 2005, 2006, 2007, 2008, 2011 Red Hat, Inc. |
| 7 | # |
| 8 | # This copyrighted material is made available to anyone wishing to use, modify, |
| 9 | # copy, or redistribute it subject to the terms and conditions of the GNU |
| 10 | # General Public License v.2. This program is distributed in the hope that it |
| 11 | # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the |
| 12 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| 13 | # See the GNU General Public License for more details. |
| 14 | # |
| 15 | # You should have received a copy of the GNU General Public License along with |
| 16 | # this program; if not, write to the Free Software Foundation, Inc., 51 |
| 17 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat |
| 18 | # trademarks that are incorporated in the source code or documentation are not |
| 19 | # subject to the GNU General Public License and may only be used or replicated |
| 20 | # with the express permission of Red Hat, Inc. |
| 21 | # |
| 22 | """ |
| 23 | Main kickstart file processing module. |
| 24 | |
| 25 | This module exports several important classes: |
| 26 | |
| 27 | Script - Representation of a single %pre, %post, or %traceback script. |
| 28 | |
| 29 | Packages - Representation of the %packages section. |
| 30 | |
| 31 | KickstartParser - The kickstart file parser state machine. |
| 32 | """ |
| 33 | |
| 34 | from collections import Iterator |
| 35 | import os |
| 36 | import shlex |
| 37 | import sys |
| 38 | import tempfile |
| 39 | from copy import copy |
| 40 | from optparse import * |
| 41 | |
| 42 | import constants |
| 43 | from errors import KickstartError, KickstartParseError, KickstartValueError, formatErrorMsg |
| 44 | from ko import KickstartObject |
| 45 | from sections import * |
| 46 | import version |
| 47 | |
| 48 | import gettext |
| 49 | _ = lambda x: gettext.ldgettext("pykickstart", x) |
| 50 | |
| 51 | STATE_END = "end" |
| 52 | STATE_COMMANDS = "commands" |
| 53 | |
| 54 | ver = version.DEVEL |
| 55 | |
| 56 | |
| 57 | class PutBackIterator(Iterator): |
| 58 | def __init__(self, iterable): |
| 59 | self._iterable = iter(iterable) |
| 60 | self._buf = None |
| 61 | |
| 62 | def __iter__(self): |
| 63 | return self |
| 64 | |
| 65 | def put(self, s): |
| 66 | self._buf = s |
| 67 | |
| 68 | def next(self): |
| 69 | if self._buf: |
| 70 | retval = self._buf |
| 71 | self._buf = None |
| 72 | return retval |
| 73 | else: |
| 74 | return self._iterable.next() |
| 75 | |
| 76 | ### |
| 77 | ### SCRIPT HANDLING |
| 78 | ### |
| 79 | class Script(KickstartObject): |
| 80 | """A class representing a single kickstart script. If functionality beyond |
| 81 | just a data representation is needed (for example, a run method in |
| 82 | anaconda), Script may be subclassed. Although a run method is not |
| 83 | provided, most of the attributes of Script have to do with running the |
| 84 | script. Instances of Script are held in a list by the Version object. |
| 85 | """ |
| 86 | def __init__(self, script, *args , **kwargs): |
| 87 | """Create a new Script instance. Instance attributes: |
| 88 | |
| 89 | errorOnFail -- If execution of the script fails, should anaconda |
| 90 | stop, display an error, and then reboot without |
| 91 | running any other scripts? |
| 92 | inChroot -- Does the script execute in anaconda's chroot |
| 93 | environment or not? |
| 94 | interp -- The program that should be used to interpret this |
| 95 | script. |
| 96 | lineno -- The line number this script starts on. |
| 97 | logfile -- Where all messages from the script should be logged. |
| 98 | script -- A string containing all the lines of the script. |
| 99 | type -- The type of the script, which can be KS_SCRIPT_* from |
| 100 | pykickstart.constants. |
| 101 | """ |
| 102 | KickstartObject.__init__(self, *args, **kwargs) |
| 103 | self.script = "".join(script) |
| 104 | |
| 105 | self.interp = kwargs.get("interp", "/bin/sh") |
| 106 | self.inChroot = kwargs.get("inChroot", False) |
| 107 | self.lineno = kwargs.get("lineno", None) |
| 108 | self.logfile = kwargs.get("logfile", None) |
| 109 | self.errorOnFail = kwargs.get("errorOnFail", False) |
| 110 | self.type = kwargs.get("type", constants.KS_SCRIPT_PRE) |
| 111 | |
| 112 | def __str__(self): |
| 113 | """Return a string formatted for output to a kickstart file.""" |
| 114 | retval = "" |
| 115 | |
| 116 | if self.type == constants.KS_SCRIPT_PRE: |
| 117 | retval += '\n%pre' |
| 118 | elif self.type == constants.KS_SCRIPT_POST: |
| 119 | retval += '\n%post' |
| 120 | elif self.type == constants.KS_SCRIPT_TRACEBACK: |
| 121 | retval += '\n%traceback' |
| 122 | |
| 123 | if self.interp != "/bin/sh" and self.interp != "": |
| 124 | retval += " --interpreter=%s" % self.interp |
| 125 | if self.type == constants.KS_SCRIPT_POST and not self.inChroot: |
| 126 | retval += " --nochroot" |
| 127 | if self.logfile != None: |
| 128 | retval += " --logfile %s" % self.logfile |
| 129 | if self.errorOnFail: |
| 130 | retval += " --erroronfail" |
| 131 | |
| 132 | if self.script.endswith("\n"): |
| 133 | if ver >= version.F8: |
| 134 | return retval + "\n%s%%end\n" % self.script |
| 135 | else: |
| 136 | return retval + "\n%s\n" % self.script |
| 137 | else: |
| 138 | if ver >= version.F8: |
| 139 | return retval + "\n%s\n%%end\n" % self.script |
| 140 | else: |
| 141 | return retval + "\n%s\n" % self.script |
| 142 | |
| 143 | |
| 144 | ## |
| 145 | ## PACKAGE HANDLING |
| 146 | ## |
| 147 | class Group: |
| 148 | """A class representing a single group in the %packages section.""" |
| 149 | def __init__(self, name="", include=constants.GROUP_DEFAULT): |
| 150 | """Create a new Group instance. Instance attributes: |
| 151 | |
| 152 | name -- The group's identifier |
| 153 | include -- The level of how much of the group should be included. |
| 154 | Values can be GROUP_* from pykickstart.constants. |
| 155 | """ |
| 156 | self.name = name |
| 157 | self.include = include |
| 158 | |
| 159 | def __str__(self): |
| 160 | """Return a string formatted for output to a kickstart file.""" |
| 161 | if self.include == constants.GROUP_REQUIRED: |
| 162 | return "@%s --nodefaults" % self.name |
| 163 | elif self.include == constants.GROUP_ALL: |
| 164 | return "@%s --optional" % self.name |
| 165 | else: |
| 166 | return "@%s" % self.name |
| 167 | |
| 168 | def __cmp__(self, other): |
| 169 | if self.name < other.name: |
| 170 | return -1 |
| 171 | elif self.name > other.name: |
| 172 | return 1 |
| 173 | return 0 |
| 174 | |
| 175 | class Packages(KickstartObject): |
| 176 | """A class representing the %packages section of the kickstart file.""" |
| 177 | def __init__(self, *args, **kwargs): |
| 178 | """Create a new Packages instance. Instance attributes: |
| 179 | |
| 180 | addBase -- Should the Base group be installed even if it is |
| 181 | not specified? |
| 182 | default -- Should the default package set be selected? |
| 183 | excludedList -- A list of all the packages marked for exclusion in |
| 184 | the %packages section, without the leading minus |
| 185 | symbol. |
| 186 | excludeDocs -- Should documentation in each package be excluded? |
| 187 | groupList -- A list of Group objects representing all the groups |
| 188 | specified in the %packages section. Names will be |
| 189 | stripped of the leading @ symbol. |
| 190 | excludedGroupList -- A list of Group objects representing all the |
| 191 | groups specified for removal in the %packages |
| 192 | section. Names will be stripped of the leading |
| 193 | -@ symbols. |
| 194 | handleMissing -- If unknown packages are specified in the %packages |
| 195 | section, should it be ignored or not? Values can |
| 196 | be KS_MISSING_* from pykickstart.constants. |
| 197 | packageList -- A list of all the packages specified in the |
| 198 | %packages section. |
| 199 | instLangs -- A list of languages to install. |
| 200 | """ |
| 201 | KickstartObject.__init__(self, *args, **kwargs) |
| 202 | |
| 203 | self.addBase = True |
| 204 | self.default = False |
| 205 | self.excludedList = [] |
| 206 | self.excludedGroupList = [] |
| 207 | self.excludeDocs = False |
| 208 | self.groupList = [] |
| 209 | self.handleMissing = constants.KS_MISSING_PROMPT |
| 210 | self.packageList = [] |
| 211 | self.instLangs = None |
| 212 | |
| 213 | def __str__(self): |
| 214 | """Return a string formatted for output to a kickstart file.""" |
| 215 | pkgs = "" |
| 216 | |
| 217 | if not self.default: |
| 218 | grps = self.groupList |
| 219 | grps.sort() |
| 220 | for grp in grps: |
| 221 | pkgs += "%s\n" % grp.__str__() |
| 222 | |
| 223 | p = self.packageList |
| 224 | p.sort() |
| 225 | for pkg in p: |
| 226 | pkgs += "%s\n" % pkg |
| 227 | |
| 228 | grps = self.excludedGroupList |
| 229 | grps.sort() |
| 230 | for grp in grps: |
| 231 | pkgs += "-%s\n" % grp.__str__() |
| 232 | |
| 233 | p = self.excludedList |
| 234 | p.sort() |
| 235 | for pkg in p: |
| 236 | pkgs += "-%s\n" % pkg |
| 237 | |
| 238 | if pkgs == "": |
| 239 | return "" |
| 240 | |
| 241 | retval = "\n%packages" |
| 242 | |
| 243 | if self.default: |
| 244 | retval += " --default" |
| 245 | if self.excludeDocs: |
| 246 | retval += " --excludedocs" |
| 247 | if not self.addBase: |
| 248 | retval += " --nobase" |
| 249 | if self.handleMissing == constants.KS_MISSING_IGNORE: |
| 250 | retval += " --ignoremissing" |
| 251 | if self.instLangs: |
| 252 | retval += " --instLangs=%s" % self.instLangs |
| 253 | |
| 254 | if ver >= version.F8: |
| 255 | return retval + "\n" + pkgs + "\n%end\n" |
| 256 | else: |
| 257 | return retval + "\n" + pkgs + "\n" |
| 258 | |
| 259 | def _processGroup (self, line): |
| 260 | op = OptionParser() |
| 261 | op.add_option("--nodefaults", action="store_true", default=False) |
| 262 | op.add_option("--optional", action="store_true", default=False) |
| 263 | |
| 264 | (opts, extra) = op.parse_args(args=line.split()) |
| 265 | |
| 266 | if opts.nodefaults and opts.optional: |
| 267 | raise KickstartValueError, _("Group cannot specify both --nodefaults and --optional") |
| 268 | |
| 269 | # If the group name has spaces in it, we have to put it back together |
| 270 | # now. |
| 271 | grp = " ".join(extra) |
| 272 | |
| 273 | if opts.nodefaults: |
| 274 | self.groupList.append(Group(name=grp, include=constants.GROUP_REQUIRED)) |
| 275 | elif opts.optional: |
| 276 | self.groupList.append(Group(name=grp, include=constants.GROUP_ALL)) |
| 277 | else: |
| 278 | self.groupList.append(Group(name=grp, include=constants.GROUP_DEFAULT)) |
| 279 | |
| 280 | def add (self, pkgList): |
| 281 | """Given a list of lines from the input file, strip off any leading |
| 282 | symbols and add the result to the appropriate list. |
| 283 | """ |
| 284 | existingExcludedSet = set(self.excludedList) |
| 285 | existingPackageSet = set(self.packageList) |
| 286 | newExcludedSet = set() |
| 287 | newPackageSet = set() |
| 288 | |
| 289 | excludedGroupList = [] |
| 290 | |
| 291 | for pkg in pkgList: |
| 292 | stripped = pkg.strip() |
| 293 | |
| 294 | if stripped[0] == "@": |
| 295 | self._processGroup(stripped[1:]) |
| 296 | elif stripped[0] == "-": |
| 297 | if stripped[1] == "@": |
| 298 | excludedGroupList.append(Group(name=stripped[2:])) |
| 299 | else: |
| 300 | newExcludedSet.add(stripped[1:]) |
| 301 | else: |
| 302 | newPackageSet.add(stripped) |
| 303 | |
| 304 | # Groups have to be excluded in two different ways (note: can't use |
| 305 | # sets here because we have to store objects): |
| 306 | excludedGroupNames = map(lambda g: g.name, excludedGroupList) |
| 307 | |
| 308 | # First, an excluded group may be cancelling out a previously given |
| 309 | # one. This is often the case when using %include. So there we should |
| 310 | # just remove the group from the list. |
| 311 | self.groupList = filter(lambda g: g.name not in excludedGroupNames, self.groupList) |
| 312 | |
| 313 | # Second, the package list could have included globs which are not |
| 314 | # processed by pykickstart. In that case we need to preserve a list of |
| 315 | # excluded groups so whatever tool doing package/group installation can |
| 316 | # take appropriate action. |
| 317 | self.excludedGroupList.extend(excludedGroupList) |
| 318 | |
| 319 | existingPackageSet = (existingPackageSet - newExcludedSet) | newPackageSet |
| 320 | existingExcludedSet = (existingExcludedSet - existingPackageSet) | newExcludedSet |
| 321 | |
| 322 | self.packageList = list(existingPackageSet) |
| 323 | self.excludedList = list(existingExcludedSet) |
| 324 | |
| 325 | |
| 326 | ### |
| 327 | ### PARSER |
| 328 | ### |
| 329 | class KickstartParser: |
| 330 | """The kickstart file parser class as represented by a basic state |
| 331 | machine. To create a specialized parser, make a subclass and override |
| 332 | any of the methods you care about. Methods that don't need to do |
| 333 | anything may just pass. However, _stateMachine should never be |
| 334 | overridden. |
| 335 | """ |
| 336 | def __init__ (self, handler, followIncludes=True, errorsAreFatal=True, |
| 337 | missingIncludeIsFatal=True): |
| 338 | """Create a new KickstartParser instance. Instance attributes: |
| 339 | |
| 340 | errorsAreFatal -- Should errors cause processing to halt, or |
| 341 | just print a message to the screen? This |
| 342 | is most useful for writing syntax checkers |
| 343 | that may want to continue after an error is |
| 344 | encountered. |
| 345 | followIncludes -- If %include is seen, should the included |
| 346 | file be checked as well or skipped? |
| 347 | handler -- An instance of a BaseHandler subclass. If |
| 348 | None, the input file will still be parsed |
| 349 | but no data will be saved and no commands |
| 350 | will be executed. |
| 351 | missingIncludeIsFatal -- Should missing include files be fatal, even |
| 352 | if errorsAreFatal is False? |
| 353 | """ |
| 354 | self.errorsAreFatal = errorsAreFatal |
| 355 | self.followIncludes = followIncludes |
| 356 | self.handler = handler |
| 357 | self.currentdir = {} |
| 358 | self.missingIncludeIsFatal = missingIncludeIsFatal |
| 359 | |
| 360 | self._state = STATE_COMMANDS |
| 361 | self._includeDepth = 0 |
| 362 | self._line = "" |
| 363 | |
| 364 | self.version = self.handler.version |
| 365 | |
| 366 | global ver |
| 367 | ver = self.version |
| 368 | |
| 369 | self._sections = {} |
| 370 | self.setupSections() |
| 371 | |
| 372 | def _reset(self): |
| 373 | """Reset the internal variables of the state machine for a new kickstart file.""" |
| 374 | self._state = STATE_COMMANDS |
| 375 | self._includeDepth = 0 |
| 376 | |
| 377 | def getSection(self, s): |
| 378 | """Return a reference to the requested section (s must start with '%'s), |
| 379 | or raise KeyError if not found. |
| 380 | """ |
| 381 | return self._sections[s] |
| 382 | |
| 383 | def handleCommand (self, lineno, args): |
| 384 | """Given the list of command and arguments, call the Version's |
| 385 | dispatcher method to handle the command. Returns the command or |
| 386 | data object returned by the dispatcher. This method may be |
| 387 | overridden in a subclass if necessary. |
| 388 | """ |
| 389 | if self.handler: |
| 390 | self.handler.currentCmd = args[0] |
| 391 | self.handler.currentLine = self._line |
| 392 | retval = self.handler.dispatcher(args, lineno) |
| 393 | |
| 394 | return retval |
| 395 | |
| 396 | def registerSection(self, obj): |
| 397 | """Given an instance of a Section subclass, register the new section |
| 398 | with the parser. Calling this method means the parser will |
| 399 | recognize your new section and dispatch into the given object to |
| 400 | handle it. |
| 401 | """ |
| 402 | if not obj.sectionOpen: |
| 403 | raise TypeError, "no sectionOpen given for section %s" % obj |
| 404 | |
| 405 | if not obj.sectionOpen.startswith("%"): |
| 406 | raise TypeError, "section %s tag does not start with a %%" % obj.sectionOpen |
| 407 | |
| 408 | self._sections[obj.sectionOpen] = obj |
| 409 | |
| 410 | def _finalize(self, obj): |
| 411 | """Called at the close of a kickstart section to take any required |
| 412 | actions. Internally, this is used to add scripts once we have the |
| 413 | whole body read. |
| 414 | """ |
| 415 | obj.finalize() |
| 416 | self._state = STATE_COMMANDS |
| 417 | |
| 418 | def _handleSpecialComments(self, line): |
| 419 | """Kickstart recognizes a couple special comments.""" |
| 420 | if self._state != STATE_COMMANDS: |
| 421 | return |
| 422 | |
| 423 | # Save the platform for s-c-kickstart. |
| 424 | if line[:10] == "#platform=": |
| 425 | self.handler.platform = self._line[11:] |
| 426 | |
| 427 | def _readSection(self, lineIter, lineno): |
| 428 | obj = self._sections[self._state] |
| 429 | |
| 430 | while True: |
| 431 | try: |
| 432 | line = lineIter.next() |
| 433 | if line == "": |
| 434 | # This section ends at the end of the file. |
| 435 | if self.version >= version.F8: |
| 436 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end.")) |
| 437 | |
| 438 | self._finalize(obj) |
| 439 | except StopIteration: |
| 440 | break |
| 441 | |
| 442 | lineno += 1 |
| 443 | |
| 444 | # Throw away blank lines and comments, unless the section wants all |
| 445 | # lines. |
| 446 | if self._isBlankOrComment(line) and not obj.allLines: |
| 447 | continue |
| 448 | |
| 449 | if line.startswith("%"): |
| 450 | args = shlex.split(line) |
| 451 | |
| 452 | if args and args[0] == "%end": |
| 453 | # This is a properly terminated section. |
| 454 | self._finalize(obj) |
| 455 | break |
| 456 | elif args and args[0] == "%ksappend": |
| 457 | continue |
| 458 | elif args and (self._validState(args[0]) or args[0] in ["%include", "%ksappend"]): |
| 459 | # This is an unterminated section. |
| 460 | if self.version >= version.F8: |
| 461 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end.")) |
| 462 | |
| 463 | # Finish up. We do not process the header here because |
| 464 | # kicking back out to STATE_COMMANDS will ensure that happens. |
| 465 | lineIter.put(line) |
| 466 | lineno -= 1 |
| 467 | self._finalize(obj) |
| 468 | break |
| 469 | else: |
| 470 | # This is just a line within a section. Pass it off to whatever |
| 471 | # section handles it. |
| 472 | obj.handleLine(line) |
| 473 | |
| 474 | return lineno |
| 475 | |
| 476 | def _validState(self, st): |
| 477 | """Is the given section tag one that has been registered with the parser?""" |
| 478 | return st in self._sections.keys() |
| 479 | |
| 480 | def _tryFunc(self, fn): |
| 481 | """Call the provided function (which doesn't take any arguments) and |
| 482 | do the appropriate error handling. If errorsAreFatal is False, this |
| 483 | function will just print the exception and keep going. |
| 484 | """ |
| 485 | try: |
| 486 | fn() |
| 487 | except Exception, msg: |
| 488 | if self.errorsAreFatal: |
| 489 | raise |
| 490 | else: |
| 491 | print msg |
| 492 | |
| 493 | def _isBlankOrComment(self, line): |
| 494 | return line.isspace() or line == "" or line.lstrip()[0] == '#' |
| 495 | |
| 496 | def _stateMachine(self, lineIter): |
| 497 | # For error reporting. |
| 498 | lineno = 0 |
| 499 | |
| 500 | while True: |
| 501 | # Get the next line out of the file, quitting if this is the last line. |
| 502 | try: |
| 503 | self._line = lineIter.next() |
| 504 | if self._line == "": |
| 505 | break |
| 506 | except StopIteration: |
| 507 | break |
| 508 | |
| 509 | lineno += 1 |
| 510 | |
| 511 | # Eliminate blank lines, whitespace-only lines, and comments. |
| 512 | if self._isBlankOrComment(self._line): |
| 513 | self._handleSpecialComments(self._line) |
| 514 | continue |
| 515 | |
| 516 | # Remove any end-of-line comments. |
| 517 | sanitized = self._line.split("#")[0] |
| 518 | |
| 519 | # Then split the line. |
| 520 | args = shlex.split(sanitized.rstrip()) |
| 521 | |
| 522 | if args[0] == "%include": |
| 523 | # This case comes up primarily in ksvalidator. |
| 524 | if not self.followIncludes: |
| 525 | continue |
| 526 | |
| 527 | if len(args) == 1 or not args[1]: |
| 528 | raise KickstartParseError, formatErrorMsg(lineno) |
| 529 | |
| 530 | self._includeDepth += 1 |
| 531 | |
| 532 | try: |
| 533 | self.readKickstart(args[1], reset=False) |
| 534 | except KickstartError: |
| 535 | # Handle the include file being provided over the |
| 536 | # network in a %pre script. This case comes up in the |
| 537 | # early parsing in anaconda. |
| 538 | if self.missingIncludeIsFatal: |
| 539 | raise |
| 540 | |
| 541 | self._includeDepth -= 1 |
| 542 | continue |
| 543 | |
| 544 | # Now on to the main event. |
| 545 | if self._state == STATE_COMMANDS: |
| 546 | if args[0] == "%ksappend": |
| 547 | # This is handled by the preprocess* functions, so continue. |
| 548 | continue |
| 549 | elif args[0][0] == '%': |
| 550 | # This is the beginning of a new section. Handle its header |
| 551 | # here. |
| 552 | newSection = args[0] |
| 553 | if not self._validState(newSection): |
| 554 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown kickstart section: %s" % newSection)) |
| 555 | |
| 556 | self._state = newSection |
| 557 | obj = self._sections[self._state] |
| 558 | self._tryFunc(lambda: obj.handleHeader(lineno, args)) |
| 559 | |
| 560 | # This will handle all section processing, kicking us back |
| 561 | # out to STATE_COMMANDS at the end with the current line |
| 562 | # being the next section header, etc. |
| 563 | lineno = self._readSection(lineIter, lineno) |
| 564 | else: |
| 565 | # This is a command in the command section. Dispatch to it. |
| 566 | self._tryFunc(lambda: self.handleCommand(lineno, args)) |
| 567 | elif self._state == STATE_END: |
| 568 | break |
| 569 | |
| 570 | def readKickstartFromString (self, s, reset=True): |
| 571 | """Process a kickstart file, provided as the string str.""" |
| 572 | if reset: |
| 573 | self._reset() |
| 574 | |
| 575 | # Add a "" to the end of the list so the string reader acts like the |
| 576 | # file reader and we only get StopIteration when we're after the final |
| 577 | # line of input. |
| 578 | i = PutBackIterator(s.splitlines(True) + [""]) |
| 579 | self._stateMachine (i) |
| 580 | |
| 581 | def readKickstart(self, f, reset=True): |
| 582 | """Process a kickstart file, given by the filename f.""" |
| 583 | if reset: |
| 584 | self._reset() |
| 585 | |
| 586 | # an %include might not specify a full path. if we don't try to figure |
| 587 | # out what the path should have been, then we're unable to find it |
| 588 | # requiring full path specification, though, sucks. so let's make |
| 589 | # the reading "smart" by keeping track of what the path is at each |
| 590 | # include depth. |
| 591 | if not os.path.exists(f): |
| 592 | if self.currentdir.has_key(self._includeDepth - 1): |
| 593 | if os.path.exists(os.path.join(self.currentdir[self._includeDepth - 1], f)): |
| 594 | f = os.path.join(self.currentdir[self._includeDepth - 1], f) |
| 595 | |
| 596 | cd = os.path.dirname(f) |
| 597 | if not cd.startswith("/"): |
| 598 | cd = os.path.abspath(cd) |
| 599 | self.currentdir[self._includeDepth] = cd |
| 600 | |
| 601 | try: |
| 602 | s = file(f).read() |
| 603 | except IOError, e: |
| 604 | raise KickstartError, formatErrorMsg(0, msg=_("Unable to open input kickstart file: %s") % e.strerror) |
| 605 | |
| 606 | self.readKickstartFromString(s, reset=False) |
| 607 | |
| 608 | def setupSections(self): |
| 609 | """Install the sections all kickstart files support. You may override |
| 610 | this method in a subclass, but should avoid doing so unless you know |
| 611 | what you're doing. |
| 612 | """ |
| 613 | self._sections = {} |
| 614 | |
| 615 | # Install the sections all kickstart files support. |
| 616 | self.registerSection(PreScriptSection(self.handler, dataObj=Script)) |
| 617 | self.registerSection(PostScriptSection(self.handler, dataObj=Script)) |
| 618 | self.registerSection(TracebackScriptSection(self.handler, dataObj=Script)) |
| 619 | self.registerSection(PackageSection(self.handler)) |