| 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 |