Ignore the pylint warning of raising-bad-type and unbalanced-tuple-unpacking in conne...
[tools/mic.git] / mic / 3rdparty / pykickstart / parser.py
1 #
2 # parser.py:  Kickstart file parser.
3 #
4 # Chris Lumens <clumens@redhat.com>
5 #
6 # Copyright 2005-2016 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, %pre-install, %post, or %traceback script.
28
29     Packages - Representation of the %packages section.
30
31     KickstartParser - The kickstart file parser state machine.
32 """
33 from collections.abc import Iterator
34
35 import os
36 import shlex
37 import sys
38 import warnings
39
40 from pykickstart import constants, version
41 from pykickstart.errors import KickstartError, KickstartParseError, KickstartParseWarning
42 from pykickstart.ko import KickstartObject
43 from pykickstart.options import KSOptionParser
44 from pykickstart.load import load_to_str
45 from pykickstart.sections import PackageSection, PreScriptSection, PreInstallScriptSection, \
46                                  PostScriptSection, TracebackScriptSection, OnErrorScriptSection, \
47                                  NullSection, RunScriptSection, PostUmountScriptSection, TpkPackageSection
48
49 from pykickstart.i18n import _
50
51 STATE_END = "end"
52 STATE_COMMANDS = "commands"
53
54 def _preprocessStateMachine(lineIter):
55     l = None
56     lineno = 0
57     retval = ""
58
59     retval = retval.encode(sys.getdefaultencoding())
60
61     while True:
62         try:
63             l = next(lineIter)
64         except StopIteration:
65             break
66
67         # At the end of the file?
68         if l == "":
69             break
70
71         lineno += 1
72         ksurl = None
73
74         ll = l.strip()
75         if not ll.startswith("%ksappend"):
76             retval += l.encode(sys.getdefaultencoding())
77             continue
78
79         # Try to pull down the remote file.
80         try:
81             ksurl = ll.split(' ')[1]
82         except:
83             raise KickstartParseError(_("Illegal url for %%ksappend: %s") % ll, lineno=lineno)
84
85         try:
86             contents = load_to_str(ksurl)
87         except KickstartError as e:
88             raise KickstartError(_("Unable to open %%ksappend file: %s") % str(e), lineno=lineno)
89         # If that worked, write the remote file to the output kickstart
90         # file in one burst.  This allows multiple %ksappend lines to
91         # exist.
92         if contents is not None:
93             retval += contents.encode(sys.getdefaultencoding())
94
95     return retval
96
97 def preprocessFromStringToString(s):
98     """Preprocess the kickstart file, provided as the string s.  This
99        method is currently only useful for handling %ksappend lines, which
100        need to be fetched before the real kickstart parser can be run.
101        Returns the complete kickstart file as a string.
102     """
103     i = iter(s.splitlines(True) + [""])
104     return _preprocessStateMachine(i)
105
106 def preprocessKickstartToString(f):
107     """Preprocess the kickstart file, given by the filename f.  This
108        method is currently only useful for handling %ksappend lines,
109        which need to be fetched before the real kickstart parser can be
110        run.  Returns the complete kickstart file as a string.
111     """
112     try:
113         contents = load_to_str(f)
114     except KickstartError as e:
115         raise KickstartError(_("Unable to open input kickstart file: %s") % str(e), lineno=0)
116
117     return _preprocessStateMachine(iter(contents.splitlines(True)))
118
119 def preprocessFromString(s):
120     """Preprocess the kickstart file, provided as the string s.  This
121        method is currently only useful for handling %ksappend lines,
122        which need to be fetched before the real kickstart parser can be
123        run.  Returns the location of the complete kickstart file.
124     """
125     s = preprocessFromStringToString(s)
126     if s.strip():
127         import tempfile
128         (outF, outName) = tempfile.mkstemp(suffix="-ks.cfg")
129
130         os.write(outF, s)
131         os.close(outF)
132         return outName
133
134     return None
135
136 def preprocessKickstart(f):
137     """Preprocess the kickstart file, given by the filename f.  This
138        method is currently only useful for handling %ksappend lines,
139        which need to be fetched before the real kickstart parser can be
140        run.  Returns the location of the complete kickstart file.
141     """
142     s = preprocessKickstartToString(f)
143     if s.strip():
144         import tempfile
145         (outF, outName) = tempfile.mkstemp(suffix="-ks.cfg")
146
147         os.write(outF, s)
148         os.close(outF)
149         return outName
150
151     return None
152
153 class PutBackIterator(Iterator):
154     def __init__(self, iterable):
155         self._iterable = iter(iterable)
156         self._buf = None
157
158     def __iter__(self):
159         return self
160
161     def put(self, s):
162         self._buf = s
163
164     def next(self):
165         if self._buf:
166             retval = self._buf
167             self._buf = None
168             return retval
169         else:
170             return next(self._iterable)
171
172     def __next__(self):
173         return self.next()                          # pylint: disable=not-callable
174
175 ###
176 ### SCRIPT HANDLING
177 ###
178 class Script(KickstartObject):
179     _ver = version.DEVEL
180
181     """A class representing a single kickstart script.  If functionality beyond
182        just a data representation is needed (for example, a run method in
183        anaconda), Script may be subclassed.  Although a run method is not
184        provided, most of the attributes of Script have to do with running the
185        script.  Instances of Script are held in a list by the Version object.
186     """
187     def __init__(self, script, *args, **kwargs):
188         """Create a new Script instance.  Instance attributes:
189
190            :keyword errorOnFail: If execution of the script fails, should anaconda
191                                  stop, display an error, and then reboot without
192                                  running any other scripts?
193
194            :keyword inChroot: Does the script execute in anaconda's chroot
195                               environment or not?
196
197            :keyword interp: The program that should be used to interpret this
198                             script.
199
200            :keyword lineno: The line number this script starts on.
201
202            :keyword logfile: Where all messages from the script should be logged.
203
204            :keyword script: A string containing all the lines of the script.
205
206            :keyword type: The type of the script, which can be KS_SCRIPT_* from
207                           :mod:`pykickstart.constants`.
208         """
209         KickstartObject.__init__(self, *args, **kwargs)
210         self.script = "".join(script)
211
212         self.interp = kwargs.get("interp", "/bin/sh")
213         self.inChroot = kwargs.get("inChroot", False)
214         self.lineno = kwargs.get("lineno", None)
215         self.logfile = kwargs.get("logfile", None)
216         self.errorOnFail = kwargs.get("errorOnFail", False)
217         self.type = kwargs.get("type", constants.KS_SCRIPT_PRE)
218
219     def __str__(self):
220         """Return a string formatted for output to a kickstart file."""
221         retval = ""
222
223         if self.type == constants.KS_SCRIPT_PRE:
224             retval += '\n%pre'
225         elif self.type == constants.KS_SCRIPT_POST:
226             retval += '\n%post'
227         elif self.type == constants.KS_SCRIPT_TRACEBACK:
228             retval += '\n%traceback'
229         elif self.type == constants.KS_SCRIPT_PREINSTALL:
230             retval += '\n%pre-install'
231         elif self.type == constants.KS_SCRIPT_ONERROR:
232             retval += '\n%onerror'
233         elif self.type == constants.KS_SCRIPT_RUN:
234             retval += '\n%runscript'
235         elif self.type == constants.KS_SCRIPT_UMOUNT:
236             retval += '\n%post-umount'
237
238         if self.interp != "/bin/sh" and self.interp:
239             retval += " --interpreter=%s" % self.interp
240         if self.type == constants.KS_SCRIPT_POST and not self.inChroot:
241             retval += " --nochroot"
242         if self.logfile is not None:
243             retval += " --logfile=%s" % self.logfile
244         if self.errorOnFail:
245             retval += " --erroronfail"
246
247         if self.script.endswith("\n"):
248             if self._ver >= version.F8:
249                 return retval + "\n%s%%end\n" % self.script
250             else:
251                 return retval + "\n%s" % self.script
252         else:
253             if self._ver >= version.F8:
254                 return retval + "\n%s\n%%end\n" % self.script
255             else:
256                 return retval + "\n%s\n" % self.script
257
258 ##
259 ## PACKAGE HANDLING
260 ##
261 class Group(KickstartObject):
262     """A class representing a single group in the %packages section."""
263     def __init__(self, name="", include=constants.GROUP_DEFAULT):
264         """Create a new Group instance.  Instance attributes:
265
266            name    -- The group's identifier
267            include -- The level of how much of the group should be included.
268                       Values can be GROUP_* from pykickstart.constants.
269         """
270         KickstartObject.__init__(self)
271         self.name = name
272         self.include = include
273
274     def __str__(self):
275         """Return a string formatted for output to a kickstart file."""
276         if self.include == constants.GROUP_REQUIRED:
277             return "@%s --nodefaults" % self.name
278         elif self.include == constants.GROUP_ALL:
279             return "@%s --optional" % self.name
280         else:
281             return "@%s" % self.name
282
283     def __lt__(self, other):
284         return self.name < other.name
285
286     def __le__(self, other):
287         return self.name <= other.name
288
289     def __eq__(self, other):
290         return self.name == other.name
291
292     def __ne__(self, other):
293         return self.name != other.name
294
295     def __gt__(self, other):
296         return self.name > other.name
297
298     def __ge__(self, other):
299         return self.name >= other.name
300
301     __hash__ = KickstartObject.__hash__
302
303 class Packages(KickstartObject):
304     _ver = version.DEVEL
305
306     """A class representing the %packages section of the kickstart file."""
307     def __init__(self, *args, **kwargs):
308         """Create a new Packages instance.  Instance attributes:
309
310            addBase       -- Should the Base group be installed even if it is
311                             not specified?
312            nocore        -- Should the Core group be skipped?  This results in
313                             a %packages section that basically only installs the
314                             packages you list, and may not be a usable system.
315            default       -- Should the default package set be selected?
316            environment   -- What base environment should be selected?  Only one
317                             may be chosen at a time.
318            excludedList  -- A list of all the packages marked for exclusion in
319                             the %packages section, without the leading minus
320                             symbol.
321            excludeDocs   -- Should documentation in each package be excluded?
322            groupList     -- A list of Group objects representing all the groups
323                             specified in the %packages section.  Names will be
324                             stripped of the leading @ symbol.
325            excludedGroupList -- A list of Group objects representing all the
326                                 groups specified for removal in the %packages
327                                 section.  Names will be stripped of the leading
328                                 -@ symbols.
329            handleMissing -- If unknown packages are specified in the %packages
330                             section, should it be ignored or not?  Values can
331                             be KS_MISSING_* from pykickstart.constants.
332            handleBroken  -- If packages with conflicts are specified in the
333                             %packages section, should it be ignored or not?
334                             Values can be KS_BROKEN_* from pykickstart.constants.
335            packageList   -- A list of all the packages specified in the
336                             %packages section.
337            instLangs     -- A list of languages to install.
338            multiLib      -- Whether to use yum's "all" multilib policy.
339            excludeWeakdeps -- Whether to exclude weak dependencies.
340            timeout       -- Number of seconds to wait for a connection before
341                             yum's or dnf's timing out or None.
342            retries       -- Number of times yum's or dnf's attempt to retrieve
343                             a file should retry before returning an error.
344            seen          -- If %packages was ever used in the kickstart file,
345                             this attribute will be set to True.
346
347         """
348         KickstartObject.__init__(self, *args, **kwargs)
349
350         self.addBase = True
351         self.nocore = False
352         self.default = False
353         self.environment = None
354         self.excludedList = []
355         self.excludedGroupList = []
356         self.excludeDocs = False
357         self.groupList = []
358         self.handleMissing = constants.KS_MISSING_PROMPT
359         self.handleBroken = constants.KS_BROKEN_REPORT
360         self.packageList = []
361         self.tpk_packageList = []
362         self.instLangs = None
363         self.multiLib = False
364         self.excludeWeakdeps = False
365         self.timeout = None
366         self.retries = None
367         self.seen = False
368
369     def __str__(self):
370         """Return a string formatted for output to a kickstart file."""
371         pkgs = self._processPackagesContent()
372         retval = ""
373
374         if self.default:
375             retval += " --default"
376         if self.excludeDocs:
377             retval += " --excludedocs"
378         if not self.addBase:
379             retval += " --nobase"
380         if self.nocore:
381             retval += " --nocore"
382         if self.handleMissing == constants.KS_MISSING_IGNORE:
383             retval += " --ignoremissing"
384         if self.handleBroken == constants.KS_BROKEN_IGNORE:
385             retval += " --ignorebroken"
386         if self.instLangs is not None:
387             retval += " --inst-langs=%s" % self.instLangs
388         if self.multiLib:
389             retval += " --multilib"
390         if self.excludeWeakdeps:
391             retval += " --exclude-weakdeps"
392         if self.timeout is not None:
393             retval += " --timeout=%d" % self.timeout
394         if self.retries is not None:
395             retval += " --retries=%d" % self.retries
396
397         if retval == "" and pkgs == "" and not self.seen:
398             return ""
399
400         if self._ver >= version.F8:
401             return "\n%packages" + retval + "\n" + pkgs + "\n%end\n"
402         else:
403             return "\n%packages" + retval + "\n" + pkgs + "\n"
404
405     def _processPackagesContent(self):
406         pkgs = ""
407
408         if not self.default:
409             if self.environment:
410                 pkgs += "@^%s\n" % self.environment
411
412         grps = self.groupList
413         grps.sort()
414         for grp in grps:
415             pkgs += "%s\n" % grp.__str__()
416
417         p = self.packageList
418         p.sort()
419         for pkg in p:
420             pkgs += "%s\n" % pkg
421
422         grps = self.excludedGroupList
423         grps.sort()
424         for grp in grps:
425             pkgs += "-%s\n" % grp.__str__()
426
427         p = self.excludedList
428         p.sort()
429         for pkg in p:
430             pkgs += "-%s\n" % pkg
431
432         return pkgs
433
434     def _processGroup(self, line):
435         op = KSOptionParser(prog="", description="", version=version.DEVEL)
436         op.add_argument("--nodefaults", action="store_true", default=False,
437                         help="", version=version.DEVEL)
438         op.add_argument("--optional", action="store_true", default=False,
439                         help="", version=version.DEVEL)
440
441         (ns, extra) = op.parse_known_args(args=line.split())
442
443         if ns.nodefaults and ns.optional:
444             raise KickstartParseError(_("Group cannot specify both --nodefaults and --optional"))
445
446         # If the group name has spaces in it, we have to put it back together
447         # now.
448         grp = " ".join(extra)
449
450         if grp in [g.name for g in self.groupList]:
451             return
452
453         if ns.nodefaults:
454             self.groupList.append(Group(name=grp, include=constants.GROUP_REQUIRED))
455         elif ns.optional:
456             self.groupList.append(Group(name=grp, include=constants.GROUP_ALL))
457         else:
458             self.groupList.append(Group(name=grp, include=constants.GROUP_DEFAULT))
459
460     def add(self, pkgList):
461         """Given a list of lines from the input file, strip off any leading
462            symbols and add the result to the appropriate list.
463         """
464         existingExcludedSet = set(self.excludedList)
465         existingPackageSet = set(self.packageList)
466         newExcludedSet = set()
467         newPackageSet = set()
468
469         excludedGroupList = []
470
471         for pkg in pkgList:
472             stripped = pkg.strip()
473
474             if stripped[0:2] == "@^":
475                 self.environment = stripped[2:]
476             elif stripped[0] == "@":
477                 self._processGroup(stripped[1:])
478             elif stripped[0] == "-":
479                 if stripped[1:3] == "@^" and self.environment == stripped[3:]:
480                     self.environment = None
481                 elif stripped[1] == "@":
482                     excludedGroupList.append(Group(name=stripped[2:]))
483                 else:
484                     newExcludedSet.add(stripped[1:])
485             else:
486                 newPackageSet.add(stripped)
487
488         # Groups have to be excluded in two different ways (note: can't use
489         # sets here because we have to store objects):
490         excludedGroupNames = [g.name for g in excludedGroupList]
491
492         # First, an excluded group may be cancelling out a previously given
493         # one.  This is often the case when using %include.  So there we should
494         # just remove the group from the list.
495         self.groupList = [g for g in self.groupList if g.name not in excludedGroupNames]
496
497         # Second, the package list could have included globs which are not
498         # processed by pykickstart.  In that case we need to preserve a list of
499         # excluded groups so whatever tool doing package/group installation can
500         # take appropriate action.
501         self.excludedGroupList.extend(excludedGroupList)
502
503         existingPackageSet = (existingPackageSet - newExcludedSet) | newPackageSet
504         existingExcludedSet = (existingExcludedSet - existingPackageSet) | newExcludedSet
505
506         # FIXME: figure these types out
507         self.packageList = sorted(existingPackageSet)
508         self.excludedList = sorted(existingExcludedSet)
509
510 class TpkPackages(KickstartObject):
511     """A class representing the %tpk_packages section of the kickstart file."""
512     def __init__(self, *args, **kwargs):
513         KickstartObject.__init__(self, *args, **kwargs)
514         self.tpk_packageList = []
515     def __str__(self):
516         tpk_pkgs = ""
517         retval = "\n%tpk_packages"
518         p = self.packageList
519         p.sort()
520         for pkg in p:
521             tpk_pkgs += "%s\n" % pkg
522         return retval + "\n" +tpk_pkgs
523     def add(self, tpkPackageList):
524         tpk_PackageSet = set(self.tpk_packageList)
525         for tpk_pkg in tpkPackageList:
526             stripped = tpk_pkg.strip()
527             tpk_PackageSet.add(stripped)
528         self.tpk_packageList = list(tpk_PackageSet)
529 ###
530 ### PARSER
531 ###
532 class KickstartParser(object):
533     """The kickstart file parser class as represented by a basic state
534        machine.  To create a specialized parser, make a subclass and override
535        any of the methods you care about.  Methods that don't need to do
536        anything may just pass.  However, _stateMachine should never be
537        overridden.
538     """
539     def __init__(self, handler, followIncludes=True, errorsAreFatal=True,
540                  missingIncludeIsFatal=True, unknownSectionIsFatal=True):
541         """Create a new KickstartParser instance.  Instance attributes:
542
543            errorsAreFatal        -- Should errors cause processing to halt, or
544                                     just print a message to the screen?  This
545                                     is most useful for writing syntax checkers
546                                     that may want to continue after an error is
547                                     encountered.
548            followIncludes        -- If %include is seen, should the included
549                                     file be checked as well or skipped?
550            handler               -- An instance of a BaseHandler subclass.  If
551                                     None, the input file will still be parsed
552                                     but no data will be saved and no commands
553                                     will be executed.
554            missingIncludeIsFatal -- Should missing include files be fatal, even
555                                     if errorsAreFatal is False?
556            unknownSectionIsFatal -- Should an unknown %section be fatal?  Not all
557                                     sections are handled by pykickstart.  Some are
558                                     user-defined, so there should be a way to have
559                                     pykickstart ignore them.
560         """
561         self.errorsAreFatal = errorsAreFatal
562         self.errorsCount = 0
563         self.followIncludes = followIncludes
564         self.handler = handler
565         self.currentdir = {}
566         self.missingIncludeIsFatal = missingIncludeIsFatal
567         self.unknownSectionIsFatal = unknownSectionIsFatal
568
569         self._state = STATE_COMMANDS
570         self._includeDepth = 0
571         self._line = ""
572
573         self.version = self.handler.version
574         Script._ver = self.version
575         Packages._ver = self.version
576
577         self._sections = {}
578         self.setupSections()
579
580     def _reset(self):
581         """Reset the internal variables of the state machine for a new kickstart file."""
582         self._state = STATE_COMMANDS
583         self._includeDepth = 0
584
585     def getSection(self, s):
586         """Return a reference to the requested section (s must start with '%'s),
587            or raise KeyError if not found.
588         """
589         return self._sections[s]
590
591     def handleCommand(self, lineno, args):
592         """Given the list of command and arguments, call the Version's
593            dispatcher method to handle the command.  Returns the command or
594            data object returned by the dispatcher.  This method may be
595            overridden in a subclass if necessary.
596         """
597         if self.handler:
598             self.handler.currentLine = self._line
599             retval = self.handler.dispatcher(args, lineno)
600             return retval
601
602     def registerSection(self, obj):
603         """Given an instance of a Section subclass, register the new section
604            with the parser.  Calling this method means the parser will
605            recognize your new section and dispatch into the given object to
606            handle it.
607         """
608         if not obj.sectionOpen:
609             raise TypeError("no sectionOpen given for section %s" % obj)
610
611         if not obj.sectionOpen.startswith("%"):
612             raise TypeError("section %s tag does not start with a %%" % obj.sectionOpen)
613
614         self._sections[obj.sectionOpen] = obj
615
616     def _finalize(self, obj):
617         """Called at the close of a kickstart section to take any required
618            actions.  Internally, this is used to add scripts once we have the
619            whole body read.
620         """
621         obj.finalize()
622         self._state = STATE_COMMANDS
623
624     def _handleSpecialComments(self, line):
625         """Kickstart recognizes a couple special comments."""
626         if self._state != STATE_COMMANDS:
627             return
628
629         # Save the platform for s-c-kickstart.
630         if line[:10] == "#platform=":
631             self.handler.platform = self._line[10:].strip()
632
633     def _readSection(self, lineIter, lineno):
634         obj = self._sections[self._state]
635
636         while True:
637             try:
638                 line = next(lineIter)
639                 if line == "" and self._includeDepth == 0:
640                     # This section ends at the end of the file.
641                     if self.version >= version.F8:
642                         raise KickstartParseError(_("Section %s does not end with %%end.") % obj.sectionOpen, lineno=lineno)
643
644                     self._finalize(obj)
645             except StopIteration:
646                 break
647
648             lineno += 1
649
650             # Throw away blank lines and comments, unless the section wants all
651             # lines.
652             if self._isBlankOrComment(line) and not obj.allLines:
653                 continue
654
655             if line.lstrip().startswith("%"):
656                 # If we're in a script, the line may begin with "%something"
657                 # that's not the start of any section we recognize, but still
658                 # valid for that script.  So, don't do the split below unless
659                 # we're sure.
660                 possibleSectionStart = line.split()[0]
661                 if not self._validState(possibleSectionStart) \
662                    and possibleSectionStart not in ("%end", "%include"):
663                     obj.handleLine(line)
664                     continue
665
666                 args = shlex.split(line)
667
668                 if args and args[0] == "%end":
669                     # This is a properly terminated section.
670                     self._finalize(obj)
671                     break
672                 elif args and args[0] == "%include":
673                     if len(args) == 1 or not args[1]:
674                         raise KickstartParseError(lineno=lineno)
675
676                     self._handleInclude(args[1])
677                     continue
678                 elif args and args[0] == "%ksappend":
679                     continue
680                 elif args and self._validState(args[0]):
681                     # This is an unterminated section.
682                     if self.version >= version.F8:
683                         raise KickstartParseError(_("Section %s does not end with %%end.") % obj.sectionOpen, lineno=lineno)
684
685                     # Finish up.  We do not process the header here because
686                     # kicking back out to STATE_COMMANDS will ensure that happens.
687                     lineIter.put(line)
688                     lineno -= 1
689                     self._finalize(obj)
690                     break
691             else:
692                 # This is just a line within a section.  Pass it off to whatever
693                 # section handles it.
694                 obj.handleLine(line)
695
696         return lineno
697
698     def _validState(self, st):
699         """Is the given section tag one that has been registered with the parser?"""
700         return st in list(self._sections.keys())
701
702     def _tryFunc(self, fn):
703         """Call the provided function (which doesn't take any arguments) and
704            do the appropriate error handling.  If errorsAreFatal is False, this
705            function will just print the exception and keep going.
706         """
707         try:
708             fn()
709         except Exception as msg:    # pylint: disable=broad-except
710             self.errorsCount += 1
711             if self.errorsAreFatal:
712                 raise
713             else:
714                 print(msg, file=sys.stderr)
715
716     def _isBlankOrComment(self, line):
717         return line.isspace() or line == "" or line.lstrip()[0] == '#'
718
719     def _handleInclude(self, f):
720         # This case comes up primarily in ksvalidator.
721         if not self.followIncludes:
722             return
723
724         self._includeDepth += 1
725
726         try:
727             self.readKickstart(f, reset=False)
728         except KickstartError:
729             # Handle the include file being provided over the
730             # network in a %pre script.  This case comes up in the
731             # early parsing in anaconda.
732             if self.missingIncludeIsFatal:
733                 raise
734
735         self._includeDepth -= 1
736
737     def _stateMachine(self, lineIter):
738         # For error reporting.
739         lineno = 0
740
741         while True:
742             # Get the next line out of the file, quitting if this is the last line.
743             try:
744                 self._line = next(lineIter)
745                 if self._line == "":
746                     break
747             except StopIteration:
748                 break
749
750             lineno += 1
751
752             # Eliminate blank lines, whitespace-only lines, and comments.
753             if self._isBlankOrComment(self._line):
754                 self._handleSpecialComments(self._line)
755                 continue
756
757             # Split the line, discarding comments.
758             args = shlex.split(self._line, comments=True)
759
760             if args[0] == "%include":
761                 if len(args) == 1 or not args[1]:
762                     raise KickstartParseError(lineno=lineno)
763
764                 self._handleInclude(args[1])
765                 continue
766
767             # Now on to the main event.
768             if self._state == STATE_COMMANDS:
769                 if args[0] == "%ksappend":
770                     # This is handled by the preprocess* functions, so continue.
771                     continue
772                 elif args[0][0] == '%':
773                     # This is the beginning of a new section.  Handle its header
774                     # here.
775                     newSection = args[0]
776                     if not self._validState(newSection):
777                         if self.unknownSectionIsFatal:
778                             raise KickstartParseError(_("Unknown kickstart section: %s") % newSection, lineno=lineno)
779                         else:
780                             # If we are ignoring unknown section errors, just create a new
781                             # NullSection for the header we just saw.  Then nothing else
782                             # needs to change.  You can turn this warning into an error via
783                             # ksvalidator, or the warnings module.
784                             warnings.warn(_("Potentially unknown section seen at line %(lineno)s: %(sectionName)s") % {"lineno": lineno, "sectionName": newSection}, KickstartParseWarning)
785                             self.registerSection(NullSection(self.handler, sectionOpen=newSection))
786
787                     self._state = newSection
788                     obj = self._sections[self._state]
789                     self._tryFunc(lambda: obj.handleHeader(lineno, args))
790
791                     # This will handle all section processing, kicking us back
792                     # out to STATE_COMMANDS at the end with the current line
793                     # being the next section header, etc.
794                     lineno = self._readSection(lineIter, lineno)
795                 else:
796                     # This is a command in the command section.  Dispatch to it.
797                     self._tryFunc(lambda: self.handleCommand(lineno, args))
798             elif self._state == STATE_END:
799                 break
800             elif self._includeDepth > 0:
801                 lineIter.put(self._line)
802                 lineno -= 1
803                 lineno = self._readSection(lineIter, lineno)
804
805     def readKickstartFromString(self, s, reset=True):
806         """Process a kickstart file, provided as the string str."""
807         if reset:
808             self._reset()
809
810         # Add a "" to the end of the list so the string reader acts like the
811         # file reader and we only get StopIteration when we're after the final
812         # line of input.
813         i = PutBackIterator(s.splitlines(True) + [""])
814         self._stateMachine(i)
815
816     def readKickstart(self, f, reset=True):
817         """Process a kickstart file, given by the filename f."""
818         if reset:
819             self._reset()
820
821         # an %include might not specify a full path.  if we don't try to figure
822         # out what the path should have been, then we're unable to find it
823         # requiring full path specification, though, sucks.  so let's make
824         # the reading "smart" by keeping track of what the path is at each
825         # include depth.
826         if not os.path.exists(f):
827             if self._includeDepth - 1 in self.currentdir:
828                 if os.path.exists(os.path.join(self.currentdir[self._includeDepth - 1], f)):
829                     f = os.path.join(self.currentdir[self._includeDepth - 1], f)
830
831         cd = os.path.dirname(f)
832         if not cd.startswith("/"):
833             cd = os.path.abspath(cd)
834         self.currentdir[self._includeDepth] = cd
835
836         try:
837             s = load_to_str(f)
838         except KickstartError as e:
839             raise KickstartError(_("Unable to open input kickstart file: %s") % str(e), lineno=0)
840
841         self.readKickstartFromString(s, reset=False)
842
843     def setupSections(self):
844         """Install the sections all kickstart files support.  You may override
845            this method in a subclass, but should avoid doing so unless you know
846            what you're doing.
847         """
848         self._sections = {}
849
850         # Install the sections all kickstart files support.
851         self.registerSection(PreScriptSection(self.handler, dataObj=Script))
852         self.registerSection(PreInstallScriptSection(self.handler, dataObj=Script))
853         self.registerSection(PostScriptSection(self.handler, dataObj=Script))
854         self.registerSection(OnErrorScriptSection(self.handler, dataObj=Script))
855         self.registerSection(TracebackScriptSection(self.handler, dataObj=Script))
856         self.registerSection(RunScriptSection(self.handler, dataObj=Script))
857         self.registerSection(PostUmountScriptSection(self.handler, dataObj=Script))
858         self.registerSection(PackageSection(self.handler))
859         self.registerSection(TpkPackageSection(self.handler))
860
861         # Whitelist well-known sections that pykickstart does not understand,
862         # but shouldn't error on.
863         self.registerSection(NullSection(self.handler, sectionOpen="%addon"))
864         self.registerSection(NullSection(self.handler, sectionOpen="%anaconda"))