OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # coding=utf-8 | 2 # coding=utf-8 |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """Traces an executable and its child processes and extract the files accessed | 7 """Traces an executable and its child processes and extract the files accessed |
8 by them. | 8 by them. |
9 | 9 |
10 The implementation uses OS-specific API. The native Kernel logger and the ETL | 10 The implementation uses OS-specific API. The native Kernel logger and the ETL |
(...skipping 19 matching lines...) Expand all Loading... |
30 import re | 30 import re |
31 import stat | 31 import stat |
32 import subprocess | 32 import subprocess |
33 import sys | 33 import sys |
34 import tempfile | 34 import tempfile |
35 import threading | 35 import threading |
36 import time | 36 import time |
37 import unicodedata | 37 import unicodedata |
38 import weakref | 38 import weakref |
39 | 39 |
| 40 from third_party import colorama |
| 41 from third_party.depot_tools import fix_encoding |
| 42 from third_party.depot_tools import subcommand |
| 43 |
40 ## OS-specific imports | 44 ## OS-specific imports |
41 | 45 |
42 if sys.platform == 'win32': | 46 if sys.platform == 'win32': |
43 from ctypes.wintypes import byref, create_unicode_buffer, c_int, c_wchar_p | 47 from ctypes.wintypes import byref, create_unicode_buffer, c_int, c_wchar_p |
44 from ctypes.wintypes import windll, FormatError # pylint: disable=E0611 | 48 from ctypes.wintypes import windll, FormatError # pylint: disable=E0611 |
45 from ctypes.wintypes import GetLastError # pylint: disable=E0611 | 49 from ctypes.wintypes import GetLastError # pylint: disable=E0611 |
46 elif sys.platform == 'darwin': | 50 elif sys.platform == 'darwin': |
47 import Carbon.File # pylint: disable=F0401 | 51 import Carbon.File # pylint: disable=F0401 |
48 import MacOS # pylint: disable=F0401 | 52 import MacOS # pylint: disable=F0401 |
49 | 53 |
(...skipping 3579 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3629 - cwd: current directory to start the process in. | 3633 - cwd: current directory to start the process in. |
3630 - api: a tracing api instance. | 3634 - api: a tracing api instance. |
3631 - output: if True, returns output, otherwise prints it at the console. | 3635 - output: if True, returns output, otherwise prints it at the console. |
3632 """ | 3636 """ |
3633 cmd = fix_python_path(cmd) | 3637 cmd = fix_python_path(cmd) |
3634 api.clean_trace(logfile) | 3638 api.clean_trace(logfile) |
3635 with api.get_tracer(logfile) as tracer: | 3639 with api.get_tracer(logfile) as tracer: |
3636 return tracer.trace(cmd, cwd, 'default', output) | 3640 return tracer.trace(cmd, cwd, 'default', output) |
3637 | 3641 |
3638 | 3642 |
3639 def CMDclean(args): | 3643 def CMDclean(parser, args): |
3640 """Cleans up traces.""" | 3644 """Cleans up traces.""" |
3641 parser = OptionParserTraceInputs(command='clean') | |
3642 options, args = parser.parse_args(args) | 3645 options, args = parser.parse_args(args) |
3643 api = get_api() | 3646 api = get_api() |
3644 api.clean_trace(options.log) | 3647 api.clean_trace(options.log) |
3645 return 0 | 3648 return 0 |
3646 | 3649 |
3647 | 3650 |
3648 def CMDtrace(args): | 3651 def CMDtrace(parser, args): |
3649 """Traces an executable.""" | 3652 """Traces an executable.""" |
3650 parser = OptionParserTraceInputs(command='trace') | |
3651 parser.allow_interspersed_args = False | 3653 parser.allow_interspersed_args = False |
3652 parser.add_option( | 3654 parser.add_option( |
3653 '-q', '--quiet', action='store_true', | 3655 '-q', '--quiet', action='store_true', |
3654 help='Redirects traced executable output to /dev/null') | 3656 help='Redirects traced executable output to /dev/null') |
3655 parser.add_option( | 3657 parser.add_option( |
3656 '-s', '--sudo', action='store_true', | 3658 '-s', '--sudo', action='store_true', |
3657 help='Use sudo when shelling out the tracer tool (ignored on Windows)') | 3659 help='Use sudo when shelling out the tracer tool (ignored on Windows)') |
3658 parser.add_option( | 3660 parser.add_option( |
3659 '-n', '--no-sudo', action='store_false', | 3661 '-n', '--no-sudo', action='store_false', |
3660 help='Don\'t use sudo') | 3662 help='Don\'t use sudo') |
3661 options, args = parser.parse_args(args) | 3663 options, args = parser.parse_args(args) |
3662 | 3664 |
3663 if not args: | 3665 if not args: |
3664 parser.error('Please provide a command to run') | 3666 parser.error('Please provide a command to run') |
3665 | 3667 |
3666 if not os.path.isabs(args[0]) and os.access(args[0], os.X_OK): | 3668 if not os.path.isabs(args[0]) and os.access(args[0], os.X_OK): |
3667 args[0] = os.path.abspath(args[0]) | 3669 args[0] = os.path.abspath(args[0]) |
3668 | 3670 |
3669 # options.sudo default value is None, which is to do whatever tracer defaults | 3671 # options.sudo default value is None, which is to do whatever tracer defaults |
3670 # do. | 3672 # do. |
3671 api = get_api(use_sudo=options.sudo) | 3673 api = get_api(use_sudo=options.sudo) |
3672 return trace(options.log, args, os.getcwd(), api, options.quiet)[0] | 3674 return trace(options.log, args, os.getcwd(), api, options.quiet)[0] |
3673 | 3675 |
3674 | 3676 |
3675 def CMDread(args): | 3677 def CMDread(parser, args): |
3676 """Reads the logs and prints the result.""" | 3678 """Reads the logs and prints the result.""" |
3677 parser = OptionParserTraceInputs(command='read') | |
3678 parser.add_option( | 3679 parser.add_option( |
3679 '-V', '--variable', | 3680 '-V', '--variable', |
3680 nargs=2, | 3681 nargs=2, |
3681 action='append', | 3682 action='append', |
3682 dest='variables', | 3683 dest='variables', |
3683 metavar='VAR_NAME directory', | 3684 metavar='VAR_NAME directory', |
3684 default=[], | 3685 default=[], |
3685 help=('Variables to replace relative directories against. Example: ' | 3686 help=('Variables to replace relative directories against. Example: ' |
3686 '"-v \'$HOME\' \'/home/%s\'" will replace all occurence of your ' | 3687 '"-v \'$HOME\' \'/home/%s\'" will replace all occurence of your ' |
3687 'home dir with $HOME') % getpass.getuser()) | 3688 'home dir with $HOME') % getpass.getuser()) |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3765 | 3766 |
3766 def parse_args(self, *args, **kwargs): | 3767 def parse_args(self, *args, **kwargs): |
3767 options, args = optparse.OptionParser.parse_args(self, *args, **kwargs) | 3768 options, args = optparse.OptionParser.parse_args(self, *args, **kwargs) |
3768 levels = [logging.ERROR, logging.INFO, logging.DEBUG] | 3769 levels = [logging.ERROR, logging.INFO, logging.DEBUG] |
3769 logging.basicConfig( | 3770 logging.basicConfig( |
3770 level=levels[min(len(levels)-1, options.verbose)], | 3771 level=levels[min(len(levels)-1, options.verbose)], |
3771 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') | 3772 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') |
3772 return options, args | 3773 return options, args |
3773 | 3774 |
3774 | 3775 |
3775 class OptionParserWithNiceDescription(OptionParserWithLogging): | 3776 class OptionParserTraceInputs(OptionParserWithLogging): |
3776 """Generates the description with the command's docstring.""" | |
3777 def __init__(self, **kwargs): | |
3778 """Sets 'description' and 'usage' if not already specified.""" | |
3779 command = kwargs.pop('command', 'help') | |
3780 kwargs.setdefault( | |
3781 'description', | |
3782 re.sub('[\r\n ]{2,}', ' ', get_command_handler(command).__doc__)) | |
3783 kwargs.setdefault('usage', '%%prog %s [options]' % command) | |
3784 OptionParserWithLogging.__init__(self, **kwargs) | |
3785 | |
3786 | |
3787 class OptionParserTraceInputs(OptionParserWithNiceDescription): | |
3788 """Adds automatic --log handling.""" | 3777 """Adds automatic --log handling.""" |
3789 def __init__(self, **kwargs): | 3778 def __init__(self, **kwargs): |
3790 OptionParserWithNiceDescription.__init__(self, **kwargs) | 3779 OptionParserWithLogging.__init__(self, **kwargs) |
3791 self.add_option( | 3780 self.add_option( |
3792 '-l', '--log', help='Log file to generate or read, required') | 3781 '-l', '--log', help='Log file to generate or read, required') |
3793 | 3782 |
3794 def parse_args(self, *args, **kwargs): | 3783 def parse_args(self, *args, **kwargs): |
3795 """Makes sure the paths make sense. | 3784 """Makes sure the paths make sense. |
3796 | 3785 |
3797 On Windows, / and \ are often mixed together in a path. | 3786 On Windows, / and \ are often mixed together in a path. |
3798 """ | 3787 """ |
3799 options, args = OptionParserWithNiceDescription.parse_args( | 3788 options, args = OptionParserWithLogging.parse_args( |
3800 self, *args, **kwargs) | 3789 self, *args, **kwargs) |
3801 if not options.log: | 3790 if not options.log: |
3802 self.error('Must supply a log file with -l') | 3791 self.error('Must supply a log file with -l') |
3803 options.log = os.path.abspath(options.log) | 3792 options.log = os.path.abspath(options.log) |
3804 return options, args | 3793 return options, args |
3805 | 3794 |
3806 | 3795 |
3807 def extract_documentation(): | |
3808 """Returns a dict {command: description} for each of documented command.""" | |
3809 commands = ( | |
3810 fn[3:] | |
3811 for fn in dir(sys.modules['__main__']) | |
3812 if fn.startswith('CMD') and get_command_handler(fn[3:]).__doc__) | |
3813 return dict((fn, get_command_handler(fn).__doc__) for fn in commands) | |
3814 | |
3815 | |
3816 def CMDhelp(args): | |
3817 """Prints list of commands or help for a specific command.""" | |
3818 doc = extract_documentation() | |
3819 # Calculates the optimal offset. | |
3820 offset = max(len(cmd) for cmd in doc) | |
3821 format_str = ' %-' + str(offset + 2) + 's %s' | |
3822 # Generate a one-liner documentation of each commands. | |
3823 commands_description = '\n'.join( | |
3824 format_str % (cmd, doc[cmd].split('\n')[0]) for cmd in sorted(doc)) | |
3825 | |
3826 parser = OptionParserWithNiceDescription( | |
3827 usage='%prog <command> [options]', | |
3828 description='Commands are:\n%s\n' % commands_description) | |
3829 parser.format_description = lambda _: parser.description | |
3830 | |
3831 # Strip out any -h or --help argument. | |
3832 _, args = parser.parse_args([i for i in args if not i in ('-h', '--help')]) | |
3833 if len(args) == 1: | |
3834 if not get_command_handler(args[0]): | |
3835 parser.error('Unknown command %s' % args[0]) | |
3836 # The command was "%prog help command", replaces ourself with | |
3837 # "%prog command --help" so help is correctly printed out. | |
3838 return main(args + ['--help']) | |
3839 elif args: | |
3840 parser.error('Unknown argument "%s"' % ' '.join(args)) | |
3841 parser.print_help() | |
3842 return 0 | |
3843 | |
3844 | |
3845 def get_command_handler(name): | |
3846 """Returns the command handler or CMDhelp if it doesn't exist.""" | |
3847 return getattr(sys.modules['__main__'], 'CMD%s' % name, None) | |
3848 | |
3849 | |
3850 def main_impl(argv): | |
3851 command = get_command_handler(argv[0] if argv else 'help') | |
3852 if not command: | |
3853 return CMDhelp(argv) | |
3854 return command(argv[1:]) | |
3855 | |
3856 def main(argv): | 3796 def main(argv): |
3857 disable_buffering() | 3797 dispatcher = subcommand.CommandDispatcher(__name__) |
3858 try: | 3798 try: |
3859 main_impl(argv) | 3799 return dispatcher.execute(OptionParserTraceInputs(), argv) |
3860 except TracingFailure, e: | 3800 except TracingFailure, e: |
3861 sys.stderr.write('\nError: ') | 3801 sys.stderr.write('\nError: ') |
3862 sys.stderr.write(str(e)) | 3802 sys.stderr.write(str(e)) |
3863 sys.stderr.write('\n') | 3803 sys.stderr.write('\n') |
3864 return 1 | 3804 return 1 |
3865 | 3805 |
3866 | 3806 |
3867 if __name__ == '__main__': | 3807 if __name__ == '__main__': |
| 3808 fix_encoding.fix_encoding() |
| 3809 disable_buffering() |
| 3810 colorama.init() |
3868 sys.exit(main(sys.argv[1:])) | 3811 sys.exit(main(sys.argv[1:])) |
OLD | NEW |