^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) #!/usr/bin/env python3
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) # -*- coding: utf-8; mode: python -*-
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) # pylint: disable=R0903, C0330, R0914, R0912, E0401
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) u"""
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) kernel-include
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) ~~~~~~~~~~~~~~
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) Implementation of the ``kernel-include`` reST-directive.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) :copyright: Copyright (C) 2016 Markus Heiser
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) :license: GPL Version 2, June 1991 see linux/COPYING for details.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) The ``kernel-include`` reST-directive is a replacement for the ``include``
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) directive. The ``kernel-include`` directive expand environment variables in
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) the path name and allows to include files from arbitrary locations.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) .. hint::
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) Including files from arbitrary locations (e.g. from ``/etc``) is a
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) security risk for builders. This is why the ``include`` directive from
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) docutils *prohibit* pathnames pointing to locations *above* the filesystem
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) tree where the reST document with the include directive is placed.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) Substrings of the form $name or ${name} are replaced by the value of
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) environment variable name. Malformed variable names and references to
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) non-existing variables are left unchanged.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) """
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) # ==============================================================================
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) # imports
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) # ==============================================================================
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) import os.path
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) from docutils import io, nodes, statemachine
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) from docutils.utils.error_reporting import SafeString, ErrorString
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) from docutils.parsers.rst import directives
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) from docutils.parsers.rst.directives.body import CodeBlock, NumberLines
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) from docutils.parsers.rst.directives.misc import Include
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) __version__ = '1.0'
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) # ==============================================================================
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) def setup(app):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) # ==============================================================================
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) app.add_directive("kernel-include", KernelInclude)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) return dict(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) version = __version__,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) parallel_read_safe = True,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) parallel_write_safe = True
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) )
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) # ==============================================================================
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) class KernelInclude(Include):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) # ==============================================================================
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) u"""KernelInclude (``kernel-include``) directive"""
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) def run(self):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) path = os.path.realpath(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) os.path.expandvars(self.arguments[0]))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) # to get a bit security back, prohibit /etc:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) if path.startswith(os.sep + "etc"):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) raise self.severe(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) 'Problems with "%s" directive, prohibited path: %s'
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) % (self.name, path))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) self.arguments[0] = path
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) #return super(KernelInclude, self).run() # won't work, see HINTs in _run()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) return self._run()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) def _run(self):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) """Include a file as part of the content of this reST file."""
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) # HINT: I had to copy&paste the whole Include.run method. I'am not happy
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) # with this, but due to security reasons, the Include.run method does
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) # not allow absolute or relative pathnames pointing to locations *above*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) # the filesystem tree where the reST document is placed.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) if not self.state.document.settings.file_insertion_enabled:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) raise self.warning('"%s" directive disabled.' % self.name)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) source = self.state_machine.input_lines.source(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) self.lineno - self.state_machine.input_offset - 1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) source_dir = os.path.dirname(os.path.abspath(source))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) path = directives.path(self.arguments[0])
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) if path.startswith('<') and path.endswith('>'):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) path = os.path.join(self.standard_include_path, path[1:-1])
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) path = os.path.normpath(os.path.join(source_dir, path))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) # HINT: this is the only line I had to change / commented out:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) #path = utils.relative_path(None, path)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) path = nodes.reprunicode(path)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) encoding = self.options.get(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) 'encoding', self.state.document.settings.input_encoding)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) e_handler=self.state.document.settings.input_encoding_error_handler
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) tab_width = self.options.get(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) 'tab-width', self.state.document.settings.tab_width)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) try:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) self.state.document.settings.record_dependencies.add(path)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) include_file = io.FileInput(source_path=path,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) encoding=encoding,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) error_handler=e_handler)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) except UnicodeEncodeError as error:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) raise self.severe('Problems with "%s" directive path:\n'
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) 'Cannot encode input file path "%s" '
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) '(wrong locale?).' %
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) (self.name, SafeString(path)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) except IOError as error:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) raise self.severe('Problems with "%s" directive path:\n%s.' %
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) (self.name, ErrorString(error)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) startline = self.options.get('start-line', None)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) endline = self.options.get('end-line', None)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) try:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) if startline or (endline is not None):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) lines = include_file.readlines()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) rawtext = ''.join(lines[startline:endline])
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) rawtext = include_file.read()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) except UnicodeError as error:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) raise self.severe('Problem with "%s" directive:\n%s' %
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) (self.name, ErrorString(error)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) # start-after/end-before: no restrictions on newlines in match-text,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) # and no restrictions on matching inside lines vs. line boundaries
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) after_text = self.options.get('start-after', None)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) if after_text:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) # skip content in rawtext before *and incl.* a matching text
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) after_index = rawtext.find(after_text)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) if after_index < 0:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) raise self.severe('Problem with "start-after" option of "%s" '
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) 'directive:\nText not found.' % self.name)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) rawtext = rawtext[after_index + len(after_text):]
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) before_text = self.options.get('end-before', None)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) if before_text:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) # skip content in rawtext after *and incl.* a matching text
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) before_index = rawtext.find(before_text)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) if before_index < 0:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) raise self.severe('Problem with "end-before" option of "%s" '
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) 'directive:\nText not found.' % self.name)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) rawtext = rawtext[:before_index]
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) include_lines = statemachine.string2lines(rawtext, tab_width,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) convert_whitespace=True)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) if 'literal' in self.options:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) # Convert tabs to spaces, if `tab_width` is positive.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) if tab_width >= 0:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) text = rawtext.expandtabs(tab_width)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) text = rawtext
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) literal_block = nodes.literal_block(rawtext, source=path,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) classes=self.options.get('class', []))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156) literal_block.line = 1
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) self.add_name(literal_block)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) if 'number-lines' in self.options:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) try:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) startline = int(self.options['number-lines'] or 1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) except ValueError:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) raise self.error(':number-lines: with non-integer '
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) 'start value')
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) endline = startline + len(include_lines)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) if text.endswith('\n'):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) text = text[:-1]
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) tokens = NumberLines([([], text)], startline, endline)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) for classes, value in tokens:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) if classes:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) literal_block += nodes.inline(value, value,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) classes=classes)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) literal_block += nodes.Text(value, value)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) literal_block += nodes.Text(text, text)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176) return [literal_block]
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) if 'code' in self.options:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) self.options['source'] = path
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) codeblock = CodeBlock(self.name,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) [self.options.pop('code')], # arguments
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181) self.options,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) include_lines, # content
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) self.lineno,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) self.content_offset,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) self.block_text,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) self.state,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) self.state_machine)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) return codeblock.run()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) self.state_machine.insert_input(include_lines, path)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) return []