18c2ecf20Sopenharmony_ci#!/usr/bin/env python3 28c2ecf20Sopenharmony_ci# -*- coding: utf-8; mode: python -*- 38c2ecf20Sopenharmony_ci# pylint: disable=R0903, C0330, R0914, R0912, E0401 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ciu""" 68c2ecf20Sopenharmony_ci kernel-include 78c2ecf20Sopenharmony_ci ~~~~~~~~~~~~~~ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci Implementation of the ``kernel-include`` reST-directive. 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci :copyright: Copyright (C) 2016 Markus Heiser 128c2ecf20Sopenharmony_ci :license: GPL Version 2, June 1991 see linux/COPYING for details. 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci The ``kernel-include`` reST-directive is a replacement for the ``include`` 158c2ecf20Sopenharmony_ci directive. The ``kernel-include`` directive expand environment variables in 168c2ecf20Sopenharmony_ci the path name and allows to include files from arbitrary locations. 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci .. hint:: 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci Including files from arbitrary locations (e.g. from ``/etc``) is a 218c2ecf20Sopenharmony_ci security risk for builders. This is why the ``include`` directive from 228c2ecf20Sopenharmony_ci docutils *prohibit* pathnames pointing to locations *above* the filesystem 238c2ecf20Sopenharmony_ci tree where the reST document with the include directive is placed. 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci Substrings of the form $name or ${name} are replaced by the value of 268c2ecf20Sopenharmony_ci environment variable name. Malformed variable names and references to 278c2ecf20Sopenharmony_ci non-existing variables are left unchanged. 288c2ecf20Sopenharmony_ci""" 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci# ============================================================================== 318c2ecf20Sopenharmony_ci# imports 328c2ecf20Sopenharmony_ci# ============================================================================== 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ciimport os.path 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cifrom docutils import io, nodes, statemachine 378c2ecf20Sopenharmony_cifrom docutils.utils.error_reporting import SafeString, ErrorString 388c2ecf20Sopenharmony_cifrom docutils.parsers.rst import directives 398c2ecf20Sopenharmony_cifrom docutils.parsers.rst.directives.body import CodeBlock, NumberLines 408c2ecf20Sopenharmony_cifrom docutils.parsers.rst.directives.misc import Include 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci__version__ = '1.0' 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci# ============================================================================== 458c2ecf20Sopenharmony_cidef setup(app): 468c2ecf20Sopenharmony_ci# ============================================================================== 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci app.add_directive("kernel-include", KernelInclude) 498c2ecf20Sopenharmony_ci return dict( 508c2ecf20Sopenharmony_ci version = __version__, 518c2ecf20Sopenharmony_ci parallel_read_safe = True, 528c2ecf20Sopenharmony_ci parallel_write_safe = True 538c2ecf20Sopenharmony_ci ) 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci# ============================================================================== 568c2ecf20Sopenharmony_ciclass KernelInclude(Include): 578c2ecf20Sopenharmony_ci# ============================================================================== 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci u"""KernelInclude (``kernel-include``) directive""" 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci def run(self): 628c2ecf20Sopenharmony_ci path = os.path.realpath( 638c2ecf20Sopenharmony_ci os.path.expandvars(self.arguments[0])) 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci # to get a bit security back, prohibit /etc: 668c2ecf20Sopenharmony_ci if path.startswith(os.sep + "etc"): 678c2ecf20Sopenharmony_ci raise self.severe( 688c2ecf20Sopenharmony_ci 'Problems with "%s" directive, prohibited path: %s' 698c2ecf20Sopenharmony_ci % (self.name, path)) 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci self.arguments[0] = path 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci #return super(KernelInclude, self).run() # won't work, see HINTs in _run() 748c2ecf20Sopenharmony_ci return self._run() 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci def _run(self): 778c2ecf20Sopenharmony_ci """Include a file as part of the content of this reST file.""" 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci # HINT: I had to copy&paste the whole Include.run method. I'am not happy 808c2ecf20Sopenharmony_ci # with this, but due to security reasons, the Include.run method does 818c2ecf20Sopenharmony_ci # not allow absolute or relative pathnames pointing to locations *above* 828c2ecf20Sopenharmony_ci # the filesystem tree where the reST document is placed. 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if not self.state.document.settings.file_insertion_enabled: 858c2ecf20Sopenharmony_ci raise self.warning('"%s" directive disabled.' % self.name) 868c2ecf20Sopenharmony_ci source = self.state_machine.input_lines.source( 878c2ecf20Sopenharmony_ci self.lineno - self.state_machine.input_offset - 1) 888c2ecf20Sopenharmony_ci source_dir = os.path.dirname(os.path.abspath(source)) 898c2ecf20Sopenharmony_ci path = directives.path(self.arguments[0]) 908c2ecf20Sopenharmony_ci if path.startswith('<') and path.endswith('>'): 918c2ecf20Sopenharmony_ci path = os.path.join(self.standard_include_path, path[1:-1]) 928c2ecf20Sopenharmony_ci path = os.path.normpath(os.path.join(source_dir, path)) 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci # HINT: this is the only line I had to change / commented out: 958c2ecf20Sopenharmony_ci #path = utils.relative_path(None, path) 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci path = nodes.reprunicode(path) 988c2ecf20Sopenharmony_ci encoding = self.options.get( 998c2ecf20Sopenharmony_ci 'encoding', self.state.document.settings.input_encoding) 1008c2ecf20Sopenharmony_ci e_handler=self.state.document.settings.input_encoding_error_handler 1018c2ecf20Sopenharmony_ci tab_width = self.options.get( 1028c2ecf20Sopenharmony_ci 'tab-width', self.state.document.settings.tab_width) 1038c2ecf20Sopenharmony_ci try: 1048c2ecf20Sopenharmony_ci self.state.document.settings.record_dependencies.add(path) 1058c2ecf20Sopenharmony_ci include_file = io.FileInput(source_path=path, 1068c2ecf20Sopenharmony_ci encoding=encoding, 1078c2ecf20Sopenharmony_ci error_handler=e_handler) 1088c2ecf20Sopenharmony_ci except UnicodeEncodeError as error: 1098c2ecf20Sopenharmony_ci raise self.severe('Problems with "%s" directive path:\n' 1108c2ecf20Sopenharmony_ci 'Cannot encode input file path "%s" ' 1118c2ecf20Sopenharmony_ci '(wrong locale?).' % 1128c2ecf20Sopenharmony_ci (self.name, SafeString(path))) 1138c2ecf20Sopenharmony_ci except IOError as error: 1148c2ecf20Sopenharmony_ci raise self.severe('Problems with "%s" directive path:\n%s.' % 1158c2ecf20Sopenharmony_ci (self.name, ErrorString(error))) 1168c2ecf20Sopenharmony_ci startline = self.options.get('start-line', None) 1178c2ecf20Sopenharmony_ci endline = self.options.get('end-line', None) 1188c2ecf20Sopenharmony_ci try: 1198c2ecf20Sopenharmony_ci if startline or (endline is not None): 1208c2ecf20Sopenharmony_ci lines = include_file.readlines() 1218c2ecf20Sopenharmony_ci rawtext = ''.join(lines[startline:endline]) 1228c2ecf20Sopenharmony_ci else: 1238c2ecf20Sopenharmony_ci rawtext = include_file.read() 1248c2ecf20Sopenharmony_ci except UnicodeError as error: 1258c2ecf20Sopenharmony_ci raise self.severe('Problem with "%s" directive:\n%s' % 1268c2ecf20Sopenharmony_ci (self.name, ErrorString(error))) 1278c2ecf20Sopenharmony_ci # start-after/end-before: no restrictions on newlines in match-text, 1288c2ecf20Sopenharmony_ci # and no restrictions on matching inside lines vs. line boundaries 1298c2ecf20Sopenharmony_ci after_text = self.options.get('start-after', None) 1308c2ecf20Sopenharmony_ci if after_text: 1318c2ecf20Sopenharmony_ci # skip content in rawtext before *and incl.* a matching text 1328c2ecf20Sopenharmony_ci after_index = rawtext.find(after_text) 1338c2ecf20Sopenharmony_ci if after_index < 0: 1348c2ecf20Sopenharmony_ci raise self.severe('Problem with "start-after" option of "%s" ' 1358c2ecf20Sopenharmony_ci 'directive:\nText not found.' % self.name) 1368c2ecf20Sopenharmony_ci rawtext = rawtext[after_index + len(after_text):] 1378c2ecf20Sopenharmony_ci before_text = self.options.get('end-before', None) 1388c2ecf20Sopenharmony_ci if before_text: 1398c2ecf20Sopenharmony_ci # skip content in rawtext after *and incl.* a matching text 1408c2ecf20Sopenharmony_ci before_index = rawtext.find(before_text) 1418c2ecf20Sopenharmony_ci if before_index < 0: 1428c2ecf20Sopenharmony_ci raise self.severe('Problem with "end-before" option of "%s" ' 1438c2ecf20Sopenharmony_ci 'directive:\nText not found.' % self.name) 1448c2ecf20Sopenharmony_ci rawtext = rawtext[:before_index] 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci include_lines = statemachine.string2lines(rawtext, tab_width, 1478c2ecf20Sopenharmony_ci convert_whitespace=True) 1488c2ecf20Sopenharmony_ci if 'literal' in self.options: 1498c2ecf20Sopenharmony_ci # Convert tabs to spaces, if `tab_width` is positive. 1508c2ecf20Sopenharmony_ci if tab_width >= 0: 1518c2ecf20Sopenharmony_ci text = rawtext.expandtabs(tab_width) 1528c2ecf20Sopenharmony_ci else: 1538c2ecf20Sopenharmony_ci text = rawtext 1548c2ecf20Sopenharmony_ci literal_block = nodes.literal_block(rawtext, source=path, 1558c2ecf20Sopenharmony_ci classes=self.options.get('class', [])) 1568c2ecf20Sopenharmony_ci literal_block.line = 1 1578c2ecf20Sopenharmony_ci self.add_name(literal_block) 1588c2ecf20Sopenharmony_ci if 'number-lines' in self.options: 1598c2ecf20Sopenharmony_ci try: 1608c2ecf20Sopenharmony_ci startline = int(self.options['number-lines'] or 1) 1618c2ecf20Sopenharmony_ci except ValueError: 1628c2ecf20Sopenharmony_ci raise self.error(':number-lines: with non-integer ' 1638c2ecf20Sopenharmony_ci 'start value') 1648c2ecf20Sopenharmony_ci endline = startline + len(include_lines) 1658c2ecf20Sopenharmony_ci if text.endswith('\n'): 1668c2ecf20Sopenharmony_ci text = text[:-1] 1678c2ecf20Sopenharmony_ci tokens = NumberLines([([], text)], startline, endline) 1688c2ecf20Sopenharmony_ci for classes, value in tokens: 1698c2ecf20Sopenharmony_ci if classes: 1708c2ecf20Sopenharmony_ci literal_block += nodes.inline(value, value, 1718c2ecf20Sopenharmony_ci classes=classes) 1728c2ecf20Sopenharmony_ci else: 1738c2ecf20Sopenharmony_ci literal_block += nodes.Text(value, value) 1748c2ecf20Sopenharmony_ci else: 1758c2ecf20Sopenharmony_ci literal_block += nodes.Text(text, text) 1768c2ecf20Sopenharmony_ci return [literal_block] 1778c2ecf20Sopenharmony_ci if 'code' in self.options: 1788c2ecf20Sopenharmony_ci self.options['source'] = path 1798c2ecf20Sopenharmony_ci codeblock = CodeBlock(self.name, 1808c2ecf20Sopenharmony_ci [self.options.pop('code')], # arguments 1818c2ecf20Sopenharmony_ci self.options, 1828c2ecf20Sopenharmony_ci include_lines, # content 1838c2ecf20Sopenharmony_ci self.lineno, 1848c2ecf20Sopenharmony_ci self.content_offset, 1858c2ecf20Sopenharmony_ci self.block_text, 1868c2ecf20Sopenharmony_ci self.state, 1878c2ecf20Sopenharmony_ci self.state_machine) 1888c2ecf20Sopenharmony_ci return codeblock.run() 1898c2ecf20Sopenharmony_ci self.state_machine.insert_input(include_lines, path) 1908c2ecf20Sopenharmony_ci return [] 191