This file is indexed.

/usr/share/pyshared/doit/cmd_strace.py is in python-doit 0.24.0-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import sys
import os
import re

from .action import CmdAction
from .task import Task
from .cmd_run import Run


# filter to display only files from cwd
opt_show_all = {
    'name':'show_all',
    'short':'a',
    'long':'all',
    'type': bool,
    'default': False,
    'help': "display all files (not only from within CWD path)",
    }

opt_keep_trace = {
    'name':'keep_trace',
    'short':'k',
    'long':'keep',
    'type': bool,
    'default': False,
    'help': "save strace command output into strace.txt",
    }


class Strace(Run):
    doc_purpose = "use strace to list file_deps and targets"
    doc_usage = "TASK"
    doc_description = """
The output is a list of files prefixed with 'R' for open in read mode
or 'W' for open in write mode.
The files are listed in chronological order.

This is a debugging feature wiht many lilmitations.
  * can strace only one task at a time
  * can only strace CmdAction
  * the process being traced itself might have some kind of cache,
    that means it might not write a target file if it exist
  * does not handle chdir

So this is NOT 100% reliable, use with care!
"""

    cmd_options = (opt_show_all, opt_keep_trace)

    TRACE_CMD = "strace -f -e trace=file %s 2>>%s "
    TRACE_OUT = 'strace.txt'

    def execute(self, params, args):
        """remove existing output file if any and do sanity checking"""
        if os.path.exists(self.TRACE_OUT): # pragma: no cover
            os.unlink(self.TRACE_OUT)
        assert len(args) == 1, 'doit strace failed, must select task to strace'
        result = Run.execute(self, params, args)
        if (not params['keep_trace']) and os.path.exists(self.TRACE_OUT):
            os.unlink(self.TRACE_OUT)
        return result

    def _execute(self, show_all):
        """1) wrap the original action with strace and save output in file
           2) add a second task that will generate the report from temp file
        """
        # find task to trace and wrap it
        selected = self.sel_tasks[0]
        for task in self.task_list:
            if task.name == selected:
                self.wrap_strace(task)
                break

        # add task to print report
        report_strace = Task(
            'strace_report',
            actions = [(find_deps, [self.outstream, self.TRACE_OUT, show_all])],
            verbosity = 2,
            task_dep = [selected],
            uptodate = [False],
        )
        self.task_list.append(report_strace)
        self.sel_tasks.append(report_strace.name)

        # clear strace file
        return Run._execute(self, sys.stdout)


    @classmethod
    def wrap_strace(cls, task):
        """wrap task actions into strace command"""
        wrapped_actions = []
        for action in task.actions:
            if isinstance(action, CmdAction):
                cmd = cls.TRACE_CMD % (action._action, cls.TRACE_OUT)
                wrapped = CmdAction(cmd, task, save_out=action.save_out)
                wrapped_actions.append(wrapped)
            else:
                wrapped_actions.append(action)
        task._action_instances = wrapped_actions
        # task should be always executed
        task._extend_uptodate([False])


def find_deps(outstream, strace_out, show_all):
    """read file witn strace output, return dict with deps, targets"""
    # 7978  open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    # get "mode" file was open, until ')' is closed
    # ignore rest of line
    # .*\(                 # ignore text until '('
    # "(?P<file>[^"]*)"    # get "file" name inside "
    # , (\[.*\])*          # ignore elments if inside [] - used by execve
    # (?P<mode>[^)]*)\)    # get mode opening file
    #  = ].*               # check syscall was successful""",
    regex = re.compile(r'.*\("(?P<file>[^"]*)", (\[.*\])*(?P<mode>[^)]*)\) = [^-].*')

    read = set()
    write = set()
    cwd = os.getcwd()
    if not os.path.exists(strace_out):
        return
    with open(strace_out) as text:
        for line in text:
            # ignore non file operation
            match = regex.match(line)
            if not match:
                continue
            rel_name = match.group('file')
            name = os.path.abspath(rel_name)

            # ignore files out of cwd
            if not show_all:
                if not name.startswith(cwd):
                    continue

            if 'WR' in match.group('mode'):
                if name not in write:
                    write.add(name)
                    outstream.write("W %s\n" % name)
            else:
                if name not in read:
                    read.add(name)
                    outstream.write("R %s\n" % name)