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