103776378SMarkus Heiser#!/usr/bin/env python3
203776378SMarkus Heiser# -*- coding: utf-8; mode: python -*-
303776378SMarkus Heiser# pylint: disable=R0903, C0330, R0914, R0912, E0401
403776378SMarkus Heiser
5*8def4042SMauro Carvalho Chehab"""
603776378SMarkus Heiser    kernel-include
703776378SMarkus Heiser    ~~~~~~~~~~~~~~
803776378SMarkus Heiser
903776378SMarkus Heiser    Implementation of the ``kernel-include`` reST-directive.
1003776378SMarkus Heiser
1103776378SMarkus Heiser    :copyright:  Copyright (C) 2016  Markus Heiser
1203776378SMarkus Heiser    :license:    GPL Version 2, June 1991 see linux/COPYING for details.
1303776378SMarkus Heiser
1403776378SMarkus Heiser    The ``kernel-include`` reST-directive is a replacement for the ``include``
1503776378SMarkus Heiser    directive. The ``kernel-include`` directive expand environment variables in
1603776378SMarkus Heiser    the path name and allows to include files from arbitrary locations.
1703776378SMarkus Heiser
1803776378SMarkus Heiser    .. hint::
1903776378SMarkus Heiser
2003776378SMarkus Heiser      Including files from arbitrary locations (e.g. from ``/etc``) is a
2103776378SMarkus Heiser      security risk for builders. This is why the ``include`` directive from
2203776378SMarkus Heiser      docutils *prohibit* pathnames pointing to locations *above* the filesystem
2303776378SMarkus Heiser      tree where the reST document with the include directive is placed.
2403776378SMarkus Heiser
2503776378SMarkus Heiser    Substrings of the form $name or ${name} are replaced by the value of
2603776378SMarkus Heiser    environment variable name. Malformed variable names and references to
2703776378SMarkus Heiser    non-existing variables are left unchanged.
2803776378SMarkus Heiser"""
2903776378SMarkus Heiser
3003776378SMarkus Heiser# ==============================================================================
3103776378SMarkus Heiser# imports
3203776378SMarkus Heiser# ==============================================================================
3303776378SMarkus Heiser
3403776378SMarkus Heiserimport os.path
3503776378SMarkus Heiser
3603776378SMarkus Heiserfrom docutils import io, nodes, statemachine
3703776378SMarkus Heiserfrom docutils.utils.error_reporting import SafeString, ErrorString
3803776378SMarkus Heiserfrom docutils.parsers.rst import directives
3903776378SMarkus Heiserfrom docutils.parsers.rst.directives.body import CodeBlock, NumberLines
4003776378SMarkus Heiserfrom docutils.parsers.rst.directives.misc import Include
4103776378SMarkus Heiser
42b62b9d81SMarkus Heiser__version__  = '1.0'
43b62b9d81SMarkus Heiser
4403776378SMarkus Heiser# ==============================================================================
4503776378SMarkus Heiserdef setup(app):
4603776378SMarkus Heiser# ==============================================================================
4703776378SMarkus Heiser
4803776378SMarkus Heiser    app.add_directive("kernel-include", KernelInclude)
49b62b9d81SMarkus Heiser    return dict(
50b62b9d81SMarkus Heiser        version = __version__,
51b62b9d81SMarkus Heiser        parallel_read_safe = True,
52b62b9d81SMarkus Heiser        parallel_write_safe = True
53b62b9d81SMarkus Heiser    )
5403776378SMarkus Heiser
5503776378SMarkus Heiser# ==============================================================================
5603776378SMarkus Heiserclass KernelInclude(Include):
5703776378SMarkus Heiser# ==============================================================================
5803776378SMarkus Heiser
59*8def4042SMauro Carvalho Chehab    """KernelInclude (``kernel-include``) directive"""
6003776378SMarkus Heiser
6103776378SMarkus Heiser    def run(self):
62bcf0a536SMauro Carvalho Chehab        env = self.state.document.settings.env
6303776378SMarkus Heiser        path = os.path.realpath(
6403776378SMarkus Heiser            os.path.expandvars(self.arguments[0]))
6503776378SMarkus Heiser
6603776378SMarkus Heiser        # to get a bit security back, prohibit /etc:
6703776378SMarkus Heiser        if path.startswith(os.sep + "etc"):
6803776378SMarkus Heiser            raise self.severe(
6903776378SMarkus Heiser                'Problems with "%s" directive, prohibited path: %s'
7003776378SMarkus Heiser                % (self.name, path))
7103776378SMarkus Heiser
7203776378SMarkus Heiser        self.arguments[0] = path
7303776378SMarkus Heiser
74bcf0a536SMauro Carvalho Chehab        env.note_dependency(os.path.abspath(path))
75bcf0a536SMauro Carvalho Chehab
7603776378SMarkus Heiser        #return super(KernelInclude, self).run() # won't work, see HINTs in _run()
7703776378SMarkus Heiser        return self._run()
7803776378SMarkus Heiser
7903776378SMarkus Heiser    def _run(self):
8003776378SMarkus Heiser        """Include a file as part of the content of this reST file."""
8103776378SMarkus Heiser
8203776378SMarkus Heiser        # HINT: I had to copy&paste the whole Include.run method. I'am not happy
8303776378SMarkus Heiser        # with this, but due to security reasons, the Include.run method does
8403776378SMarkus Heiser        # not allow absolute or relative pathnames pointing to locations *above*
8503776378SMarkus Heiser        # the filesystem tree where the reST document is placed.
8603776378SMarkus Heiser
8703776378SMarkus Heiser        if not self.state.document.settings.file_insertion_enabled:
8803776378SMarkus Heiser            raise self.warning('"%s" directive disabled.' % self.name)
8903776378SMarkus Heiser        source = self.state_machine.input_lines.source(
9003776378SMarkus Heiser            self.lineno - self.state_machine.input_offset - 1)
9103776378SMarkus Heiser        source_dir = os.path.dirname(os.path.abspath(source))
9203776378SMarkus Heiser        path = directives.path(self.arguments[0])
9303776378SMarkus Heiser        if path.startswith('<') and path.endswith('>'):
9403776378SMarkus Heiser            path = os.path.join(self.standard_include_path, path[1:-1])
9503776378SMarkus Heiser        path = os.path.normpath(os.path.join(source_dir, path))
9603776378SMarkus Heiser
9703776378SMarkus Heiser        # HINT: this is the only line I had to change / commented out:
9803776378SMarkus Heiser        #path = utils.relative_path(None, path)
9903776378SMarkus Heiser
10003776378SMarkus Heiser        encoding = self.options.get(
10103776378SMarkus Heiser            'encoding', self.state.document.settings.input_encoding)
10203776378SMarkus Heiser        e_handler=self.state.document.settings.input_encoding_error_handler
10303776378SMarkus Heiser        tab_width = self.options.get(
10403776378SMarkus Heiser            'tab-width', self.state.document.settings.tab_width)
10503776378SMarkus Heiser        try:
10603776378SMarkus Heiser            self.state.document.settings.record_dependencies.add(path)
10703776378SMarkus Heiser            include_file = io.FileInput(source_path=path,
10803776378SMarkus Heiser                                        encoding=encoding,
10903776378SMarkus Heiser                                        error_handler=e_handler)
11003776378SMarkus Heiser        except UnicodeEncodeError as error:
11103776378SMarkus Heiser            raise self.severe('Problems with "%s" directive path:\n'
11203776378SMarkus Heiser                              'Cannot encode input file path "%s" '
11303776378SMarkus Heiser                              '(wrong locale?).' %
11403776378SMarkus Heiser                              (self.name, SafeString(path)))
11503776378SMarkus Heiser        except IOError as error:
11603776378SMarkus Heiser            raise self.severe('Problems with "%s" directive path:\n%s.' %
11703776378SMarkus Heiser                      (self.name, ErrorString(error)))
11803776378SMarkus Heiser        startline = self.options.get('start-line', None)
11903776378SMarkus Heiser        endline = self.options.get('end-line', None)
12003776378SMarkus Heiser        try:
12103776378SMarkus Heiser            if startline or (endline is not None):
12203776378SMarkus Heiser                lines = include_file.readlines()
12303776378SMarkus Heiser                rawtext = ''.join(lines[startline:endline])
12403776378SMarkus Heiser            else:
12503776378SMarkus Heiser                rawtext = include_file.read()
12603776378SMarkus Heiser        except UnicodeError as error:
12703776378SMarkus Heiser            raise self.severe('Problem with "%s" directive:\n%s' %
12803776378SMarkus Heiser                              (self.name, ErrorString(error)))
12903776378SMarkus Heiser        # start-after/end-before: no restrictions on newlines in match-text,
13003776378SMarkus Heiser        # and no restrictions on matching inside lines vs. line boundaries
13103776378SMarkus Heiser        after_text = self.options.get('start-after', None)
13203776378SMarkus Heiser        if after_text:
13303776378SMarkus Heiser            # skip content in rawtext before *and incl.* a matching text
13403776378SMarkus Heiser            after_index = rawtext.find(after_text)
13503776378SMarkus Heiser            if after_index < 0:
13603776378SMarkus Heiser                raise self.severe('Problem with "start-after" option of "%s" '
13703776378SMarkus Heiser                                  'directive:\nText not found.' % self.name)
13803776378SMarkus Heiser            rawtext = rawtext[after_index + len(after_text):]
13903776378SMarkus Heiser        before_text = self.options.get('end-before', None)
14003776378SMarkus Heiser        if before_text:
14103776378SMarkus Heiser            # skip content in rawtext after *and incl.* a matching text
14203776378SMarkus Heiser            before_index = rawtext.find(before_text)
14303776378SMarkus Heiser            if before_index < 0:
14403776378SMarkus Heiser                raise self.severe('Problem with "end-before" option of "%s" '
14503776378SMarkus Heiser                                  'directive:\nText not found.' % self.name)
14603776378SMarkus Heiser            rawtext = rawtext[:before_index]
14703776378SMarkus Heiser
14803776378SMarkus Heiser        include_lines = statemachine.string2lines(rawtext, tab_width,
14903776378SMarkus Heiser                                                  convert_whitespace=True)
15003776378SMarkus Heiser        if 'literal' in self.options:
15103776378SMarkus Heiser            # Convert tabs to spaces, if `tab_width` is positive.
15203776378SMarkus Heiser            if tab_width >= 0:
15303776378SMarkus Heiser                text = rawtext.expandtabs(tab_width)
15403776378SMarkus Heiser            else:
15503776378SMarkus Heiser                text = rawtext
15603776378SMarkus Heiser            literal_block = nodes.literal_block(rawtext, source=path,
15703776378SMarkus Heiser                                    classes=self.options.get('class', []))
15803776378SMarkus Heiser            literal_block.line = 1
15903776378SMarkus Heiser            self.add_name(literal_block)
16003776378SMarkus Heiser            if 'number-lines' in self.options:
16103776378SMarkus Heiser                try:
16203776378SMarkus Heiser                    startline = int(self.options['number-lines'] or 1)
16303776378SMarkus Heiser                except ValueError:
16403776378SMarkus Heiser                    raise self.error(':number-lines: with non-integer '
16503776378SMarkus Heiser                                     'start value')
16603776378SMarkus Heiser                endline = startline + len(include_lines)
16703776378SMarkus Heiser                if text.endswith('\n'):
16803776378SMarkus Heiser                    text = text[:-1]
16903776378SMarkus Heiser                tokens = NumberLines([([], text)], startline, endline)
17003776378SMarkus Heiser                for classes, value in tokens:
17103776378SMarkus Heiser                    if classes:
17203776378SMarkus Heiser                        literal_block += nodes.inline(value, value,
17303776378SMarkus Heiser                                                      classes=classes)
17403776378SMarkus Heiser                    else:
17503776378SMarkus Heiser                        literal_block += nodes.Text(value, value)
17603776378SMarkus Heiser            else:
17703776378SMarkus Heiser                literal_block += nodes.Text(text, text)
17803776378SMarkus Heiser            return [literal_block]
17903776378SMarkus Heiser        if 'code' in self.options:
18003776378SMarkus Heiser            self.options['source'] = path
18103776378SMarkus Heiser            codeblock = CodeBlock(self.name,
18203776378SMarkus Heiser                                  [self.options.pop('code')], # arguments
18303776378SMarkus Heiser                                  self.options,
18403776378SMarkus Heiser                                  include_lines, # content
18503776378SMarkus Heiser                                  self.lineno,
18603776378SMarkus Heiser                                  self.content_offset,
18703776378SMarkus Heiser                                  self.block_text,
18803776378SMarkus Heiser                                  self.state,
18903776378SMarkus Heiser                                  self.state_machine)
19003776378SMarkus Heiser            return codeblock.run()
19103776378SMarkus Heiser        self.state_machine.insert_input(include_lines, path)
19203776378SMarkus Heiser        return []
193