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