From c88082327ff5cb630c7611f50655ec714f40a57a Mon Sep 17 00:00:00 2001 From: Josh Lay Date: Thu, 13 Jun 2019 20:17:02 -0500 Subject: [PATCH] add mitogen - requires ansible 2.7 --- ansible.cfg | 2 + mitogen-0.2.7/LICENSE | 26 + mitogen-0.2.7/MANIFEST.in | 1 + mitogen-0.2.7/PKG-INFO | 23 + mitogen-0.2.7/README.md | 13 + mitogen-0.2.7/ansible_mitogen/__init__.py | 0 mitogen-0.2.7/ansible_mitogen/__init__.pyc | Bin 0 -> 155 bytes .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 155 bytes .../__pycache__/affinity.cpython-37.pyc | Bin 0 -> 9281 bytes .../__pycache__/connection.cpython-37.pyc | Bin 0 -> 25760 bytes .../__pycache__/loaders.cpython-37.pyc | Bin 0 -> 682 bytes .../__pycache__/logging.cpython-37.pyc | Bin 0 -> 2387 bytes .../__pycache__/mixins.cpython-37.pyc | Bin 0 -> 14307 bytes .../__pycache__/module_finder.cpython-37.pyc | Bin 0 -> 3217 bytes .../__pycache__/parsing.cpython-37.pyc | Bin 0 -> 1727 bytes .../__pycache__/planner.cpython-37.pyc | Bin 0 -> 15806 bytes .../__pycache__/process.cpython-37.pyc | Bin 0 -> 9828 bytes .../__pycache__/runner.cpython-37.pyc | Bin 0 -> 30523 bytes .../__pycache__/services.cpython-37.pyc | Bin 0 -> 15329 bytes .../__pycache__/strategy.cpython-37.pyc | Bin 0 -> 9956 bytes .../__pycache__/target.cpython-37.pyc | Bin 0 -> 20112 bytes .../transport_config.cpython-37.pyc | Bin 0 -> 21239 bytes mitogen-0.2.7/ansible_mitogen/affinity.py | 269 ++ mitogen-0.2.7/ansible_mitogen/affinity.pyc | Bin 0 -> 11086 bytes .../ansible_mitogen/compat/__init__.py | 0 .../compat/simplejson/__init__.py | 318 ++ .../compat/simplejson/decoder.py | 354 ++ .../compat/simplejson/encoder.py | 440 +++ .../compat/simplejson/scanner.py | 65 + mitogen-0.2.7/ansible_mitogen/connection.py | 1021 +++++ mitogen-0.2.7/ansible_mitogen/connection.pyc | Bin 0 -> 32926 bytes mitogen-0.2.7/ansible_mitogen/loaders.py | 48 + mitogen-0.2.7/ansible_mitogen/loaders.pyc | Bin 0 -> 797 bytes mitogen-0.2.7/ansible_mitogen/logging.py | 127 + mitogen-0.2.7/ansible_mitogen/logging.pyc | Bin 0 -> 3133 bytes mitogen-0.2.7/ansible_mitogen/mixins.py | 432 +++ mitogen-0.2.7/ansible_mitogen/mixins.pyc | Bin 0 -> 16658 bytes .../ansible_mitogen/module_finder.py | 157 + .../ansible_mitogen/module_finder.pyc | Bin 0 -> 4370 bytes mitogen-0.2.7/ansible_mitogen/parsing.py | 84 + mitogen-0.2.7/ansible_mitogen/parsing.pyc | Bin 0 -> 2013 bytes mitogen-0.2.7/ansible_mitogen/planner.py | 499 +++ mitogen-0.2.7/ansible_mitogen/planner.pyc | Bin 0 -> 19326 bytes .../ansible_mitogen/plugins/__init__.py | 0 .../plugins/action/__init__.py | 0 .../plugins/action/mitogen_get_stack.py | 54 + .../plugins/connection/__init__.py | 0 .../plugins/connection/mitogen_doas.py | 44 + .../plugins/connection/mitogen_docker.py | 51 + .../plugins/connection/mitogen_jail.py | 44 + .../plugins/connection/mitogen_kubectl.py | 71 + .../plugins/connection/mitogen_local.py | 86 + .../plugins/connection/mitogen_lxc.py | 44 + .../plugins/connection/mitogen_lxd.py | 44 + .../plugins/connection/mitogen_machinectl.py | 44 + .../plugins/connection/mitogen_setns.py | 44 + .../plugins/connection/mitogen_ssh.py | 65 + .../plugins/connection/mitogen_ssh.pyc | Bin 0 -> 1727 bytes .../plugins/connection/mitogen_su.py | 44 + .../plugins/connection/mitogen_sudo.py | 44 + .../plugins/strategy/__init__.py | 0 .../__pycache__/mitogen_linear.cpython-37.pyc | Bin 0 -> 754 bytes .../plugins/strategy/mitogen.py | 61 + .../plugins/strategy/mitogen_free.py | 62 + .../plugins/strategy/mitogen_host_pinned.py | 67 + .../plugins/strategy/mitogen_linear.py | 62 + .../plugins/strategy/mitogen_linear.pyc | Bin 0 -> 982 bytes mitogen-0.2.7/ansible_mitogen/process.py | 358 ++ mitogen-0.2.7/ansible_mitogen/process.pyc | Bin 0 -> 11763 bytes mitogen-0.2.7/ansible_mitogen/runner.py | 928 +++++ mitogen-0.2.7/ansible_mitogen/runner.pyc | Bin 0 -> 36946 bytes mitogen-0.2.7/ansible_mitogen/services.py | 537 +++ mitogen-0.2.7/ansible_mitogen/services.pyc | Bin 0 -> 18260 bytes mitogen-0.2.7/ansible_mitogen/strategy.py | 296 ++ mitogen-0.2.7/ansible_mitogen/strategy.pyc | Bin 0 -> 11444 bytes mitogen-0.2.7/ansible_mitogen/target.py | 777 ++++ mitogen-0.2.7/ansible_mitogen/target.pyc | Bin 0 -> 24402 bytes .../ansible_mitogen/transport_config.py | 636 +++ .../ansible_mitogen/transport_config.pyc | Bin 0 -> 27622 bytes mitogen-0.2.7/mitogen.egg-info/PKG-INFO | 23 + mitogen-0.2.7/mitogen.egg-info/SOURCES.txt | 78 + .../mitogen.egg-info/dependency_links.txt | 1 + mitogen-0.2.7/mitogen.egg-info/not-zip-safe | 1 + mitogen-0.2.7/mitogen.egg-info/top_level.txt | 2 + mitogen-0.2.7/mitogen/__init__.py | 120 + mitogen-0.2.7/mitogen/__init__.pyc | Bin 0 -> 2505 bytes .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 2198 bytes .../mitogen/__pycache__/core.cpython-37.pyc | Bin 0 -> 102045 bytes .../mitogen/__pycache__/debug.cpython-37.pyc | Bin 0 -> 6343 bytes .../mitogen/__pycache__/fork.cpython-37.pyc | Bin 0 -> 4850 bytes .../mitogen/__pycache__/master.cpython-37.pyc | Bin 0 -> 33651 bytes .../mitogen/__pycache__/minify.cpython-37.pyc | Bin 0 -> 2857 bytes .../mitogen/__pycache__/parent.cpython-37.pyc | Bin 0 -> 69048 bytes .../mitogen/__pycache__/select.cpython-37.pyc | Bin 0 -> 10142 bytes .../__pycache__/service.cpython-37.pyc | Bin 0 -> 32895 bytes .../mitogen/__pycache__/unix.cpython-37.pyc | Bin 0 -> 4383 bytes .../mitogen/__pycache__/utils.cpython-37.pyc | Bin 0 -> 6008 bytes mitogen-0.2.7/mitogen/compat/__init__.py | 0 mitogen-0.2.7/mitogen/compat/pkgutil.py | 593 +++ mitogen-0.2.7/mitogen/compat/tokenize.py | 453 +++ mitogen-0.2.7/mitogen/core.py | 3409 +++++++++++++++++ mitogen-0.2.7/mitogen/core.pyc | Bin 0 -> 122272 bytes mitogen-0.2.7/mitogen/debug.py | 236 ++ mitogen-0.2.7/mitogen/debug.pyc | Bin 0 -> 8472 bytes mitogen-0.2.7/mitogen/doas.py | 113 + mitogen-0.2.7/mitogen/docker.py | 81 + mitogen-0.2.7/mitogen/fakessh.py | 461 +++ mitogen-0.2.7/mitogen/fork.py | 223 ++ mitogen-0.2.7/mitogen/fork.pyc | Bin 0 -> 5964 bytes mitogen-0.2.7/mitogen/jail.py | 65 + mitogen-0.2.7/mitogen/kubectl.py | 65 + mitogen-0.2.7/mitogen/lxc.py | 75 + mitogen-0.2.7/mitogen/lxd.py | 77 + mitogen-0.2.7/mitogen/master.py | 1173 ++++++ mitogen-0.2.7/mitogen/master.pyc | Bin 0 -> 40240 bytes mitogen-0.2.7/mitogen/minify.py | 139 + mitogen-0.2.7/mitogen/minify.pyc | Bin 0 -> 3572 bytes mitogen-0.2.7/mitogen/os_fork.py | 183 + mitogen-0.2.7/mitogen/parent.py | 2330 +++++++++++ mitogen-0.2.7/mitogen/parent.pyc | Bin 0 -> 83619 bytes mitogen-0.2.7/mitogen/profiler.py | 166 + mitogen-0.2.7/mitogen/select.py | 333 ++ mitogen-0.2.7/mitogen/select.pyc | Bin 0 -> 11581 bytes mitogen-0.2.7/mitogen/service.py | 1085 ++++++ mitogen-0.2.7/mitogen/service.pyc | Bin 0 -> 40022 bytes mitogen-0.2.7/mitogen/setns.py | 238 ++ mitogen-0.2.7/mitogen/ssh.py | 317 ++ mitogen-0.2.7/mitogen/ssh.pyc | Bin 0 -> 9207 bytes mitogen-0.2.7/mitogen/su.py | 128 + mitogen-0.2.7/mitogen/sudo.py | 277 ++ mitogen-0.2.7/mitogen/unix.py | 168 + mitogen-0.2.7/mitogen/unix.pyc | Bin 0 -> 5618 bytes mitogen-0.2.7/mitogen/utils.py | 227 ++ mitogen-0.2.7/mitogen/utils.pyc | Bin 0 -> 7119 bytes mitogen-0.2.7/setup.cfg | 15 + mitogen-0.2.7/setup.py | 67 + 136 files changed, 21264 insertions(+) create mode 100644 mitogen-0.2.7/LICENSE create mode 100644 mitogen-0.2.7/MANIFEST.in create mode 100644 mitogen-0.2.7/PKG-INFO create mode 100644 mitogen-0.2.7/README.md create mode 100644 mitogen-0.2.7/ansible_mitogen/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/__init__.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/__init__.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/affinity.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/connection.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/loaders.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/logging.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/mixins.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/module_finder.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/parsing.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/planner.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/process.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/runner.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/services.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/strategy.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/target.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/__pycache__/transport_config.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/affinity.py create mode 100644 mitogen-0.2.7/ansible_mitogen/affinity.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/compat/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/compat/simplejson/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/compat/simplejson/decoder.py create mode 100644 mitogen-0.2.7/ansible_mitogen/compat/simplejson/encoder.py create mode 100644 mitogen-0.2.7/ansible_mitogen/compat/simplejson/scanner.py create mode 100644 mitogen-0.2.7/ansible_mitogen/connection.py create mode 100644 mitogen-0.2.7/ansible_mitogen/connection.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/loaders.py create mode 100644 mitogen-0.2.7/ansible_mitogen/loaders.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/logging.py create mode 100644 mitogen-0.2.7/ansible_mitogen/logging.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/mixins.py create mode 100644 mitogen-0.2.7/ansible_mitogen/mixins.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/module_finder.py create mode 100644 mitogen-0.2.7/ansible_mitogen/module_finder.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/parsing.py create mode 100644 mitogen-0.2.7/ansible_mitogen/parsing.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/planner.py create mode 100644 mitogen-0.2.7/ansible_mitogen/planner.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/action/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/action/mitogen_get_stack.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_doas.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_docker.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_jail.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_kubectl.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_local.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxc.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxd.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_machinectl.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_setns.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_su.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_sudo.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/__init__.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/__pycache__/mitogen_linear.cpython-37.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_free.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_linear.py create mode 100644 mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_linear.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/process.py create mode 100644 mitogen-0.2.7/ansible_mitogen/process.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/runner.py create mode 100644 mitogen-0.2.7/ansible_mitogen/runner.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/services.py create mode 100644 mitogen-0.2.7/ansible_mitogen/services.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/strategy.py create mode 100644 mitogen-0.2.7/ansible_mitogen/strategy.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/target.py create mode 100644 mitogen-0.2.7/ansible_mitogen/target.pyc create mode 100644 mitogen-0.2.7/ansible_mitogen/transport_config.py create mode 100644 mitogen-0.2.7/ansible_mitogen/transport_config.pyc create mode 100644 mitogen-0.2.7/mitogen.egg-info/PKG-INFO create mode 100644 mitogen-0.2.7/mitogen.egg-info/SOURCES.txt create mode 100644 mitogen-0.2.7/mitogen.egg-info/dependency_links.txt create mode 100644 mitogen-0.2.7/mitogen.egg-info/not-zip-safe create mode 100644 mitogen-0.2.7/mitogen.egg-info/top_level.txt create mode 100644 mitogen-0.2.7/mitogen/__init__.py create mode 100644 mitogen-0.2.7/mitogen/__init__.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/__init__.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/core.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/debug.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/fork.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/master.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/minify.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/parent.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/select.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/service.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/unix.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/__pycache__/utils.cpython-37.pyc create mode 100644 mitogen-0.2.7/mitogen/compat/__init__.py create mode 100644 mitogen-0.2.7/mitogen/compat/pkgutil.py create mode 100644 mitogen-0.2.7/mitogen/compat/tokenize.py create mode 100644 mitogen-0.2.7/mitogen/core.py create mode 100644 mitogen-0.2.7/mitogen/core.pyc create mode 100644 mitogen-0.2.7/mitogen/debug.py create mode 100644 mitogen-0.2.7/mitogen/debug.pyc create mode 100644 mitogen-0.2.7/mitogen/doas.py create mode 100644 mitogen-0.2.7/mitogen/docker.py create mode 100644 mitogen-0.2.7/mitogen/fakessh.py create mode 100644 mitogen-0.2.7/mitogen/fork.py create mode 100644 mitogen-0.2.7/mitogen/fork.pyc create mode 100644 mitogen-0.2.7/mitogen/jail.py create mode 100644 mitogen-0.2.7/mitogen/kubectl.py create mode 100644 mitogen-0.2.7/mitogen/lxc.py create mode 100644 mitogen-0.2.7/mitogen/lxd.py create mode 100644 mitogen-0.2.7/mitogen/master.py create mode 100644 mitogen-0.2.7/mitogen/master.pyc create mode 100644 mitogen-0.2.7/mitogen/minify.py create mode 100644 mitogen-0.2.7/mitogen/minify.pyc create mode 100644 mitogen-0.2.7/mitogen/os_fork.py create mode 100644 mitogen-0.2.7/mitogen/parent.py create mode 100644 mitogen-0.2.7/mitogen/parent.pyc create mode 100644 mitogen-0.2.7/mitogen/profiler.py create mode 100644 mitogen-0.2.7/mitogen/select.py create mode 100644 mitogen-0.2.7/mitogen/select.pyc create mode 100644 mitogen-0.2.7/mitogen/service.py create mode 100644 mitogen-0.2.7/mitogen/service.pyc create mode 100644 mitogen-0.2.7/mitogen/setns.py create mode 100644 mitogen-0.2.7/mitogen/ssh.py create mode 100644 mitogen-0.2.7/mitogen/ssh.pyc create mode 100644 mitogen-0.2.7/mitogen/su.py create mode 100644 mitogen-0.2.7/mitogen/sudo.py create mode 100644 mitogen-0.2.7/mitogen/unix.py create mode 100644 mitogen-0.2.7/mitogen/unix.pyc create mode 100644 mitogen-0.2.7/mitogen/utils.py create mode 100644 mitogen-0.2.7/mitogen/utils.pyc create mode 100644 mitogen-0.2.7/setup.cfg create mode 100644 mitogen-0.2.7/setup.py diff --git a/ansible.cfg b/ansible.cfg index e57103f..6749b25 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,6 +1,8 @@ [defaults] host_key_checking = False retry_files_enabled = False +strategy_plugins = ./mitogen-0.2.7/ansible_mitogen/plugins/strategy +strategy = mitogen_linear [ssh_connection] pipelining = True diff --git a/mitogen-0.2.7/LICENSE b/mitogen-0.2.7/LICENSE new file mode 100644 index 000000000..70e43a9 --- /dev/null +++ b/mitogen-0.2.7/LICENSE @@ -0,0 +1,26 @@ +Copyright 2019, David Wilson + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/mitogen-0.2.7/MANIFEST.in b/mitogen-0.2.7/MANIFEST.in new file mode 100644 index 000000000..1aba38f --- /dev/null +++ b/mitogen-0.2.7/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE diff --git a/mitogen-0.2.7/PKG-INFO b/mitogen-0.2.7/PKG-INFO new file mode 100644 index 000000000..f63ba91 --- /dev/null +++ b/mitogen-0.2.7/PKG-INFO @@ -0,0 +1,23 @@ +Metadata-Version: 1.1 +Name: mitogen +Version: 0.2.7 +Summary: Library for writing distributed self-replicating programs. +Home-page: https://github.com/dw/mitogen/ +Author: David Wilson +Author-email: UNKNOWN +License: New BSD +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Environment :: Console +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: System :: Distributed Computing +Classifier: Topic :: System :: Systems Administration diff --git a/mitogen-0.2.7/README.md b/mitogen-0.2.7/README.md new file mode 100644 index 000000000..5ef2447 --- /dev/null +++ b/mitogen-0.2.7/README.md @@ -0,0 +1,13 @@ + +# Mitogen + + +Please see the documentation. + +![](https://i.imgur.com/eBM6LhJ.gif) + +[![Total alerts](https://img.shields.io/lgtm/alerts/g/dw/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/dw/mitogen/alerts/) + +[![Build Status](https://travis-ci.org/dw/mitogen.svg?branch=master)](https://travis-ci.org/dw/mitogen) + +[![Pipelines Status](https://dev.azure.com/dw-mitogen/Mitogen/_apis/build/status/dw.mitogen?branchName=master)](https://dev.azure.com/dw-mitogen/Mitogen/_build/latest?definitionId=1?branchName=master) diff --git a/mitogen-0.2.7/ansible_mitogen/__init__.py b/mitogen-0.2.7/ansible_mitogen/__init__.py new file mode 100644 index 000000000..e69de29 diff --git a/mitogen-0.2.7/ansible_mitogen/__init__.pyc b/mitogen-0.2.7/ansible_mitogen/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..004bfb02ea212eb9548e521a043fae55ff0fcbe5 GIT binary patch literal 155 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 RH`Xp*z4-HsmjbWj|1YU^^pOAn literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/connection.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/connection.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74d1aa37e207472c98bf8392494a903298c3034a GIT binary patch literal 25760 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 Ib@sXc2ea-1)c^nh literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/loaders.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/loaders.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d75377e3a7b1cc1f3b036bc463082d89663ca12 GIT binary patch literal 682 zcmZ9KPm9zr7{=eUooT0k92dc(_S(a?;>C+By5d1tMA(B`7DA@U&Q|khNK&DGBfo(s zKS=i^dszGmo+RzKQ#OHzCr@5}yl>jmEQjU}>SCMS(n#^kT1XlRLSoMXIzS_37qHM=if4m`vRid?O%jEI1@_G59+Eeg8t)@myx$B4M5G T0f4|uJrAXK`yqO8=Tr0#Q1iDQ literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/logging.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/logging.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..083cc40fb0cbdde868046c96790437694de8615f GIT binary patch literal 2387 zcmZ8i&2J+`7JpU!>2^EGAU;MbKrN&h)@a5-fCNG-cIP7#M#^STGNV}+-|IyH0Kb+0 z{rcBe0RD%a-suJh?v%kKOhW?C zp}OsY=!_$EKmdOnxLZb(ZrUXvx)+d!UqVIy1Ds|tr&*7C+`oi$Z5w#N!%N_ypriE* zCtc?q9$iAZ!RbEqyD!kwJ~8XrK`qOf7Oa>|rP8~7Vgg>&(=t0n^I>Zql0{_LQUG{> z!EFFQ0~u&Cr&o|Uoa{rNp1mI>Ns{afon%RsO+=z)l5wf3q?CCw5p|vAqE1cphFL~M@A>~u3P^d=Y6 zQl8#D$ZD}UDYVQ*b@%A4hm>-XI-GLn5>i)q+~rEc~HaAL{vs`G_`l*F7oolmKq4y)}kD$R{`35*`%p1cGm&mKH}`7-y|k6 zIonu(Gguw(@M@U42U5#bh#mmo8XMv-HpGT-!s&%`P8-UdF;#DBGNKJ(PD2|Qxno~_ z+K`c>$;caf>QhZdK91@@hx5)AjH%ku(YaH(+&y==*EsF>2Ik#{T#=#kI-I+U9q8Co zmpUHx8W(f>Jta`x{?qPr9go%;mxsJ#$D#)2>y3*Euy9AaCSHKr7CRWf)?6s{2l?k& z{MD_yAJxe%{!MbLe$*$XbE2{-)8ae*6njgod4IzMxzL%`O08keysf>xCwnF+#jz+W zX?nX)9`Aq2cAji+Z|&`y;Q771-N(Dz#(TJR|Jk-ejZ%oC3h;%f8o%9LVFejaC?%B% zPBK*$Rc@2m_ta~4q7sKvarFiOr14*EG=(!jM{ z-CXn{JJQawwq5IwUG-x_uIKY$ANt|h?^ahHSGCT{GC6A1rbby=3JuCkE>w+jq69CL zIMk>zlccD$w0d)|6-&F-;yOr%Dw~KCsm6ola8k6V)JdW1M4q%e7gc^e_u7GzBT?D? z9t<9CJ-YX7=P8yZ`{Lfy&y%7~YN7Ao(Mk-Z5_o8KP>N)#@Ho|CXivYYUItse?rUC}e`wiB>z1HZ6GQ&!3{XW`6jOma?C{wPb&c+^6`b R1S5+{?7FV&gxy!({XYe8ZioN? literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/mixins.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/mixins.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb59de347671615ba98838c2e45feb7b22ee4d01 GIT binary patch literal 14307 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Ν!lP!O9gh@V>cE?BSJa%P?> z?Gn76`XdaL!-!{6q`H*H?kN@N^8AIvQeq9uy57jQd{6BQuX8?~>Z~l1{2WSszkX6S z0ATB+Js4y~rU!%G^f+F%VW!U6u?;<&=E8QrtCWzK=&_Wgw5@3(b%ta4E{dX!wlFw1 z97JZ@Pn==9`EniG>>7T3&dI7&63s&5?qgTn!&2ml&JIPq;)GbwMtNCpUnJa;f%SBf z<(BE$RNllJ%}L3fnte4J4znZcO-e50JD3?2_jdn#_@W=6Y~RCX?{vu;q2xO05}&RS zj|8;KJQ|WN?Rs6(Arf8fLKr9zf7x4<|u}z*yY#;aPn7R3v_&}xBU+6{h4t^mX^e-SCa-F!?h3jIy zcrV3CTL5tMeGMbBpa#y!BY5@x4y#B-D|U3-ut)G}z48poBB00|E!PUw~n% zi)fY+-1NX8K(DDr-5QgTE) zP&OFKa)OOhqkR*oU2&<-ryL;~x7mqF2~1<`%fo|B(^#}k;LZvjI+(i(Mk`h8=#gp3zv-@N%}Et% zFrjIUR!wWPc3z)pjGOYWE(5#BP+zq$!@=)fz`^DVw>bI=r<|RFX*>d~8l7)T;s$il z2x#}%3=03hOt>fPJGq*Kp)H%1Q@6NoceSC?iPY-nnckP*#O_ClT9%8#vV?Ql!Wg>7rDD+!tglL`1&`ad zy*$Za<6R7#jkIf5cY}KWZz!FlW9um)3i&n6|Y=c zcMpiBIH*j%t?_4FrV|U$x<2Fr4X`6oY|8 zdL_D(ZKq|PixhoX)%JqUV!KOa_G)3u4SWFEvmqkk&g1Jfz%!d;x;_~oyS2-pzFaMr p;5m`jhobv6sSmz@II%lAq(cJ|kbnlvV;vT>gLcpizY_XE@IO!>64U?y literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/parsing.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/parsing.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc0a1c02ff0c1b22d417786ed3641feb30480584 GIT binary patch literal 1727 zcmds&O>ZMb5QeK~#vfQoScyeSE~wYxfJN3`aRFHf5yAxtEJRodnMLFFblL7rPxnyW zvxy}~Ldx<#k|TdwTu?i{0H00Ko71j~6?;0Dp$T z{SyJe=K$b70RRbOkW5CJ<z30m@*CGHp4 z$t)4N!WyZ}oPSpugT%9tfuAeCP$ul+UeFx<5mzSDWxcZ?)K=6c;m@UV%427yG`c}< zWvS8eGh@#U&y{n^%=?{AI9B)AI5pMCzo|FzW6BmP+@U1w9&w4UP}@S8d0LR2lgQJV zw&&?4WPjmv-MI}_?Xl`@F?Cj#9#fSUmVA#@k4mF5D>2o|BMI$B8QW+O13&`+PJ#?8 z09e6wErX12;!Cz-GC7Orea~d z!@ZO>+|Ib*&qrVVz!$uY7o3tl>O{Z2hGECh+1J5Su%k}@3f zQ%}KBt25-?LFs5PRc3(Z6)%>4VNLhmmC%=D+^`N5p6|;-V>rs>g9m3k3Vih@NBn$& zgnSZ~)~BCvDLQ?1;slQ-nYMm*WKOS-RL%qd+jQjDI=>mSEhwD zO_gMpuxfdmdc5$xxN5G;S4Bow$y9mw8_+ud@b}4JVRIb3)M7c9D?gA}XuEtm6%GfR zXiuN@Kk9!x5Nq_)&Fr8MRDT)8vf2+erfUGw2le?d(7G28-rZwuwwLU)9rl>fy8y67 zJ6tWfNBg}k+BeMY$pB-(IF?Dk_==rEuOW`#gl~gZcgvPm2D=Xkb4%mJ1$Si;fJDis z%Qb8Fto#RNhfn^8pt;c151;g+(dagqqh3^XoEPL>Xq~l2Jw}fJ)~M9H&|?6o_SSme z*Za$$a}t4^BE%FrUyltLy7&8*tlfYcAH(r%a!Sk&I@TmjGr ZS=*nk`{-wfAz%1gCD9JsiP~}d;2(W5`6vJY literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/planner.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/planner.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..054b7e11d4a7ed72de1eb430c53f21858534f296 GIT binary patch literal 15806 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/services.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/services.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cdf1be044cb6ed542539a649637f60a903beee8 GIT binary patch literal 15329 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

sB)Z_8~QaKkV<#hwFCa;a- zbUZ?N6T*7R%Wk+=u4(1RDHT1X58IXrc2>r5Xgm~H>4@@Uc}vECZ-|@o)(rL>Dhz;C zr#K8)_646ZSXEQ)#8n7{1)tKkOpHWXoGLg{k}>up&Y0>V4rw2C=Y^@5kBf>}#L5?< zq~}q2Tj$IPl`;9HG6Q<+qr3OKhu^z@`{ph0!NZ&H{ni7bJ=&a77zn$GGeB6KpPtFR zSbw=aOndF~3e_44K+MoFHQrn^S%iTGnaJ z9%uNv(>>9Qex3qr=3 zt`s?-hWbCdznQVYDG-9d8o&Y>o33U~h8neI22!z}IYdg%jUj8AF+5`N8xGT_w_ECe z8$4rL>$T~b10whfOk-2kn&EUv4ZjBwQJ#&{+4fmdF7*uRRJ((mIMoKtMWMvt6RAt& zJ>^X(m+}Mj1)9weyr7fR?_u}b=xpKkvp`ZMTW+VjY2K920uOv~d%JN zeA}e4bVHSD^&w6iMveK3hvb!)C@%%p_+uy&>X|@Dtr01Oq|3R2aa-am){^B|h=G@^ zmUW4*Ap7^qQDbnZ&&eOY*Z1RCMwGCenIBsIkRx%1Hx-EQ$LOp1u=|H`h7m7CiK4VPq zJ^R@gnRSbO`bT@tF0zy?b23|4pIgXW?$xxF)wI2Dsc&c}tNj$|jGC}7vOW6|d(v{4 zt_|vg#&%0}jZO&^6F9=Th`6OGYQUPsT?^YS^#y0Zs*`iG`*e%+pY9ZVul~FBPuLIG zlh!BfN$U~IYS2diCuR8&wkrH7fe%$3P~jd*i{e>1ukq1kopQy4_re`Hi(3vMzU#Bb zaF4NkIw!Uid~z&N3hb-hNe(c z$0n|v-7^4{kL3|b8z-Ci<^PDAJD$Iwe&s~`r5O0|F^2NFL zQeQ)-g;T`?Xqg*}4GW1R8i0Dv(u=^`6u+-ccRgghD2RO&SHU zgBsUSlFEGPW)vk)?@_du=1pJqJj$eypJ^0}U44KK$f3Yx7_N+{PRdUZ^y%S3A3Fkj zLwp1FQ3-Y^LpDB#gz^&{5^GAOBFjbfRUo6NtiU2X_aTc1kV>Mf?cU!Jg!#&i;A=C;Sf-TJsRnwxJ zbx<4Bi!xSA*eGMAa#q$^5OvXb&N?l+XAygpwTkBFoW3th2W73aUzMkePn=s53t!X@ zxcQ|C6}z6H^3SsmSbHg7y-Rhvx8$fOJeXv(@E@So(1%%kE7iM^bPZ8RCZ;HAK*cCd$vKMs0^Cgh+&kYAz>;L zF!>^*n+>zj986i>@V8R>(jusn!tKqOnTAe0Si`oXwlE=04v1LM^!_S$`g$aU4O8)?D z{vmdx7&Gno$4Jy#WpTj*ieTy#9izq!@iNt>zI04z7+w3NBQ9S36i*;E{R6zx=?7AU zeiTlm_

Dlv%zUnlGl1+4%hGh14IS{a?fGkFX>6zW9;(q1(gVk<1t0Pw)AV z^AvKJ3T8WRfWJVsD4~}|lCF~le2yD_mEO3_bXC8}Qm1i|D3_yot9-d6Yt0X>&L5@s zkT;PkCH?5Ua`ft9eL0p=GjU$N(3AR(VOR`;T63kjw(6Yy5&IES{}-Bh-yZ+~ literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/strategy.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/strategy.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45359886c1b5f8efa07102aef151f74dfe2b79ec GIT binary patch literal 9956 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/transport_config.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/transport_config.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ccb6a46ffa610f8f6a8846ad82338a920199561 GIT binary patch literal 21239 zcmeHPO^h5#R?f_-uCD%ZyKT4IKV#42*t27ox83&s?-=jQ`lrYC?zlbc?wQ%m-JzVF z5mlA#tjwCotaf*AkM>|MAka#zkU%Seut6YJLPA1tLLA`2jT;9FA;iyxy&<@;NPxZ< znOTw9m0i{LAS6VMCS6e(8S%XrFJ45v?`2;<-NTe^<#hNra+%b@QiuVj^&#!6n(iNo?7iM`j}srtOFBn#b#%t*90Fb=Qx$=NOhZrY;RZF4^|H@Fi8 zVv(L&u9bV^ad*A3ZsdAlZ!Bzie6u%2|J1{d=Ry;D_+w3A;D7qgrB=}9m)1Rd>r&H= zE-~Kmf~|{dcE~Ta-6&{s|KjDv&n|xHQX)tE>!nE8ehAIhoxpFn&Be}E?+{6ph}yBE z*4LoEGITmt*2T1@X@fO(DZ}Iyu$3rzVcu>@cDxUYAc9ZTs(3^o@iLDlIXM48;{%W zmC<;wB3rNNy)lA8N|cu=P1AY@gHGfIzU|dRg0a#Uzp9Hv5Z{Om4911;WMC&XP1~H? z(QaxVPJe9d7^1RctY{xqKG1exWcv2!3G{tw46w3>E5gW(L^o<}ncU-K4Gj^r%?EB2 z3qZhXrJmtNy>Z4HcGrt~WmL}No}}jLk?8X80CE)|%aeLpuj=9`V8^KG1L))!{z%s} zu30+MEQ9G*iI>;Mn88XDjlZMDR!Dl zN2v5Ndxc7KR5IDCR60tfGwdvt=Be}=`xKRqQR#Jdj!MU=be`3ybb?A3*r%yCrqbux7pU|KmA=TnL?x3-UuJJm=~XISVLwBq zGgP|DzCxw5?9COe_ExXF(&5hb*T}4{nPJcs4kv%LVGGw@^LS`REju!upxqIeCbsDW zzRw-lH@-I9W>?rK$=Q;b*YKWX*qHjUpIoNC@L6qf)3@8)3>sw1*QARyJ6^YW(e+KY z-H`?juLxuB_bmIuXIWae-YbXPYvc`jB6Uv>Vb3(_%G)y#dNI8<6tY_gIkH8QN9Gy_ z#26lDW)zrq2)|=@I@}f^yi_dRxiD?a^*6X51!8NrG~$@1#SR7jS$dV3^np6m_E9R) z*!B^UD0_dXSU;B{p^@Oxqd+_~ZN>x-!(>pTgbQXM$Sj<|k8IcHSkRdCo@UtLj%&*$ z3m^GNxL}3>1{WUI?E3CUU2eLJ`;psldAwGsBREjzDZ$%8#Opw{sR%qv0xLI%N_#yc zZ8zjXwLLFry1r>UPSEuuGmuNT6lBEFNsyxD@1;n~S){nRPoy0?3?Bu8B?I!}NUKSc z-&bHBAcZD(hGM*(YBOFiR|T!c4YMQM4cFsMZt~EvJ#r0Z*b2HHGuOCjJCVD=N7QL$ zjXOb`|B`}pig3!`hoXEdMOox)WJ6|~N3DS7Z8sbh+yVJ4HjS--{ZPSpSiu!K5-`Yk{IVFg%j=mMiEZj9E$Vpj6ShJ zO)$y{6mqfQIy^ho&9?2dTz^!pmeK5QD=;VIBnrgvHRV={?_E1;Va31-_b#0dOnEvG zI3BzZz^VAOfi3ut6`XR|m77D=d1D~lm6h8x-*Fas$hXXf>v4$E4e1}!^oSNZ3%_AU zy#A1H)zQu$D0q)bz2<5R1%0s)G~eV-H^TmAM0hh{*sAxR`eOy0Tzj+kheErOLW5^& za-R#=F(bFlgKm6a^t@z8;Rb#{y!a1PQP|U3iX3q_2vfGTg@)(vLl;hoOe>rk}$LliAutT$bh?_R$ z`Wu0x>}-ebRjQE77R~T4QlZA;bv3y&RFd_yd)Ni$ZQ>gG#hD!6J6aYqt zj{eE25(fzC8?OD8^zcLrb<}c0b+VXueC9fK#QP_K?BPZ+PaeWaxnYYlbHlY^qO>-& zIdkoi4-QibxW84zm&<^1bEpF3K@lG)AlmAX{QW~g?E4W+BcaZOJo3XcW_s2d(N#<* zpdPwD`==CgdHWC((chD5mL1$jz*q+%Dfy2Xa!F<$motD|Qx6HLZuCC8`=nAZFaBOrA zb6nwc+hJt;4j*+jIi4uGZHEu*iUxj?4$1iT>CBMiYYrQpTs%PacxPDPJmlgGgl!7D z9nU6io|8Au?c1!5B=X1xXkYZNsVL=m1sYIks1)~7o5=g^GY`9K+=;wTP>i_*Vcayy z@$ZTpCo)5#)*32EfBoAfh_~qs5W?GJ`yYteL;`QqA@u*HpqIP7e2t;V_ZR|i)46Cz zk?phw3gGNtfMY5=*)+*9ruV~4r!u1eNtKF2#kpH-Q4j~d;gb^Ps45H^CQ+soQBLj> zC9O78kZ%+V67NX{&kkBTUSMSUjgvTs6mha6ldCvXpw}}3;S9w1TDNI>e1m%Yar2|rm>^wtec?qiK$&jVU%>GYnUm5M__Urs^a zNTbaC^G8B=jk_iGxbEJ?Ygsifpf~+w~ z0$CMF|5)Ra__3;z^06i)*<(#gn#Y=w1dlZ>sU7QpBz3GAN#|GxC6QyzN(#q1B*`1= zu%vCQBa*PO<|I{P9hD@FH81HI>zE{FtmBfBu}(-b#yTl!80#fTz*wgw^J^k zq+6_4B++7-l47x5WpA>#o@mw?_SF@wcCB~tUC-XS5%>|`jNnYS|C~%c_p2G%4|!bv}oQT%Cj4CMw=!d+sI5ffx`EUd%TI5OB5&v zlu$CGi0{YeWZQdWZ-p{?>wxWvqbAN+Ob6oMl*EM5@8~;PUE48sw7R}i+R^I9PI*VG zmzV|`6Abkf_~MvH>K&+eJbMejKs~+5dU{^y9js?rjoKLT1ieY+@oDvGWg=$n9&_CF z-Kbu_3}-;xre2}{wvQ{?qoppXI@n_&C1OWgkA>02Ta;`th`=B;Ure2kzI`I!Q!46c zU14t326qML^M=yZ%lWRdnjG98)B@4giEv)783JB{d|E-baCcXgZePlG8=o6M;q0qV zxX^+=aqF!S`Yh&K3gm>dF6a}tt}A`c=jD`Pk$s7XWuc%u+*(q)J3Y9&J^6(KA+Zh> z^om=zm0ouZW*qz3rPy@(Be^4j3F+&`?Ox=+q49(I&DR3ED$o z@3IQ;l+>e7mv>vrPAW0&H=fb=r^Yj^U!~>V!NgkP)@TzN zn}t}y;O_wu8&(F0SQ9m4TA$Xp&*ot!9Y9NiAde?`BG|$*of%JJ7s5)c{J13D2^&+s zSMa@2x~Y+i8DCB{4(S1fQm(K!ZfZN)hvz@m)1*DUGtabV`c3V_*^iB=ygv4{vSU2c ze^~nHK!7WNHLp>*Gw*#N5C(KlG(5V@{ z@g|S#C=%pv3oHvqjE4XO!8D^H2X2?oOF*USMMh7W)l)E!aB?Z=7PmGOgFczpLIJe^ zpF@d6*#0C2B9kzq@{YDXwqBv@cvOuhVxz$~GMNU>9F-p!81uZjz^!7a$z0%8$y{co zxjZ#?^k@1H4VVROCTpd>4T#$i(-3!H6v%JPZ8yc&HBI|&B5Ot8zL=LaFsOpy|Y@szI5Z>-KF~5?_9gJLN;I>PHwHdck`aSQPaf{;F%m~xJcb2Yv@hK3T}9I zGwe+g1o|$PVO{!-f`P+9bPK}FN_)|(x`C~;F^LJkYg3zwmNA^vGhvU=_e@BiU8YlR z&7if2oK67{9EN{B(W<8JUUrgL)`pvk(xh|H7Nd)fWBmxd zquLLY-p?2HzGn=6Pdy;EFai=(`(*{mE{C7!L5Z`Et`5XDMgW0ozoLM6twh~ZygkBr5No0YP|KTrfC--F*YyNv9eyRR7(T?8;^e*uTE7<)tLb`O4Smz_Wu#r zC&MGIGk4{SEAP8D&W84bQk(~n{XGu3QouF9%Ocr$Ys$Od4#Mbt6V~*@_-oL8%Ac!-ry9Dj#}Im2p0GA{v~uJL@jtn@R0k+gttR#hN8($5p+9gZcMmne!xw~ z_VHp45(aI5i<;_iaS;m5_OgJc5D=%=5%3<5xyHS~Z-z;Nl;F0PB%lI%EMFWYM;EdI z?9FHtLErdIZnoSo3WSTa3S=xg6f$>g&*>t2*>4i6^nHkhI9CJX-nJjo@RL$Hf;*gX z-=P;3q<)ZKm>UFYjg*>XHK7hPf4VOcIw<)?ZX`~~9HIKJtK2ASio&^5gz+a%8659_Sk;xIbt)MH?M?K`qxH2C;&XP`{ zQYYXAyx`HR^r%BofcOZWt{4aw6)^%kV>?>CEZ+^W-$qlCGaS zi4W2g(!h4+eG)ZODoKA^Fx^GINH2pSnMUR-6ZGU0?+W^qXpDooPDFM^GjBJB)IYRs z*bu7Y6)>>{Rc^S*F?Rpjgvdol%U-x5bb?#40u=~z&ADN37_KrmEEJM5kOZ3h`GNQ(ibAR?-dm;j zC_MGuq1qU+L%qp5+#i`~Z>F9xq~3H=##mU#>I@!G1l=&hz||@;@(V_(H<7lcu-MnJ z1~rwDHYcgIN@@F8cEKIYt)(08!|C=CvF*-mWUS+jUSqj;I0+;6Jz1?R zR)Mk90U>no=_VFCfPnMbcJ7v#%I@eR` zzsSE0-v5IS>c>nvr<7+k;BPmiiw6nEZK( zqPiz@po?g|bR_pe9FJ;gIg1!p*+-00Ji-z&jqs6Vgzqoiy|=nle{W^!e!&=nCj3b% zhKz9*h=IN`iceQBShI{kCv=w4E3XBCC*H^LYcLm!lgPg)BYrfeJUJ^f9j1Cq*qzQi zShS}G&4c-5|zP>7m z?VTEkd{5dDMg#MdT))1Tp1?Q$hoYza=)@xK8XZhp3JBwPCr2k%)bUtp#xpryrRCnS zoWc?Z+kYb=?glp!vNF|Ufv&)={Aa2w-GHtN(4*PRQ11(UW=f-}{x69jqhf}#%0@D+ zCFZ<$PTI@>PHpo!<}_%`{+vc>_?+IuZ{sLGnaUvN^pTv6J~wkJZfcq~h$2m`VI*es z5b)8u0%kOC6-C78K9zhbv1+*bJn`8(p4`pf*{%HJiHmsm{+;))tuEESzVxm7+jrhs zDzJR8u*cI8kc-1B`4QObU9D8m;bfWZchFq2oCR$v*b9d?9dqvw0P^qs20oE7I z*q+ZRZV)}1>T5(sSGts2uhnOIO7~9ei|!?=ckW|j zM;G%Mx_7?M>kZ+1CD&JyTpy)F6cK)(OO#mAjl+la?N8^F2=gjpd9946M=fhsweD;8 zZ>{v#GPI=dz}7Lf>WzDM@7`OMPvoXjLKIuMnX`KT8r82=6lVugP@1<9guQX@Z@41x z3;Y5&<-Up^3f7p+9gO5IbDv|NrIC68(>}w>4I?I>D+R_2c8|#C*VJ5vfq4r5 z6NY;~(LqHw%G(P$9i)8jAga1QBnq8O$qI9c&xmzMOmofX8(4uQfo*;_nMue_nAv?g zOCe=OB{iTuDCTr6FX$ZuYN2;>(YtqbO5kPmzGH@YSl_7jq0;wDxq(RfelCJ}Sm!Xo z2TJGX3Oavbet8%W@XPoMNy!N0m!d=qFG4R5!vK1@pSO_ZjMl)}&*Md>MmOpTUa!NZ zHG#ELT){;klA8RvhDG`lf{XO011Y<#cPL)g7oEVz3&l~`n@GRP?P7BLT#VnPira8E z1%^3&znlJ6pUkPlf-Td;k*(Gnzjpn`T^`xJij1_=n?^)_lL$)enWE72XZNaCWp8gH i`bMFOX?^ZPCmS(@FmHr!w)#R=K literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/affinity.py b/mitogen-0.2.7/ansible_mitogen/affinity.py new file mode 100644 index 000000000..09a6ace --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/affinity.py @@ -0,0 +1,269 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" +As Mitogen separates asynchronous IO out to a broker thread, communication +necessarily involves context switching and waking that thread. When application +threads and the broker share a CPU, this can be almost invisibly fast - around +25 microseconds for a full A->B->A round-trip. + +However when threads are scheduled on different CPUs, round-trip delays +regularly vary wildly, and easily into milliseconds. Many contributing factors +exist, not least scenarios like: + +1. A is preempted immediately after waking B, but before releasing the GIL. +2. B wakes from IO wait only to immediately enter futex wait. +3. A may wait 10ms or more for another timeslice, as the scheduler on its CPU + runs threads unrelated to its transaction (i.e. not B), wake only to release + its GIL, before entering IO sleep waiting for a reply from B, which cannot + exist yet. +4. B wakes, acquires GIL, performs work, and sends reply to A, causing it to + wake. B is preempted before releasing GIL. +5. A wakes from IO wait only to immediately enter futex wait. +6. B may wait 10ms or more for another timeslice, wake only to release its GIL, + before sleeping again. +7. A wakes, acquires GIL, finally receives reply. + +Per above if we are unlucky, on an even moderately busy machine it is possible +to lose milliseconds just in scheduling delay, and the effect is compounded +when pairs of threads in process A are communicating with pairs of threads in +process B using the same scheme, such as when Ansible WorkerProcess is +communicating with ContextService in the connection multiplexer. In the worst +case it could involve 4 threads working in lockstep spread across 4 busy CPUs. + +Since multithreading in Python is essentially useless except for waiting on IO +due to the presence of the GIL, at least in Ansible there is no good reason for +threads in the same process to run on distinct CPUs - they always operate in +lockstep due to the GIL, and are thus vulnerable to issues like above. + +Linux lacks any natural API to describe what we want, it only permits +individual threads to be constrained to run on specific CPUs, and for that +constraint to be inherited by new threads and forks of the constrained thread. + +This module therefore implements a CPU pinning policy for Ansible processes, +providing methods that should be called early in any new process, either to +rebalance which CPU it is pinned to, or in the case of subprocesses, to remove +the pinning entirely. It is likely to require ongoing tweaking, since pinning +necessarily involves preventing the scheduler from making load balancing +decisions. +""" + +import ctypes +import mmap +import multiprocessing +import os +import struct + +import mitogen.core +import mitogen.parent + + +try: + _libc = ctypes.CDLL(None, use_errno=True) + _strerror = _libc.strerror + _strerror.restype = ctypes.c_char_p + _pthread_mutex_init = _libc.pthread_mutex_init + _pthread_mutex_lock = _libc.pthread_mutex_lock + _pthread_mutex_unlock = _libc.pthread_mutex_unlock + _sched_setaffinity = _libc.sched_setaffinity +except (OSError, AttributeError): + _libc = None + _strerror = None + _pthread_mutex_init = None + _pthread_mutex_lock = None + _pthread_mutex_unlock = None + _sched_setaffinity = None + + +class pthread_mutex_t(ctypes.Structure): + """ + Wrap pthread_mutex_t to allow storing a lock in shared memory. + """ + _fields_ = [ + ('data', ctypes.c_uint8 * 512), + ] + + def init(self): + if _pthread_mutex_init(self.data, 0): + raise Exception(_strerror(ctypes.get_errno())) + + def acquire(self): + if _pthread_mutex_lock(self.data): + raise Exception(_strerror(ctypes.get_errno())) + + def release(self): + if _pthread_mutex_unlock(self.data): + raise Exception(_strerror(ctypes.get_errno())) + + +class State(ctypes.Structure): + """ + Contents of shared memory segment. This allows :meth:`Manager.assign` to be + called from any child, since affinity assignment must happen from within + the context of the new child process. + """ + _fields_ = [ + ('lock', pthread_mutex_t), + ('counter', ctypes.c_uint8), + ] + + +class Policy(object): + """ + Process affinity policy. + """ + def assign_controller(self): + """ + Assign the Ansible top-level policy to this process. + """ + + def assign_muxprocess(self): + """ + Assign the MuxProcess policy to this process. + """ + + def assign_worker(self): + """ + Assign the WorkerProcess policy to this process. + """ + + def assign_subprocess(self): + """ + Assign the helper subprocess policy to this process. + """ + +class FixedPolicy(Policy): + """ + :class:`Policy` for machines where the only control method available is + fixed CPU placement. The scheme here was tested on an otherwise idle 16 + thread machine. + + - The connection multiplexer is pinned to CPU 0. + - The Ansible top-level (strategy) is pinned to CPU 1. + - WorkerProcesses are pinned sequentually to 2..N, wrapping around when no + more CPUs exist. + - Children such as SSH may be scheduled on any CPU except 0/1. + + If the machine has less than 4 cores available, the top-level and workers + are pinned between CPU 2..N, i.e. no CPU is reserved for the top-level + process. + + This could at least be improved by having workers pinned to independent + cores, before reusing the second hyperthread of an existing core. + + A hook is installed that causes :meth:`reset` to run in the child of any + process created with :func:`mitogen.parent.detach_popen`, ensuring + CPU-intensive children like SSH are not forced to share the same core as + the (otherwise potentially very busy) parent. + """ + def __init__(self, cpu_count=None): + #: For tests. + self.cpu_count = cpu_count or multiprocessing.cpu_count() + self.mem = mmap.mmap(-1, 4096) + self.state = State.from_buffer(self.mem) + self.state.lock.init() + + if self.cpu_count < 2: + # uniprocessor + self._reserve_mux = False + self._reserve_controller = False + self._reserve_mask = 0 + self._reserve_shift = 0 + elif self.cpu_count < 4: + # small SMP + self._reserve_mux = True + self._reserve_controller = False + self._reserve_mask = 1 + self._reserve_shift = 1 + else: + # big SMP + self._reserve_mux = True + self._reserve_controller = True + self._reserve_mask = 3 + self._reserve_shift = 2 + + def _set_affinity(self, mask): + mitogen.parent._preexec_hook = self._clear + self._set_cpu_mask(mask) + + def _balance(self): + self.state.lock.acquire() + try: + n = self.state.counter + self.state.counter += 1 + finally: + self.state.lock.release() + + self._set_cpu(self._reserve_shift + ( + (n % (self.cpu_count - self._reserve_shift)) + )) + + def _set_cpu(self, cpu): + self._set_affinity(1 << cpu) + + def _clear(self): + all_cpus = (1 << self.cpu_count) - 1 + self._set_affinity(all_cpus & ~self._reserve_mask) + + def assign_controller(self): + if self._reserve_controller: + self._set_cpu(1) + else: + self._balance() + + def assign_muxprocess(self): + self._set_cpu(0) + + def assign_worker(self): + self._balance() + + def assign_subprocess(self): + self._clear() + + +class LinuxPolicy(FixedPolicy): + def _mask_to_bytes(self, mask): + """ + Convert the (type long) mask to a cpu_set_t. + """ + chunks = [] + shiftmask = (2 ** 64) - 1 + for x in range(16): + chunks.append(struct.pack('>= 64 + return mitogen.core.b('').join(chunks) + + def _set_cpu_mask(self, mask): + s = self._mask_to_bytes(mask) + _sched_setaffinity(os.getpid(), len(s), s) + + +if _sched_setaffinity is not None: + policy = LinuxPolicy() +else: + policy = Policy() diff --git a/mitogen-0.2.7/ansible_mitogen/affinity.pyc b/mitogen-0.2.7/ansible_mitogen/affinity.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1aa3a7452cfcd5b8d32c77e56322c1b994db4392 GIT binary patch literal 11086 zcmd5?O>Z1Wdaj-!DYHd=_)C`T^}5#CB=lxDvbD&L(4_*}gmw529=wIQ%E24i@JTyNPYjjP}t76m=`>Alh5J{5cEz#e?A^nT| ze1V@Y@$(8luL_fhpXO_P;%j1*hL z<)=E>F&XKpR89r*Qu&3+Ce9jLd3pCUX{#WEl}Zks-6to*#F5H7GPAQ;Wpt*3wk9={ zk@w2!Vj;CTu*Cs+nOPI)Fi1bwA)9D3mdfOEuJ$nu6BX*sy7J+Kj8tV=H2cLRlSr8cX85!lY^|H|WbTdtFbmgwpUY3q%RtCzYp3NxNDp0YIY81d! zRKV?y99BUNX=EKqN7ykchUAa$-s`6CbmeWlOkR$hoxvF9N(X68v4Bae?o0+dkE%e2 z{8BglJ}8-~MO<^^?U|R>$r;XK=^Gm+Feg1DU+9cFQh8Q&BVq>;>);{WR7&Y8gBM_Z zURK6Yp}-#q30?-Lj8_?qQEuxlby-Tc-|8?2OPV8_g#EDU$9L~_8VN895WwBfq%Vj{ zrkusk*5asyT|pAJJ90kJ*#y>SY`|`;8o8j*P2XM0ts_oP~BRMAtZ>&sFW&4W`EJYbf z2V~^T<^&`w7IIkmg`6os52#`RS`U1nG+0?!Pfxj?oK}n>jR}HacI2ok;1G>Ql<`iP zot3~A%2P&(QfcR<9W6~%8(BIFSd_a=?FwR=v7GBLIc-teEV?bD9pG56W)XX4)RDf* zCU6!$cGoaJ@}YJ6bm@BW zgGHFY?Y$)L$pp>DP@=?42&N@BCN4S-u*1iD}wicozjsfY|g4+DC#a%xyGC))dpBC1EL@22TJZK^|AsB9l`aUqQgl~aY>eQ*~< z<>WJ`ha~5StaFlcWkLsb&wyQ$o9Wdowz#*b{x?I5-!Vt_($Z4SRa3KxNmnYQA_Gxg^@Zgim8tY#D@Hb?AGLHY?>F!)sk;y7`5)&P)5 zbG=7Ll8zC|rjCZHP%x?p`*$9E-r$KgQC=NHx5lI3EOsP)H9RJW?S^LdfKoQonhDsr zqhisOcNta?$76VP#Gyl4Gqzwi%!vbcM|yUidWX|fuhIbw*+UpV>a@kh_%(|eKw(ub zBWK!-)0{HxwKcw*>My_i^2_^SQwULc8jy3whD$<-GAe5@L!=*sWtQI-Lfl4T`8z@g z5}etKI;W?$n64rBd}-7Y~%F7fviQhT=3rbV6iKNB$5}BXXVpfmCsQk z-1q)R;ehn}Unv(rBFm+RLwoCZKnlHckS5mlp2O%2SjGuWR(8zwrLUdqm!GwU%KSg` zU1z`ZRlc|3#)6?AuXM#vLq8%A$Qa!LR$=`TxZuA4+1am*@Utc5QG6n{n-j7 z+cw;xhn7GO@ZP(O2m2qpe3w zNgIm9U=7_L4Az_2!WxBX_%|DkHfi!w*fEuea$3S4&gAgG(Icr5-+$cff}dU1F2~p= z2MM;^Dd>QTCPCrEl#4g62I49BxgYV3#eG(A2MtD38lWBHY*CQ=vxw&_q3|nsMKotK zJHhLx)nOyUIt%-zFgdFZ>zwP)VY^p<2_|7zr!o0-N%c+O6R27%69pH!zxZW&{3@fJ zP*IXw$yC1)e6ASnaSr{L4X>wS)+_k-8Xm9W@!DBp))gVdpRUB8WOE(A0g>-xeMtEU z@InH};59r6UMYImKTjC8B7Utm_;p%toNq9+ZHm}9?x$!P1MJR=*fj2MA$)-je{HR{ z>YG`iyubM<67`7drMfA?Jp?X5>n=_W5p~HV)q&Clm!#UWhm61-mp^5pGOo%H{F>f` zcoxZt72O%#=-gvK$T-+Y0N$I9Mxx>4i-jd+{O;c|%5pPEhfyRrso?UvQS^AWhqyO!QODoEyr!Oipq2eAyu=1U5_kKstohoB5i(AIA4P&@d zCuMAKqRA0Eg3T#+5aS&h?RE2JZ|@UswGB`7l#vjiysTRtZ(qMrOXe;*OVo%u!>2|L>-H(nXmsE_xdUnn|CMc7 zWi{N^WO61HyF|;T3pxWc)Z?)+Fi{7nXKQjAVWW?0aw^H>WVodod0O^o9j)}kxQ{O< zi;`TdNRj-Y1&9tgc85G_l6K|9+I?`MjSsQv=UxCht+Y(oz-Hh)4Xux+l!xBp-iuXk zW#)+6&)mklIjT%{^HE)4bxVayfo@JgWs^Z^OEQl-l8moVs`55>9(=x|O(5g-0mW<$ z)`9AuO#}Ktn}1|o7W*;r>Vu1UGy}n(R8g6j+&*@)(uQT{?0{VCgT5tes`&T}kc9us z68N8yhb5xCghF_lpx}Kh#2`VTH*K!wj#@m~6ve2Q+o%We<_3S<5YuyF`W%)gbvfM@ z`$D)sK?#ia(q?cT2^wa$n!yEO`n(HYnPJu6h;Z?lReTaw#lZz(zPs{TeE&QkvfjMS zgA2HP+}lQ;b)VZCT3i-FLkL{MYh-yEd60%swvi5V%Of^LWA3KSm`n7mtOlGxa|Jz{ zsWM>EjAoqBawI^0KNwcHBI3%#qcZ8egy_U27gExXRpBXYq28*!3<*r^Bs3GpX&cA? z<@`+f{eaAUIpZgK6e;?NtBCs(oLBa~{Zusk`q~GvYD5EJF!*Z(o$sPxZ@rx0_j2n} zYcs*`R3{2gQ!+bwnD0djyruq=wD zET$|AIMW}#$)3R;_k=xeD#Y59FUsF<4cg*i9vfKfz0);b^i#OtIxpOSAn}piV!9!& zUU_9HSDb#IZJ+(4he9b@DpH9aS{Yo?M}^lC?ENOD*|e~pEiFJp<}75$rdD@h{~qFl z<%{HU(oVLLE3M~STgOJV1T_#2p7~D>9w*|~X&i&(-rMkyV>YluR#s=gNDsZne*yzI zO~kenhD)NqhhAl);ahnkuGaca8Wpz0r4LWz1Y58|n4R8lJk1j%sEPu{@6UoU(ETsR zhW-3;M-A+;VZ&mgKHN1P#BOUM5|i@h7Mm^mIu_M7$$b4fn1k?o-v?i;HL!FRte}|q z1z15-LL{5X<<_z*uw({B%F?(_sDnX-Nd(1bODu|>e?4JPI2!+1>hP3ED1c-INSVO1 zmnI6bfBV%5gk7Dj1W_#h$1#ETw*lBEnqnLv?ps4Wv=XC(L3Gh1LF4~v3HmJ|#9x7A zB4OtorV*Cl z2W(4d$c=?~(n96=q%97Ah&=$+qf)|g*D^1B!V`I2PF~J z5u56w3qLWv!=InU%nt2M2Hp>Tk6S|pm}`D6$B$qBQT)9WgCXZ>PMI-ra=~5LoQFQR zDvSA6sj~gvZy{quR7Y;kk#NW#r&b$IT4FMG9j`&4*`zZ2@ydrYH%{oFp2IW1w3sR4 zh(WM};UbX#F9-vU*OQAehyrlI2&)|QM)-)S*pn8*eAMp|a~ze1XZtC9 zfpB}UwSX4#fG;7Tp(YIqurcdd7-I^h&N=WFWFq7P-lMo6l`{!ixY=4yRcENH))J{~FHZPCU${_XZxX-c1M;YHP|(m|HYH zAbm4iXThw-kM7;;y@$0Roeg?W=wZewL30bqmvB6j4YG-HgOU?#M;^|`8a(B06P9wy zcYM!<+nE4-dU{=b3kD9j(f5p5^k8;;_-A`R;tNvnwHxB!!%>{tFGlTl@-o*%u@btFyqLiI Y;&0l%_VTr@D_5`l?8@_7$yV#X0hgUCV*mgE literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/compat/__init__.py b/mitogen-0.2.7/ansible_mitogen/compat/__init__.py new file mode 100644 index 000000000..e69de29 diff --git a/mitogen-0.2.7/ansible_mitogen/compat/simplejson/__init__.py b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/__init__.py new file mode 100644 index 000000000..d5b4d39 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/__init__.py @@ -0,0 +1,318 @@ +r"""JSON (JavaScript Object Notation) is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +:mod:`simplejson` exposes an API familiar to users of the standard library +:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained +version of the :mod:`json` library contained in Python 2.6, but maintains +compatibility with Python 2.4 and Python 2.5 and (currently) has +significant performance advantages, even without using the optional C +extension for speedups. + +Encoding basic Python object hierarchies:: + + >>> import simplejson as json + >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print json.dumps("\"foo\bar") + "\"foo\bar" + >>> print json.dumps(u'\u1234') + "\u1234" + >>> print json.dumps('\\') + "\\" + >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> json.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Compact encoding:: + + >>> import simplejson as json + >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) + '[1,2,3,{"4":5,"6":7}]' + +Pretty printing:: + + >>> import simplejson as json + >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) + >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + { + "4": 5, + "6": 7 + } + +Decoding JSON:: + + >>> import simplejson as json + >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj + True + >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar' + True + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> json.load(io)[0] == 'streaming API' + True + +Specializing JSON object decoding:: + + >>> import simplejson as json + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + >>> import decimal + >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1') + True + +Specializing JSON object encoding:: + + >>> import simplejson as json + >>> def encode_complex(obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... raise TypeError(repr(o) + " is not JSON serializable") + ... + >>> json.dumps(2 + 1j, default=encode_complex) + '[2.0, 1.0]' + >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j) + '[2.0, 1.0]' + >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j)) + '[2.0, 1.0]' + + +Using simplejson.tool from the shell to validate and pretty-print:: + + $ echo '{"json":"obj"}' | python -m simplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -m simplejson.tool + Expecting property name: line 1 column 2 (char 2) +""" +__version__ = '2.0.9' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONEncoder', +] + +__author__ = 'Bob Ippolito ' + +from decoder import JSONDecoder +from encoder import JSONEncoder + +_default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, +) + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and object + members will be pretty-printed with that indent level. An indent level + of 0 will only insert newlines. ``None`` is the most compact representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + iterable = _default_encoder.iterencode(obj) + else: + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, + default=default, **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is false then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + return _default_encoder.encode(obj) + if cls is None: + cls = JSONEncoder + return cls( + skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, default=default, + **kw).encode(obj) + + +_default_decoder = JSONDecoder(encoding=None, object_hook=None) + + +def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + If the contents of ``fp`` is encoded with an ASCII based encoding other + than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must + be specified. Encodings that are not ASCII based (such as UCS-2) are + not allowed, and should be wrapped with + ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` + object and passed to ``loads()`` + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + return loads(fp.read(), + encoding=encoding, cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, **kw) + + +def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding + other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name + must be specified. Encodings that are not ASCII based (such as UCS-2) + are not allowed and should be decoded to ``unicode`` first. + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN, null, true, false. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + if (cls is None and encoding is None and object_hook is None and + parse_int is None and parse_float is None and + parse_constant is None and not kw): + return _default_decoder.decode(s) + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + if parse_float is not None: + kw['parse_float'] = parse_float + if parse_int is not None: + kw['parse_int'] = parse_int + if parse_constant is not None: + kw['parse_constant'] = parse_constant + return cls(encoding=encoding, **kw).decode(s) diff --git a/mitogen-0.2.7/ansible_mitogen/compat/simplejson/decoder.py b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/decoder.py new file mode 100644 index 000000000..b769ea4 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/decoder.py @@ -0,0 +1,354 @@ +"""Implementation of JSONDecoder +""" +import re +import sys +import struct + +from simplejson.scanner import make_scanner +try: + from simplejson._speedups import scanstring as c_scanstring +except ImportError: + c_scanstring = None + +__all__ = ['JSONDecoder'] + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + + +def errmsg(msg, doc, pos, end=None): + # Note that this function is called from _speedups + lineno, colno = linecol(doc, pos) + if end is None: + #fmt = '{0}: line {1} column {2} (char {3})' + #return fmt.format(msg, lineno, colno, pos) + fmt = '%s: line %d column %d (char %d)' + return fmt % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})' + #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end) + fmt = '%s: line %d column %d - line %d column %d (char %d - %d)' + return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end) + + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, +} + +STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): + """Scan the string s for a JSON string. End is the index of the + character in s after the quote that started the JSON string. + Unescapes all valid JSON string escape sequences and raises ValueError + on attempt to decode an invalid string. If strict is False then literal + control characters are allowed in the string. + + Returns a tuple of the decoded string and the index of the character in s + after the end quote.""" + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + end = chunk.end() + content, terminator = chunk.groups() + # Content is contains zero or more unescaped string characters + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + # Terminator is the end of string, a literal control character, + # or a backslash denoting that an escape sequence follows + if terminator == '"': + break + elif terminator != '\\': + if strict: + msg = "Invalid control character %r at" % (terminator,) + #msg = "Invalid control character {0!r} at".format(terminator) + raise ValueError(errmsg(msg, s, end)) + else: + _append(terminator) + continue + try: + esc = s[end] + except IndexError: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + # If not a unicode escape sequence, must be in the lookup table + if esc != 'u': + try: + char = _b[esc] + except KeyError: + msg = "Invalid \\escape: " + repr(esc) + raise ValueError(errmsg(msg, s, end)) + end += 1 + else: + # Unicode escape sequence + esc = s[end + 1:end + 5] + next_end = end + 5 + if len(esc) != 4: + msg = "Invalid \\uXXXX escape" + raise ValueError(errmsg(msg, s, end)) + uni = int(esc, 16) + # Check for surrogate pair on UCS-4 systems + if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: + msg = "Invalid \\uXXXX\\uXXXX surrogate pair" + if not s[end + 5:end + 7] == '\\u': + raise ValueError(errmsg(msg, s, end)) + esc2 = s[end + 7:end + 11] + if len(esc2) != 4: + raise ValueError(errmsg(msg, s, end)) + uni2 = int(esc2, 16) + uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + next_end += 6 + char = unichr(uni) + end = next_end + # Append the unescaped character + _append(char) + return u''.join(chunks), end + + +# Use speedup if available +scanstring = c_scanstring or py_scanstring + +WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) +WHITESPACE_STR = ' \t\n\r' + +def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + pairs = {} + # Use a slice to prevent IndexError from being raised, the following + # check will raise a more specific ValueError if the string is empty + nextchar = s[end:end + 1] + # Normally we expect nextchar == '"' + if nextchar != '"': + if nextchar in _ws: + end = _w(s, end).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + return pairs, end + 1 + elif nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end)) + end += 1 + while True: + key, end = scanstring(s, end, encoding, strict) + + # To skip some function call overhead we optimize the fast paths where + # the JSON key separator is ": " or just ":". + if s[end:end + 1] != ':': + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise ValueError(errmsg("Expecting : delimiter", s, end)) + + end += 1 + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + try: + value, end = scan_once(s, end) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + pairs[key] = value + + try: + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + end += 1 + + if nextchar == '}': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + + try: + nextchar = s[end] + if nextchar in _ws: + end += 1 + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + + end += 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end - 1)) + + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + +def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + values = [] + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + # Look-ahead for trivial empty array + if nextchar == ']': + return values, end + 1 + _append = values.append + while True: + try: + value, end = scan_once(s, end) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + _append(value) + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end)) + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + return values, end + +class JSONDecoder(object): + """Simple JSON decoder + + Performs the following translations in decoding by default: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + + """ + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True): + """``encoding`` determines the encoding used to interpret any ``str`` + objects decoded by this instance (utf-8 by default). It has no + effect when decoding ``unicode`` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as ``unicode``. + + ``object_hook``, if specified, will be called with the result + of every JSON object decoded and its return value will be used in + place of the given ``dict``. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + """ + self.encoding = encoding + self.object_hook = object_hook + self.parse_float = parse_float or float + self.parse_int = parse_int or int + self.parse_constant = parse_constant or _CONSTANTS.__getitem__ + self.strict = strict + self.parse_object = JSONObject + self.parse_array = JSONArray + self.parse_string = scanstring + self.scan_once = make_scanner(self) + + def decode(self, s, _w=WHITESPACE.match): + """Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise ValueError(errmsg("Extra data", s, end, len(s))) + return obj + + def raw_decode(self, s, idx=0): + """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning + with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + + """ + try: + obj, end = self.scan_once(s, idx) + except StopIteration: + raise ValueError("No JSON object could be decoded") + return obj, end diff --git a/mitogen-0.2.7/ansible_mitogen/compat/simplejson/encoder.py b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/encoder.py new file mode 100644 index 000000000..cf58290 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/encoder.py @@ -0,0 +1,440 @@ +"""Implementation of JSONEncoder +""" +import re + +try: + from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii +except ImportError: + c_encode_basestring_ascii = None +try: + from simplejson._speedups import make_encoder as c_make_encoder +except ImportError: + c_make_encoder = None + +ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +HAS_UTF8 = re.compile(r'[\x80-\xff]') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', +} +for i in range(0x20): + #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +# Assume this produces an infinity on all machines (probably not guaranteed) +INFINITY = float('1e66666') +FLOAT_REPR = repr + +def encode_basestring(s): + """Return a JSON representation of a Python string + + """ + def replace(match): + return ESCAPE_DCT[match.group(0)] + return '"' + ESCAPE.sub(replace, s) + '"' + + +def py_encode_basestring_ascii(s): + """Return an ASCII-only JSON representation of a Python string + + """ + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + n = ord(s) + if n < 0x10000: + #return '\\u{0:04x}'.format(n) + return '\\u%04x' % (n,) + else: + # surrogate pair + n -= 0x10000 + s1 = 0xd800 | ((n >> 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii + +class JSONEncoder(object): + """Extensible JSON encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + + """ + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None): + """Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is false, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is true, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is true, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is true, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is true, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that + indent level. An indent level of 0 will only insert newlines. + None is the most compact representation. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.indent = indent + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + def default(self, o): + """Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + + """ + raise TypeError(repr(o) + " is not JSON serializable") + + def encode(self, o): + """Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = self.iterencode(o, _one_shot=True) + if not isinstance(chunks, (list, tuple)): + chunks = list(chunks) + return ''.join(chunks) + + def iterencode(self, o, _one_shot=False): + """Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + if self.ensure_ascii: + _encoder = encode_basestring_ascii + else: + _encoder = encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + + def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY): + # Check for specials. Note that this type of test is processor- and/or + # platform-specific, so do tests which don't depend on the internals. + + if o != o: + text = 'NaN' + elif o == _inf: + text = 'Infinity' + elif o == _neginf: + text = '-Infinity' + else: + return _repr(o) + + if not allow_nan: + raise ValueError( + "Out of range float values are not JSON compliant: " + + repr(o)) + + return text + + + if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys: + _iterencode = c_make_encoder( + markers, self.default, _encoder, self.indent, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, self.allow_nan) + else: + _iterencode = _make_iterencode( + markers, self.default, _encoder, self.indent, floatstr, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot) + return _iterencode(o, 0) + +def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, + ## HACK: hand-optimized bytecode; turn globals into locals + False=False, + True=True, + ValueError=ValueError, + basestring=basestring, + dict=dict, + float=float, + id=id, + int=int, + isinstance=isinstance, + list=list, + long=long, + str=str, + tuple=tuple, + ): + + def _iterencode_list(lst, _current_indent_level): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + buf = '[' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + else: + newline_indent = None + separator = _item_separator + first = True + for value in lst: + if first: + first = False + else: + buf = separator + if isinstance(value, basestring): + yield buf + _encoder(value) + elif value is None: + yield buf + 'null' + elif value is True: + yield buf + 'true' + elif value is False: + yield buf + 'false' + elif isinstance(value, (int, long)): + yield buf + str(value) + elif isinstance(value, float): + yield buf + _floatstr(value) + else: + yield buf + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (_indent * _current_indent_level)) + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(dct, _current_indent_level): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + item_separator = _item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = _item_separator + first = True + if _sort_keys: + items = dct.items() + items.sort(key=lambda kv: kv[0]) + else: + items = dct.iteritems() + for key, value in items: + if isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = _floatstr(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif isinstance(key, (int, long)): + key = str(key) + elif _skipkeys: + continue + else: + raise TypeError("key " + repr(key) + " is not a string") + if first: + first = False + else: + yield item_separator + yield _encoder(key) + yield _key_separator + if isinstance(value, basestring): + yield _encoder(value) + elif value is None: + yield 'null' + elif value is True: + yield 'true' + elif value is False: + yield 'false' + elif isinstance(value, (int, long)): + yield str(value) + elif isinstance(value, float): + yield _floatstr(value) + else: + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (_indent * _current_indent_level)) + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(o, _current_indent_level): + if isinstance(o, basestring): + yield _encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield _floatstr(o) + elif isinstance(o, (list, tuple)): + for chunk in _iterencode_list(o, _current_indent_level): + yield chunk + elif isinstance(o, dict): + for chunk in _iterencode_dict(o, _current_indent_level): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + o = _default(o) + for chunk in _iterencode(o, _current_indent_level): + yield chunk + if markers is not None: + del markers[markerid] + + return _iterencode diff --git a/mitogen-0.2.7/ansible_mitogen/compat/simplejson/scanner.py b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/scanner.py new file mode 100644 index 000000000..adbc6ec --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/compat/simplejson/scanner.py @@ -0,0 +1,65 @@ +"""JSON token scanner +""" +import re +try: + from simplejson._speedups import make_scanner as c_make_scanner +except ImportError: + c_make_scanner = None + +__all__ = ['make_scanner'] + +NUMBER_RE = re.compile( + r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', + (re.VERBOSE | re.MULTILINE | re.DOTALL)) + +def py_make_scanner(context): + parse_object = context.parse_object + parse_array = context.parse_array + parse_string = context.parse_string + match_number = NUMBER_RE.match + encoding = context.encoding + strict = context.strict + parse_float = context.parse_float + parse_int = context.parse_int + parse_constant = context.parse_constant + object_hook = context.object_hook + + def _scan_once(string, idx): + try: + nextchar = string[idx] + except IndexError: + raise StopIteration + + if nextchar == '"': + return parse_string(string, idx + 1, encoding, strict) + elif nextchar == '{': + return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook) + elif nextchar == '[': + return parse_array((string, idx + 1), _scan_once) + elif nextchar == 'n' and string[idx:idx + 4] == 'null': + return None, idx + 4 + elif nextchar == 't' and string[idx:idx + 4] == 'true': + return True, idx + 4 + elif nextchar == 'f' and string[idx:idx + 5] == 'false': + return False, idx + 5 + + m = match_number(string, idx) + if m is not None: + integer, frac, exp = m.groups() + if frac or exp: + res = parse_float(integer + (frac or '') + (exp or '')) + else: + res = parse_int(integer) + return res, m.end() + elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': + return parse_constant('NaN'), idx + 3 + elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': + return parse_constant('Infinity'), idx + 8 + elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': + return parse_constant('-Infinity'), idx + 9 + else: + raise StopIteration + + return _scan_once + +make_scanner = c_make_scanner or py_make_scanner diff --git a/mitogen-0.2.7/ansible_mitogen/connection.py b/mitogen-0.2.7/ansible_mitogen/connection.py new file mode 100644 index 000000000..b5f28d3 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/connection.py @@ -0,0 +1,1021 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +from __future__ import unicode_literals + +import errno +import logging +import os +import pprint +import stat +import sys +import time + +import jinja2.runtime +import ansible.constants as C +import ansible.errors +import ansible.plugins.connection +import ansible.utils.shlex + +import mitogen.core +import mitogen.fork +import mitogen.unix +import mitogen.utils + +import ansible_mitogen.parsing +import ansible_mitogen.process +import ansible_mitogen.services +import ansible_mitogen.target +import ansible_mitogen.transport_config + + +LOG = logging.getLogger(__name__) + + +def get_remote_name(spec): + """ + Return the value to use for the "remote_name" parameter. + """ + if spec.mitogen_mask_remote_name(): + return 'ansible' + return None + + +def optional_int(value): + """ + Convert `value` to an integer if it is not :data:`None`, otherwise return + :data:`None`. + """ + try: + return int(value) + except (TypeError, ValueError): + return None + + +def convert_bool(obj): + if isinstance(obj, bool): + return obj + if str(obj).lower() in ('no', 'false', '0'): + return False + if str(obj).lower() not in ('yes', 'true', '1'): + raise ansible.errors.AnsibleConnectionFailure( + 'expected yes/no/true/false/0/1, got %r' % (obj,) + ) + return True + + +def default(value, default): + """ + Return `default` is `value` is :data:`None`, otherwise return `value`. + """ + if value is None: + return default + return value + + +def _connect_local(spec): + """ + Return ContextService arguments for a local connection. + """ + return { + 'method': 'local', + 'kwargs': { + 'python_path': spec.python_path(), + } + } + + +def _connect_ssh(spec): + """ + Return ContextService arguments for an SSH connection. + """ + if C.HOST_KEY_CHECKING: + check_host_keys = 'enforce' + else: + check_host_keys = 'ignore' + + # #334: tilde-expand private_key_file to avoid implementation difference + # between Python and OpenSSH. + private_key_file = spec.private_key_file() + if private_key_file is not None: + private_key_file = os.path.expanduser(private_key_file) + + return { + 'method': 'ssh', + 'kwargs': { + 'check_host_keys': check_host_keys, + 'hostname': spec.remote_addr(), + 'username': spec.remote_user(), + 'compression': convert_bool( + default(spec.mitogen_ssh_compression(), True) + ), + 'password': spec.password(), + 'port': spec.port(), + 'python_path': spec.python_path(), + 'identity_file': private_key_file, + 'identities_only': False, + 'ssh_path': spec.ssh_executable(), + 'connect_timeout': spec.ansible_ssh_timeout(), + 'ssh_args': spec.ssh_args(), + 'ssh_debug_level': spec.mitogen_ssh_debug_level(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_docker(spec): + """ + Return ContextService arguments for a Docker connection. + """ + return { + 'method': 'docker', + 'kwargs': { + 'username': spec.remote_user(), + 'container': spec.remote_addr(), + 'python_path': spec.python_path(), + 'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_kubectl(spec): + """ + Return ContextService arguments for a Kubernetes connection. + """ + return { + 'method': 'kubectl', + 'kwargs': { + 'pod': spec.remote_addr(), + 'python_path': spec.python_path(), + 'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(), + 'kubectl_path': spec.mitogen_kubectl_path(), + 'kubectl_args': spec.extra_args(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_jail(spec): + """ + Return ContextService arguments for a FreeBSD jail connection. + """ + return { + 'method': 'jail', + 'kwargs': { + 'username': spec.remote_user(), + 'container': spec.remote_addr(), + 'python_path': spec.python_path(), + 'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_lxc(spec): + """ + Return ContextService arguments for an LXC Classic container connection. + """ + return { + 'method': 'lxc', + 'kwargs': { + 'container': spec.remote_addr(), + 'python_path': spec.python_path(), + 'lxc_attach_path': spec.mitogen_lxc_attach_path(), + 'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_lxd(spec): + """ + Return ContextService arguments for an LXD container connection. + """ + return { + 'method': 'lxd', + 'kwargs': { + 'container': spec.remote_addr(), + 'python_path': spec.python_path(), + 'lxc_path': spec.mitogen_lxc_path(), + 'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_machinectl(spec): + """ + Return ContextService arguments for a machinectl connection. + """ + return _connect_setns(spec, kind='machinectl') + + +def _connect_setns(spec, kind=None): + """ + Return ContextService arguments for a mitogen_setns connection. + """ + return { + 'method': 'setns', + 'kwargs': { + 'container': spec.remote_addr(), + 'username': spec.remote_user(), + 'python_path': spec.python_path(), + 'kind': kind or spec.mitogen_kind(), + 'docker_path': spec.mitogen_docker_path(), + 'lxc_path': spec.mitogen_lxc_path(), + 'lxc_info_path': spec.mitogen_lxc_info_path(), + 'machinectl_path': spec.mitogen_machinectl_path(), + } + } + + +def _connect_su(spec): + """ + Return ContextService arguments for su as a become method. + """ + return { + 'method': 'su', + 'enable_lru': True, + 'kwargs': { + 'username': spec.become_user(), + 'password': spec.become_pass(), + 'python_path': spec.python_path(), + 'su_path': spec.become_exe(), + 'connect_timeout': spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_sudo(spec): + """ + Return ContextService arguments for sudo as a become method. + """ + return { + 'method': 'sudo', + 'enable_lru': True, + 'kwargs': { + 'username': spec.become_user(), + 'password': spec.become_pass(), + 'python_path': spec.python_path(), + 'sudo_path': spec.become_exe(), + 'connect_timeout': spec.timeout(), + 'sudo_args': spec.sudo_args(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_doas(spec): + """ + Return ContextService arguments for doas as a become method. + """ + return { + 'method': 'doas', + 'enable_lru': True, + 'kwargs': { + 'username': spec.become_user(), + 'password': spec.become_pass(), + 'python_path': spec.python_path(), + 'doas_path': spec.become_exe(), + 'connect_timeout': spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_mitogen_su(spec): + """ + Return ContextService arguments for su as a first class connection. + """ + return { + 'method': 'su', + 'kwargs': { + 'username': spec.remote_user(), + 'password': spec.password(), + 'python_path': spec.python_path(), + 'su_path': spec.become_exe(), + 'connect_timeout': spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_mitogen_sudo(spec): + """ + Return ContextService arguments for sudo as a first class connection. + """ + return { + 'method': 'sudo', + 'kwargs': { + 'username': spec.remote_user(), + 'password': spec.password(), + 'python_path': spec.python_path(), + 'sudo_path': spec.become_exe(), + 'connect_timeout': spec.timeout(), + 'sudo_args': spec.sudo_args(), + 'remote_name': get_remote_name(spec), + } + } + + +def _connect_mitogen_doas(spec): + """ + Return ContextService arguments for doas as a first class connection. + """ + return { + 'method': 'doas', + 'kwargs': { + 'username': spec.remote_user(), + 'password': spec.password(), + 'python_path': spec.python_path(), + 'doas_path': spec.become_exe(), + 'connect_timeout': spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + + +#: Mapping of connection method names to functions invoked as `func(spec)` +#: generating ContextService keyword arguments matching a connection +#: specification. +CONNECTION_METHOD = { + 'docker': _connect_docker, + 'kubectl': _connect_kubectl, + 'jail': _connect_jail, + 'local': _connect_local, + 'lxc': _connect_lxc, + 'lxd': _connect_lxd, + 'machinectl': _connect_machinectl, + 'setns': _connect_setns, + 'ssh': _connect_ssh, + 'smart': _connect_ssh, # issue #548. + 'su': _connect_su, + 'sudo': _connect_sudo, + 'doas': _connect_doas, + 'mitogen_su': _connect_mitogen_su, + 'mitogen_sudo': _connect_mitogen_sudo, + 'mitogen_doas': _connect_mitogen_doas, +} + + +class Broker(mitogen.master.Broker): + """ + WorkerProcess maintains at most 2 file descriptors, therefore does not need + the exuberant syscall expense of EpollPoller, so override it and restore + the poll() poller. + """ + poller_class = mitogen.core.Poller + + +class CallChain(mitogen.parent.CallChain): + """ + Extend :class:`mitogen.parent.CallChain` to additionally cause the + associated :class:`Connection` to be reset if a ChannelError occurs. + + This only catches failures that occur while a call is pending, it is a + stop-gap until a more general method is available to notice connection in + every situation. + """ + call_aborted_msg = ( + 'Mitogen was disconnected from the remote environment while a call ' + 'was in-progress. If you feel this is in error, please file a bug. ' + 'Original error was: %s' + ) + + def __init__(self, connection, context, pipelined=False): + super(CallChain, self).__init__(context, pipelined) + #: The connection to reset on CallError. + self._connection = connection + + def _rethrow(self, recv): + try: + return recv.get().unpickle() + except mitogen.core.ChannelError as e: + self._connection.reset() + raise ansible.errors.AnsibleConnectionFailure( + self.call_aborted_msg % (e,) + ) + + def call(self, func, *args, **kwargs): + """ + Like :meth:`mitogen.parent.CallChain.call`, but log timings. + """ + t0 = time.time() + try: + recv = self.call_async(func, *args, **kwargs) + return self._rethrow(recv) + finally: + LOG.debug('Call took %d ms: %r', 1000 * (time.time() - t0), + mitogen.parent.CallSpec(func, args, kwargs)) + + +class Connection(ansible.plugins.connection.ConnectionBase): + #: mitogen.master.Broker for this worker. + broker = None + + #: mitogen.master.Router for this worker. + router = None + + #: mitogen.parent.Context representing the parent Context, which is + #: presently always the connection multiplexer process. + parent = None + + #: mitogen.parent.Context for the target account on the target, possibly + #: reached via become. + context = None + + #: Context for the login account on the target. This is always the login + #: account, even when become=True. + login_context = None + + #: Only sudo, su, and doas are supported for now. + become_methods = ['sudo', 'su', 'doas'] + + #: Dict containing init_child() return value as recorded at startup by + #: ContextService. Contains: + #: + #: fork_context: Context connected to the fork parent : process in the + #: target account. + #: home_dir: Target context's home directory. + #: good_temp_dir: A writeable directory where new temporary directories + #: can be created. + init_child_result = None + + #: A :class:`mitogen.parent.CallChain` for calls made to the target + #: account, to ensure subsequent calls fail with the original exception if + #: pipelined directory creation or file transfer fails. + chain = None + + # + # Note: any of the attributes below may be :data:`None` if the connection + # plugin was constructed directly by a non-cooperative action, such as in + # the case of the synchronize module. + # + + #: Set to the host name as it appears in inventory by on_action_run(). + inventory_hostname = None + + #: Set to task_vars by on_action_run(). + _task_vars = None + + #: Set to 'hostvars' by on_action_run() + host_vars = None + + #: Set by on_action_run() + delegate_to_hostname = None + + #: Set to '_loader.get_basedir()' by on_action_run(). Used by mitogen_local + #: to change the working directory to that of the current playbook, + #: matching vanilla Ansible behaviour. + loader_basedir = None + + def __init__(self, play_context, new_stdin, **kwargs): + assert ansible_mitogen.process.MuxProcess.unix_listener_path, ( + 'Mitogen connection types may only be instantiated ' + 'while the "mitogen" strategy is active.' + ) + super(Connection, self).__init__(play_context, new_stdin) + + def __del__(self): + """ + Ansible cannot be trusted to always call close() e.g. the synchronize + action constructs a local connection like this. So provide a destructor + in the hopes of catching these cases. + """ + # https://github.com/dw/mitogen/issues/140 + self.close() + + def on_action_run(self, task_vars, delegate_to_hostname, loader_basedir): + """ + Invoked by ActionModuleMixin to indicate a new task is about to start + executing. We use the opportunity to grab relevant bits from the + task-specific data. + + :param dict task_vars: + Task variable dictionary. + :param str delegate_to_hostname: + :data:`None`, or the template-expanded inventory hostname this task + is being delegated to. A similar variable exists on PlayContext + when ``delegate_to:`` is active, however it is unexpanded. + :param str loader_basedir: + Loader base directory; see :attr:`loader_basedir`. + """ + self.inventory_hostname = task_vars['inventory_hostname'] + self._task_vars = task_vars + self.host_vars = task_vars['hostvars'] + self.delegate_to_hostname = delegate_to_hostname + self.loader_basedir = loader_basedir + self._mitogen_reset(mode='put') + + def get_task_var(self, key, default=None): + """ + Fetch the value of a task variable related to connection configuration, + or, if delegate_to is active, fetch the same variable via HostVars for + the delegated-to machine. + + When running with delegate_to, Ansible tasks have variables associated + with the original machine, not the delegated-to machine, therefore it + does not make sense to extract connection-related configuration for the + delegated-to machine from them. + """ + if self._task_vars: + if self.delegate_to_hostname is None: + if key in self._task_vars: + return self._task_vars[key] + else: + delegated_vars = self._task_vars['ansible_delegated_vars'] + if self.delegate_to_hostname in delegated_vars: + task_vars = delegated_vars[self.delegate_to_hostname] + if key in task_vars: + return task_vars[key] + + return default + + @property + def homedir(self): + self._connect() + return self.init_child_result['home_dir'] + + @property + def connected(self): + return self.context is not None + + def _spec_from_via(self, proxied_inventory_name, via_spec): + """ + Produce a dict connection specifiction given a string `via_spec`, of + the form `[[become_method:]become_user@]inventory_hostname`. + """ + become_user, _, inventory_name = via_spec.rpartition('@') + become_method, _, become_user = become_user.rpartition(':') + + # must use __contains__ to avoid a TypeError for a missing host on + # Ansible 2.3. + if self.host_vars is None or inventory_name not in self.host_vars: + raise ansible.errors.AnsibleConnectionFailure( + self.unknown_via_msg % ( + via_spec, + proxied_inventory_name, + ) + ) + + via_vars = self.host_vars[inventory_name] + return ansible_mitogen.transport_config.MitogenViaSpec( + inventory_name=inventory_name, + play_context=self._play_context, + host_vars=dict(via_vars), # TODO: make it lazy + become_method=become_method or None, + become_user=become_user or None, + ) + + unknown_via_msg = 'mitogen_via=%s of %s specifies an unknown hostname' + via_cycle_msg = 'mitogen_via=%s of %s creates a cycle (%s)' + + def _stack_from_spec(self, spec, stack=(), seen_names=()): + """ + Return a tuple of ContextService parameter dictionaries corresponding + to the connection described by `spec`, and any connection referenced by + its `mitogen_via` or `become` fields. Each element is a dict of the + form:: + + { + # Optional. If present and `True`, this hop is elegible for + # interpreter recycling. + "enable_lru": True, + # mitogen.master.Router method name. + "method": "ssh", + # mitogen.master.Router method kwargs. + "kwargs": { + "hostname": "..." + } + } + + :param ansible_mitogen.transport_config.Spec spec: + Connection specification. + :param tuple stack: + Stack elements from parent call (used for recursion). + :param tuple seen_names: + Inventory hostnames from parent call (cycle detection). + :returns: + Tuple `(stack, seen_names)`. + """ + if spec.inventory_name() in seen_names: + raise ansible.errors.AnsibleConnectionFailure( + self.via_cycle_msg % ( + spec.mitogen_via(), + spec.inventory_name(), + ' -> '.join(reversed( + seen_names + (spec.inventory_name(),) + )), + ) + ) + + if spec.mitogen_via(): + stack = self._stack_from_spec( + self._spec_from_via(spec.inventory_name(), spec.mitogen_via()), + stack=stack, + seen_names=seen_names + (spec.inventory_name(),), + ) + + stack += (CONNECTION_METHOD[spec.transport()](spec),) + if spec.become() and ((spec.become_user() != spec.remote_user()) or + C.BECOME_ALLOW_SAME_USER): + stack += (CONNECTION_METHOD[spec.become_method()](spec),) + + return stack + + def _connect_broker(self): + """ + Establish a reference to the Broker, Router and parent context used for + connections. + """ + if not self.broker: + self.broker = mitogen.master.Broker() + self.router, self.parent = mitogen.unix.connect( + path=ansible_mitogen.process.MuxProcess.unix_listener_path, + broker=self.broker, + ) + + def _build_stack(self): + """ + Construct a list of dictionaries representing the connection + configuration between the controller and the target. This is + additionally used by the integration tests "mitogen_get_stack" action + to fetch the would-be connection configuration. + """ + return self._stack_from_spec( + ansible_mitogen.transport_config.PlayContextSpec( + connection=self, + play_context=self._play_context, + transport=self.transport, + inventory_name=self.inventory_hostname, + ) + ) + + def _connect_stack(self, stack): + """ + Pass `stack` to ContextService, requesting a copy of the context object + representing the last tuple element. If no connection exists yet, + ContextService will recursively establish it before returning it or + throwing an error. + + See :meth:`ansible_mitogen.services.ContextService.get` docstring for + description of the returned dictionary. + """ + try: + dct = self.parent.call_service( + service_name='ansible_mitogen.services.ContextService', + method_name='get', + stack=mitogen.utils.cast(list(stack)), + ) + except mitogen.core.CallError: + LOG.warning('Connection failed; stack configuration was:\n%s', + pprint.pformat(stack)) + raise + + if dct['msg']: + if dct['method_name'] in self.become_methods: + raise ansible.errors.AnsibleModuleError(dct['msg']) + raise ansible.errors.AnsibleConnectionFailure(dct['msg']) + + self.context = dct['context'] + self.chain = CallChain(self, self.context, pipelined=True) + if self._play_context.become: + self.login_context = dct['via'] + else: + self.login_context = self.context + + self.init_child_result = dct['init_child_result'] + + def get_good_temp_dir(self): + """ + Return the 'good temporary directory' as discovered by + :func:`ansible_mitogen.target.init_child` immediately after + ContextService constructed the target context. + """ + self._connect() + return self.init_child_result['good_temp_dir'] + + def _connect(self): + """ + Establish a connection to the master process's UNIX listener socket, + constructing a mitogen.master.Router to communicate with the master, + and a mitogen.parent.Context to represent it. + + Depending on the original transport we should emulate, trigger one of + the _connect_*() service calls defined above to cause the master + process to establish the real connection on our behalf, or return a + reference to the existing one. + """ + if self.connected: + return + + self._connect_broker() + stack = self._build_stack() + self._connect_stack(stack) + + def _mitogen_reset(self, mode): + """ + Forget everything we know about the connected context. This function + cannot be called _reset() since that name is used as a public API by + Ansible 2.4 wait_for_connection plug-in. + + :param str mode: + Name of ContextService method to use to discard the context, either + 'put' or 'reset'. + """ + if not self.context: + return + + self.chain.reset() + self.parent.call_service( + service_name='ansible_mitogen.services.ContextService', + method_name=mode, + context=self.context + ) + + self.context = None + self.login_context = None + self.init_child_result = None + self.chain = None + + def _shutdown_broker(self): + """ + Shutdown the broker thread during :meth:`close` or :meth:`reset`. + """ + if self.broker: + self.broker.shutdown() + self.broker.join() + self.broker = None + self.router = None + + # #420: Ansible executes "meta" actions in the top-level process, + # meaning "reset_connection" will cause :class:`mitogen.core.Latch` + # FDs to be cached and erroneously shared by children on subsequent + # WorkerProcess forks. To handle that, call on_fork() to ensure any + # shared state is discarded. + # #490: only attempt to clean up when it's known that some + # resources exist to cleanup, otherwise later __del__ double-call + # to close() due to GC at random moment may obliterate an unrelated + # Connection's resources. + mitogen.fork.on_fork() + + def close(self): + """ + Arrange for the mitogen.master.Router running in the worker to + gracefully shut down, and wait for shutdown to complete. Safe to call + multiple times. + """ + self._mitogen_reset(mode='put') + self._shutdown_broker() + + def _reset_find_task_vars(self): + """ + Monsterous hack: since "meta: reset_connection" does not run from an + action, we cannot capture task variables via :meth:`on_action_run`. + Instead walk the parent frames searching for the `all_vars` local from + StrategyBase._execute_meta(). If this fails, just leave task_vars + unset, likely causing a subtly wrong configuration to be selected. + """ + frame = sys._getframe() + while frame and not self._task_vars: + self._task_vars = frame.f_locals.get('all_vars') + frame = frame.f_back + + reset_compat_msg = ( + 'Mitogen only supports "reset_connection" on Ansible 2.5.6 or later' + ) + + def reset(self): + """ + Explicitly terminate the connection to the remote host. This discards + any local state we hold for the connection, returns the Connection to + the 'disconnected' state, and informs ContextService the connection is + bad somehow, and should be shut down and discarded. + """ + if self._task_vars is None: + self._reset_find_task_vars() + + if self._play_context.remote_addr is None: + # <2.5.6 incorrectly populate PlayContext for reset_connection + # https://github.com/ansible/ansible/issues/27520 + raise ansible.errors.AnsibleConnectionFailure( + self.reset_compat_msg + ) + + self._connect() + self._mitogen_reset(mode='reset') + self._shutdown_broker() + + # Compatibility with Ansible 2.4 wait_for_connection plug-in. + _reset = reset + + def get_chain(self, use_login=False, use_fork=False): + """ + Return the :class:`mitogen.parent.CallChain` to use for executing + function calls. + + :param bool use_login: + If :data:`True`, always return the chain for the login account + rather than any active become user. + :param bool use_fork: + If :data:`True`, return the chain for the fork parent. + :returns mitogen.parent.CallChain: + """ + self._connect() + if use_login: + return self.login_context.default_call_chain + # See FORK_SUPPORTED comments in target.py. + if use_fork and self.init_child_result['fork_context'] is not None: + return self.init_child_result['fork_context'].default_call_chain + return self.chain + + def spawn_isolated_child(self): + """ + Fork or launch a new child off the target context. + + :returns: + mitogen.core.Context of the new child. + """ + return self.get_chain(use_fork=True).call( + ansible_mitogen.target.spawn_isolated_child + ) + + def get_extra_args(self): + """ + Overridden by connections/mitogen_kubectl.py to a list of additional + arguments for the command. + """ + # TODO: maybe use this for SSH too. + return [] + + def get_default_cwd(self): + """ + Overridden by connections/mitogen_local.py to emulate behaviour of CWD + being fixed to that of ActionBase._loader.get_basedir(). + """ + return None + + def get_default_env(self): + """ + Overridden by connections/mitogen_local.py to emulate behaviour of + WorkProcess environment inherited from WorkerProcess. + """ + return None + + def exec_command(self, cmd, in_data='', sudoable=True, mitogen_chdir=None): + """ + Implement exec_command() by calling the corresponding + ansible_mitogen.target function in the target. + + :param str cmd: + Shell command to execute. + :param bytes in_data: + Data to supply on ``stdin`` of the process. + :returns: + (return code, stdout bytes, stderr bytes) + """ + emulate_tty = (not in_data and sudoable) + rc, stdout, stderr = self.get_chain().call( + ansible_mitogen.target.exec_command, + cmd=mitogen.utils.cast(cmd), + in_data=mitogen.utils.cast(in_data), + chdir=mitogen_chdir or self.get_default_cwd(), + emulate_tty=emulate_tty, + ) + + stderr += b'Shared connection to %s closed.%s' % ( + self._play_context.remote_addr.encode(), + (b'\r\n' if emulate_tty else b'\n'), + ) + return rc, stdout, stderr + + def fetch_file(self, in_path, out_path): + """ + Implement fetch_file() by calling the corresponding + ansible_mitogen.target function in the target. + + :param str in_path: + Remote filesystem path to read. + :param str out_path: + Local filesystem path to write. + """ + output = self.get_chain().call( + ansible_mitogen.target.read_path, + mitogen.utils.cast(in_path), + ) + ansible_mitogen.target.write_path(out_path, output) + + def put_data(self, out_path, data, mode=None, utimes=None): + """ + Implement put_file() by caling the corresponding ansible_mitogen.target + function in the target, transferring small files inline. This is + pipelined and will return immediately; failed transfers are reported as + exceptions in subsequent functon calls. + + :param str out_path: + Remote filesystem path to write. + :param byte data: + File contents to put. + """ + self.get_chain().call_no_reply( + ansible_mitogen.target.write_path, + mitogen.utils.cast(out_path), + mitogen.core.Blob(data), + mode=mode, + utimes=utimes, + ) + + #: Maximum size of a small file before switching to streaming + #: transfer. This should really be the same as + #: mitogen.services.FileService.IO_SIZE, however the message format has + #: slightly more overhead, so just randomly subtract 4KiB. + SMALL_FILE_LIMIT = mitogen.core.CHUNK_SIZE - 4096 + + def _throw_io_error(self, e, path): + if e.args[0] == errno.ENOENT: + s = 'file or module does not exist: ' + path + raise ansible.errors.AnsibleFileNotFound(s) + + def put_file(self, in_path, out_path): + """ + Implement put_file() by streamily transferring the file via + FileService. + + :param str in_path: + Local filesystem path to read. + :param str out_path: + Remote filesystem path to write. + """ + try: + st = os.stat(in_path) + except OSError as e: + self._throw_io_error(e, in_path) + raise + + if not stat.S_ISREG(st.st_mode): + raise IOError('%r is not a regular file.' % (in_path,)) + + # If the file is sufficiently small, just ship it in the argument list + # rather than introducing an extra RTT for the child to request it from + # FileService. + if st.st_size <= self.SMALL_FILE_LIMIT: + try: + fp = open(in_path, 'rb') + try: + s = fp.read(self.SMALL_FILE_LIMIT + 1) + finally: + fp.close() + except OSError: + self._throw_io_error(e, in_path) + raise + + # Ensure did not grow during read. + if len(s) == st.st_size: + return self.put_data(out_path, s, mode=st.st_mode, + utimes=(st.st_atime, st.st_mtime)) + + self._connect() + self.parent.call_service( + service_name='mitogen.service.FileService', + method_name='register', + path=mitogen.utils.cast(in_path) + ) + + # For now this must remain synchronous, as the action plug-in may have + # passed us a temporary file to transfer. A future FileService could + # maintain an LRU list of open file descriptors to keep the temporary + # file alive, but that requires more work. + self.get_chain().call( + ansible_mitogen.target.transfer_file, + context=self.parent, + in_path=in_path, + out_path=out_path + ) diff --git a/mitogen-0.2.7/ansible_mitogen/connection.pyc b/mitogen-0.2.7/ansible_mitogen/connection.pyc new file mode 100644 index 0000000000000000000000000000000000000000..729ffe7f40ba184cf03ebfbb41ffe245518aa2a0 GIT binary patch literal 32926 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 logging.WARNING: + record.levelno = logging.DEBUG + + if _process_pid == os.getpid(): + process_name = _process_name + else: + process_name = '?' + + s = '[%-4s %d] %s' % (process_name, os.getpid(), self.format(record)) + if record.levelno >= logging.ERROR: + display.error(s, wrap_text=False) + elif record.levelno >= logging.WARNING: + display.warning(s, formatted=True) + else: + self.normal_method(s) + + +def setup(): + """ + Install handlers for Mitogen loggers to redirect them into the Ansible + display framework. Ansible installs its own logging framework handlers when + C.DEFAULT_LOG_PATH is set, therefore disable propagation for our handlers. + """ + l_mitogen = logging.getLogger('mitogen') + l_mitogen_io = logging.getLogger('mitogen.io') + l_ansible_mitogen = logging.getLogger('ansible_mitogen') + + for logger in l_mitogen, l_mitogen_io, l_ansible_mitogen: + logger.handlers = [Handler(display.vvv)] + logger.propagate = False + + if display.verbosity > 2: + l_ansible_mitogen.setLevel(logging.DEBUG) + l_mitogen.setLevel(logging.DEBUG) + else: + # Mitogen copies the active log level into new children, allowing them + # to filter tiny messages before they hit the network, and therefore + # before they wake the IO loop. Explicitly setting INFO saves ~4% + # running against just the local machine. + l_mitogen.setLevel(logging.ERROR) + l_ansible_mitogen.setLevel(logging.ERROR) + + if display.verbosity > 3: + l_mitogen_io.setLevel(logging.DEBUG) diff --git a/mitogen-0.2.7/ansible_mitogen/logging.pyc b/mitogen-0.2.7/ansible_mitogen/logging.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf32ee6d64cc2430ea2c12a6efb84c5e8ebe7542 GIT binary patch literal 3133 zcmcIm>uwvz75-+Ir0yZDI0kwNvNd9aC}Jwe1<8*#jbkg00fi1$Hr%K+7Ax&&wbXKl znHfq}K>-EScj(LX0s0C>&>ww*7RU<}-x*Su{B{ITn9Dh5X1?>C?`-_z#_A9CfBt-c z`Cl8r7heGG9{>uVP^f{i!k&WSJVG5|FOt0ob&S1uF>Yb6y%={mKD#Z`81)jy%h+3n zdeQtAExm%{RoK5mDT&!ay^8S~_ST^E8ZJQ7M&XY|gnWZ%=5L^A<8Q!T7exm;!l;Ks zg<^?|7r=LWJrv7gaf=SS{S^QeU)5Itd>25z?@T@QI;+MLWBoAfEb*n4o7DN))xx8& za6sS1?;&xy17HT^fL9nrJUfpG({4Wk;64Dbnj}fGt9_CuO+MC%H%VR?+a$Fqld*O# zFSScfD}OW)(_z2GD{cVDCe|Ei=Q28aMl{ZM0F>HKs=}{dx0zH$xG~-7(3kh}pL+;k z>&T4t)~L?Uw#v$H6?#&ev-|tG(_7=pn^HITKNx&C_;f38T(w{8Y`(fxo3gB$axgjb zJqFYM#=Sn`8qY+#6AOM{=S@*-dr6}hz?X#ULjW3NPXVEgk{rd*EvBi6QAW5>!km_D zb&&4%Nkg}@fbzN1$v5G`?>PbS(AaVA-%s|ZK5@D!5`UxZ{uMTfrwQwa>yUytba+S2Gf!;S1|5++V0AS$4WUOqr9{pXzjtNh9#& z8&pqi5)7bd4(059lkfd)}osDH*lepYCgGU7w+;uvFnQ65_~V%ZO9R~xJtqZE13DAROdY9 zp@0dbl&|xJw$@Wh{@_@|f4X=7W0%}3UMBaPr2o|Blg#T^{stL}{^o*drS`e^HeKZc z;onZvr)eOeKGAh!ggC>ePjo z`pSj$xbojJ{;!3?n`&Kk)o)Zc+Ego1C%UUP)gAt#K5KVguT;(~^TajF#-^CooYEl6 z3UiQUQW+sF*8D{`PW$w20>@Wey+lT@OL#?4KIc;iU?=XVm6ceSmVD%yIk7P~PCdmiD{C!{cSEnPJJJ~>#z_rJ#r zZZEUyj$5#Qr}t<@Vy{Xiu=1|Yn?x!lEUg8TOP4U8M{G;Pe@Ow}hyWJou4K#ochL`? zG|uOBog9U(LSY1?BA9!|$aZ)wjNS}BRx}CJ%dE-wGZ9dkg#vTtA z+esBpaY^M}VopQkswuB*uP$(Uq#KF%aPVmRs|U|_p0NV6Zy!ASI;mXZw11yRTYYG( z=Arq1t&@p0lf2A*Wg2;cncB;cp=G5^z9Tc|vNEVl_>{TXu`9NR z{N*gGOlW%9Pwp&A5wdkvL$9lRM6{gt(}^a6zoZ;=x~i+X(H-@c+K9TUr{3)JR4?kP zps}I;52t==b(W>{5Ew-$uS@&jz*sGr5y4$#CiM642{6v9Cd&faY^zV?gL7Nt^eujg zhV%uW!JLFClPEGaG>sNj4oc0KA$_0ye1ao4|6P#3*rp*^*()+7F6bz5zyp4w=2 MTAg@h?HBL;A2&L;p8x;= literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/mixins.py b/mitogen-0.2.7/ansible_mitogen/mixins.py new file mode 100644 index 000000000..890467f --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/mixins.py @@ -0,0 +1,432 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import logging +import os +import pwd +import random +import traceback + +try: + from shlex import quote as shlex_quote +except ImportError: + from pipes import quote as shlex_quote + +from ansible.module_utils._text import to_bytes +from ansible.parsing.utils.jsonify import jsonify + +import ansible +import ansible.constants +import ansible.plugins +import ansible.plugins.action + +import mitogen.core +import mitogen.select +import mitogen.utils + +import ansible_mitogen.connection +import ansible_mitogen.planner +import ansible_mitogen.target +from ansible.module_utils._text import to_text + + +LOG = logging.getLogger(__name__) + + +class ActionModuleMixin(ansible.plugins.action.ActionBase): + """ + The Mitogen-patched PluginLoader dynamically mixes this into every action + class that Ansible attempts to load. It exists to override all the + assumptions built into the base action class that should really belong in + some middle layer, or at least in the connection layer. + + Functionality is defined here for: + + * Capturing the final set of task variables and giving Connection a chance + to update its idea of the correct execution environment, before any + attempt is made to call a Connection method. While it's not expected for + the interpreter to change on a per-task basis, Ansible permits this, and + so it must be supported. + + * Overriding lots of methods that try to call out to shell for mundane + reasons, such as copying files around, changing file permissions, + creating temporary directories and suchlike. + + * Short-circuiting any use of Ansiballz or related code for executing a + module remotely using shell commands and SSH. + + * Short-circuiting most of the logic in dealing with the fact that Ansible + always runs become: tasks across at least the SSH user account and the + destination user account, and handling the security permission issues + that crop up due to this. Mitogen always runs a task completely within + the target user account, so it's not a problem for us. + """ + def __init__(self, task, connection, *args, **kwargs): + """ + Verify the received connection is really a Mitogen connection. If not, + transmute this instance back into the original unadorned base class. + + This allows running the Mitogen strategy in mixed-target playbooks, + where some targets use SSH while others use WinRM or some fancier UNIX + connection plug-in. That's because when the Mitogen strategy is active, + ActionModuleMixin is unconditionally mixed into any action module that + is instantiated, and there is no direct way for the monkey-patch to + know what kind of connection will be used upfront. + """ + super(ActionModuleMixin, self).__init__(task, connection, *args, **kwargs) + if not isinstance(connection, ansible_mitogen.connection.Connection): + _, self.__class__ = type(self).__bases__ + + def run(self, tmp=None, task_vars=None): + """ + Override run() to notify Connection of task-specific data, so it has a + chance to know e.g. the Python interpreter in use. + """ + self._connection.on_action_run( + task_vars=task_vars, + delegate_to_hostname=self._task.delegate_to, + loader_basedir=self._loader.get_basedir(), + ) + return super(ActionModuleMixin, self).run(tmp, task_vars) + + COMMAND_RESULT = { + 'rc': 0, + 'stdout': '', + 'stdout_lines': [], + 'stderr': '' + } + + def fake_shell(self, func, stdout=False): + """ + Execute a function and decorate its return value in the style of + _low_level_execute_command(). This produces a return value that looks + like some shell command was run, when really func() was implemented + entirely in Python. + + If the function raises :py:class:`mitogen.core.CallError`, this will be + translated into a failed shell command with a non-zero exit status. + + :param func: + Function invoked as `func()`. + :returns: + See :py:attr:`COMMAND_RESULT`. + """ + dct = self.COMMAND_RESULT.copy() + try: + rc = func() + if stdout: + dct['stdout'] = repr(rc) + except mitogen.core.CallError: + LOG.exception('While emulating a shell command') + dct['rc'] = 1 + dct['stderr'] = traceback.format_exc() + + return dct + + def _remote_file_exists(self, path): + """ + Determine if `path` exists by directly invoking os.path.exists() in the + target user account. + """ + LOG.debug('_remote_file_exists(%r)', path) + return self._connection.get_chain().call( + ansible_mitogen.target.file_exists, + mitogen.utils.cast(path) + ) + + def _configure_module(self, module_name, module_args, task_vars=None): + """ + Mitogen does not use the Ansiballz framework. This call should never + happen when ActionMixin is active, so crash if it does. + """ + assert False, "_configure_module() should never be called." + + def _is_pipelining_enabled(self, module_style, wrap_async=False): + """ + Mitogen does not use SSH pipelining. This call should never happen when + ActionMixin is active, so crash if it does. + """ + assert False, "_is_pipelining_enabled() should never be called." + + def _generate_tmp_path(self): + return os.path.join( + self._connection.get_good_temp_dir(), + 'ansible_mitogen_action_%016x' % ( + random.getrandbits(8*8), + ) + ) + + def _generate_tmp_path(self): + return os.path.join( + self._connection.get_good_temp_dir(), + 'ansible_mitogen_action_%016x' % ( + random.getrandbits(8*8), + ) + ) + + def _make_tmp_path(self, remote_user=None): + """ + Create a temporary subdirectory as a child of the temporary directory + managed by the remote interpreter. + """ + LOG.debug('_make_tmp_path(remote_user=%r)', remote_user) + path = self._generate_tmp_path() + LOG.debug('Temporary directory: %r', path) + self._connection.get_chain().call_no_reply(os.mkdir, path) + self._connection._shell.tmpdir = path + return path + + def _remove_tmp_path(self, tmp_path): + """ + Replace the base implementation's invocation of rm -rf, replacing it + with a pipelined call to :func:`ansible_mitogen.target.prune_tree`. + """ + LOG.debug('_remove_tmp_path(%r)', tmp_path) + if tmp_path is None and ansible.__version__ > '2.6': + tmp_path = self._connection._shell.tmpdir # 06f73ad578d + if tmp_path is not None: + self._connection.get_chain().call_no_reply( + ansible_mitogen.target.prune_tree, + tmp_path, + ) + self._connection._shell.tmpdir = None + + def _transfer_data(self, remote_path, data): + """ + Used by the base _execute_module(), and in <2.4 also by the template + action module, and probably others. + """ + if isinstance(data, dict): + data = jsonify(data) + if not isinstance(data, bytes): + data = to_bytes(data, errors='surrogate_or_strict') + + LOG.debug('_transfer_data(%r, %s ..%d bytes)', + remote_path, type(data), len(data)) + self._connection.put_data(remote_path, data) + return remote_path + + #: Actions listed here cause :func:`_fixup_perms2` to avoid a needless + #: roundtrip, as they modify file modes separately afterwards. This is due + #: to the method prototype having a default of `execute=True`. + FIXUP_PERMS_RED_HERRING = set(['copy']) + + def _fixup_perms2(self, remote_paths, remote_user=None, execute=True): + """ + Mitogen always executes ActionBase helper methods in the context of the + target user account, so it is never necessary to modify permissions + except to ensure the execute bit is set if requested. + """ + LOG.debug('_fixup_perms2(%r, remote_user=%r, execute=%r)', + remote_paths, remote_user, execute) + if execute and self._task.action not in self.FIXUP_PERMS_RED_HERRING: + return self._remote_chmod(remote_paths, mode='u+x') + return self.COMMAND_RESULT.copy() + + def _remote_chmod(self, paths, mode, sudoable=False): + """ + Issue an asynchronous set_file_mode() call for every path in `paths`, + then format the resulting return value list with fake_shell(). + """ + LOG.debug('_remote_chmod(%r, mode=%r, sudoable=%r)', + paths, mode, sudoable) + return self.fake_shell(lambda: mitogen.select.Select.all( + self._connection.get_chain().call_async( + ansible_mitogen.target.set_file_mode, path, mode + ) + for path in paths + )) + + def _remote_chown(self, paths, user, sudoable=False): + """ + Issue an asynchronous os.chown() call for every path in `paths`, then + format the resulting return value list with fake_shell(). + """ + LOG.debug('_remote_chown(%r, user=%r, sudoable=%r)', + paths, user, sudoable) + ent = self._connection.get_chain().call(pwd.getpwnam, user) + return self.fake_shell(lambda: mitogen.select.Select.all( + self._connection.get_chain().call_async( + os.chown, path, ent.pw_uid, ent.pw_gid + ) + for path in paths + )) + + def _remote_expand_user(self, path, sudoable=True): + """ + Replace the base implementation's attempt to emulate + os.path.expanduser() with an actual call to os.path.expanduser(). + + :param bool sudoable: + If :data:`True`, indicate unqualified tilde ("~" with no username) + should be evaluated in the context of the login account, not any + become_user. + """ + LOG.debug('_remote_expand_user(%r, sudoable=%r)', path, sudoable) + if not path.startswith('~'): + # /home/foo -> /home/foo + return path + if sudoable or not self._play_context.become: + if path == '~': + # ~ -> /home/dmw + return self._connection.homedir + if path.startswith('~/'): + # ~/.ansible -> /home/dmw/.ansible + return os.path.join(self._connection.homedir, path[2:]) + # ~root/.ansible -> /root/.ansible + return self._connection.get_chain(use_login=(not sudoable)).call( + os.path.expanduser, + mitogen.utils.cast(path), + ) + + def get_task_timeout_secs(self): + """ + Return the task "async:" value, portable across 2.4-2.5. + """ + try: + return self._task.async_val + except AttributeError: + return getattr(self._task, 'async') + + def _temp_file_gibberish(self, module_args, wrap_async): + # Ansible>2.5 module_utils reuses the action's temporary directory if + # one exists. Older versions error if this key is present. + if ansible.__version__ > '2.5': + if wrap_async: + # Sharing is not possible with async tasks, as in that case, + # the directory must outlive the action plug-in. + module_args['_ansible_tmpdir'] = None + else: + module_args['_ansible_tmpdir'] = self._connection._shell.tmpdir + + # If _ansible_tmpdir is unset, Ansible>2.6 module_utils will use + # _ansible_remote_tmp as the location to create the module's temporary + # directory. Older versions error if this key is present. + if ansible.__version__ > '2.6': + module_args['_ansible_remote_tmp'] = ( + self._connection.get_good_temp_dir() + ) + + def _execute_module(self, module_name=None, module_args=None, tmp=None, + task_vars=None, persist_files=False, + delete_remote_tmp=True, wrap_async=False): + """ + Collect up a module's execution environment then use it to invoke + target.run_module() or helpers.run_module_async() in the target + context. + """ + if module_name is None: + module_name = self._task.action + if module_args is None: + module_args = self._task.args + if task_vars is None: + task_vars = {} + + self._update_module_args(module_name, module_args, task_vars) + env = {} + self._compute_environment_string(env) + self._temp_file_gibberish(module_args, wrap_async) + + self._connection._connect() + result = ansible_mitogen.planner.invoke( + ansible_mitogen.planner.Invocation( + action=self, + connection=self._connection, + module_name=mitogen.core.to_text(module_name), + module_args=mitogen.utils.cast(module_args), + task_vars=task_vars, + templar=self._templar, + env=mitogen.utils.cast(env), + wrap_async=wrap_async, + timeout_secs=self.get_task_timeout_secs(), + ) + ) + + if ansible.__version__ < '2.5' and delete_remote_tmp and \ + getattr(self._connection._shell, 'tmpdir', None) is not None: + # Built-in actions expected tmpdir to be cleaned up automatically + # on _execute_module(). + self._remove_tmp_path(self._connection._shell.tmpdir) + + return result + + def _postprocess_response(self, result): + """ + Apply fixups mimicking ActionBase._execute_module(); this is copied + verbatim from action/__init__.py, the guts of _parse_returned_data are + garbage and should be removed or reimplemented once tests exist. + + :param dict result: + Dictionary with format:: + + { + "rc": int, + "stdout": "stdout data", + "stderr": "stderr data" + } + """ + data = self._parse_returned_data(result) + + # Cutpasted from the base implementation. + if 'stdout' in data and 'stdout_lines' not in data: + data['stdout_lines'] = (data['stdout'] or u'').splitlines() + if 'stderr' in data and 'stderr_lines' not in data: + data['stderr_lines'] = (data['stderr'] or u'').splitlines() + + return data + + def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, + executable=None, + encoding_errors='surrogate_then_replace', + chdir=None): + """ + Override the base implementation by simply calling + target.exec_command() in the target context. + """ + LOG.debug('_low_level_execute_command(%r, in_data=%r, exe=%r, dir=%r)', + cmd, type(in_data), executable, chdir) + if executable is None: # executable defaults to False + executable = self._play_context.executable + if executable: + cmd = executable + ' -c ' + shlex_quote(cmd) + + rc, stdout, stderr = self._connection.exec_command( + cmd=cmd, + in_data=in_data, + sudoable=sudoable, + mitogen_chdir=chdir, + ) + stdout_text = to_text(stdout, errors=encoding_errors) + + return { + 'rc': rc, + 'stdout': stdout_text, + 'stdout_lines': stdout_text.splitlines(), + 'stderr': stderr, + } diff --git a/mitogen-0.2.7/ansible_mitogen/mixins.pyc b/mitogen-0.2.7/ansible_mitogen/mixins.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9be208e08b9447ee91b1717e6da3d5ff6d98337b GIT binary patch literal 16658 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 O)j9g>zn$!^bpHq5Ko^_< literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/module_finder.py b/mitogen-0.2.7/ansible_mitogen/module_finder.py new file mode 100644 index 000000000..633e3ca --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/module_finder.py @@ -0,0 +1,157 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +from __future__ import unicode_literals + +import collections +import imp +import os + +import mitogen.master + + +PREFIX = 'ansible.module_utils.' + + +Module = collections.namedtuple('Module', 'name path kind parent') + + +def get_fullname(module): + """ + Reconstruct a Module's canonical path by recursing through its parents. + """ + bits = [str(module.name)] + while module.parent: + bits.append(str(module.parent.name)) + module = module.parent + return '.'.join(reversed(bits)) + + +def get_code(module): + """ + Compile and return a Module's code object. + """ + fp = open(module.path) + try: + return compile(fp.read(), str(module.name), 'exec') + finally: + fp.close() + + +def is_pkg(module): + """ + Return :data:`True` if a Module represents a package. + """ + return module.kind == imp.PKG_DIRECTORY + + +def find(name, path=(), parent=None): + """ + Return a Module instance describing the first matching module found on the + search path. + + :param str name: + Module name. + :param list path: + List of directory names to search for the module. + :param Module parent: + Optional module parent. + """ + assert isinstance(path, tuple) + head, _, tail = name.partition('.') + try: + tup = imp.find_module(head, list(path)) + except ImportError: + return parent + + fp, modpath, (suffix, mode, kind) = tup + if fp: + fp.close() + + if parent and modpath == parent.path: + # 'from timeout import timeout', where 'timeout' is a function but also + # the name of the module being imported. + return None + + if kind == imp.PKG_DIRECTORY: + modpath = os.path.join(modpath, '__init__.py') + + module = Module(head, modpath, kind, parent) + # TODO: this code is entirely wrong on Python 3.x, but works well enough + # for Ansible. We need a new find_child() that only looks in the package + # directory, never falling back to the parent search path. + if tail and kind == imp.PKG_DIRECTORY: + return find_relative(module, tail, path) + return module + + +def find_relative(parent, name, path=()): + if parent.kind == imp.PKG_DIRECTORY: + path = (os.path.dirname(parent.path),) + path + return find(name, path, parent=parent) + + +def scan_fromlist(code): + for level, modname_s, fromlist in mitogen.master.scan_code_imports(code): + for name in fromlist: + yield level, '%s.%s' % (modname_s, name) + if not fromlist: + yield level, modname_s + + +def scan(module_name, module_path, search_path): + module = Module(module_name, module_path, imp.PY_SOURCE, None) + stack = [module] + seen = set() + + while stack: + module = stack.pop(0) + for level, fromname in scan_fromlist(get_code(module)): + if not fromname.startswith(PREFIX): + continue + + imported = find(fromname[len(PREFIX):], search_path) + if imported is None or imported in seen: + continue + + seen.add(imported) + stack.append(imported) + parent = imported.parent + while parent: + fullname = get_fullname(parent) + module = Module(fullname, parent.path, parent.kind, None) + if module not in seen: + seen.add(module) + stack.append(module) + parent = parent.parent + + return sorted( + (PREFIX + get_fullname(module), module.path, is_pkg(module)) + for module in seen + ) diff --git a/mitogen-0.2.7/ansible_mitogen/module_finder.pyc b/mitogen-0.2.7/ansible_mitogen/module_finder.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dde494887e279a9d49efc9ff30433795f9ddef6f GIT binary patch literal 4370 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, limit the time an asynchronous job may run for. + self.timeout_secs = timeout_secs + #: Initially ``None``, but set by :func:`invoke`. The path on the + #: master to the module's implementation file. + self.module_path = None + #: Initially ``None``, but set by :func:`invoke`. The raw source or + #: binary contents of the module. + self.module_source = None + + def __repr__(self): + return 'Invocation(module_name=%s)' % (self.module_name,) + + +class Planner(object): + """ + A Planner receives a module name and the contents of its implementation + file, indicates whether or not it understands how to run the module, and + exports a method to run the module. + """ + def __init__(self, invocation): + self._inv = invocation + + def detect(self): + """ + Return true if the supplied `invocation` matches the module type + implemented by this planner. + """ + raise NotImplementedError() + + def should_fork(self): + """ + Asynchronous tasks must always be forked. + """ + return self._inv.wrap_async + + def get_push_files(self): + """ + Return a list of files that should be propagated to the target context + using PushFileService. The default implementation pushes nothing. + """ + return [] + + def get_module_deps(self): + """ + Return a list of the Python module names imported by the module. + """ + return [] + + def get_kwargs(self, **kwargs): + """ + If :meth:`detect` returned :data:`True`, plan for the module's + execution, including granting access to or delivering any files to it + that are known to be absent, and finally return a dict:: + + { + # Name of the class from runners.py that implements the + # target-side execution of this module type. + "runner_name": "...", + + # Remaining keys are passed to the constructor of the class + # named by `runner_name`. + } + """ + new = dict((mitogen.core.UnicodeType(k), kwargs[k]) + for k in kwargs) + new.setdefault('good_temp_dir', + self._inv.connection.get_good_temp_dir()) + new.setdefault('cwd', self._inv.connection.get_default_cwd()) + new.setdefault('extra_env', self._inv.connection.get_default_env()) + new.setdefault('emulate_tty', True) + new.setdefault('service_context', self._inv.connection.parent) + return new + + def __repr__(self): + return '%s()' % (type(self).__name__,) + + +class BinaryPlanner(Planner): + """ + Binary modules take their arguments and will return data to Ansible in the + same way as want JSON modules. + """ + runner_name = 'BinaryRunner' + + def detect(self): + return module_common._is_binary(self._inv.module_source) + + def get_push_files(self): + return [mitogen.core.to_text(self._inv.module_path)] + + def get_kwargs(self, **kwargs): + return super(BinaryPlanner, self).get_kwargs( + runner_name=self.runner_name, + module=self._inv.module_name, + path=self._inv.module_path, + json_args=json.dumps(self._inv.module_args), + env=self._inv.env, + **kwargs + ) + + +class ScriptPlanner(BinaryPlanner): + """ + Common functionality for script module planners -- handle interpreter + detection and rewrite. + """ + def _rewrite_interpreter(self, path): + """ + Given the original interpreter binary extracted from the script's + interpreter line, look up the associated `ansible_*_interpreter` + variable, render it and return it. + + :param str path: + Absolute UNIX path to original interpreter. + + :returns: + Shell fragment prefix used to execute the script via "/bin/sh -c". + While `ansible_*_interpreter` documentation suggests shell isn't + involved here, the vanilla implementation uses it and that use is + exploited in common playbooks. + """ + key = u'ansible_%s_interpreter' % os.path.basename(path).strip() + try: + template = self._inv.task_vars[key] + except KeyError: + return path + + return mitogen.utils.cast(self._inv.templar.template(template)) + + def _get_interpreter(self): + path, arg = ansible_mitogen.parsing.parse_hashbang( + self._inv.module_source + ) + if path is None: + raise ansible.errors.AnsibleError(NO_INTERPRETER_MSG % ( + self._inv.module_name, + )) + + fragment = self._rewrite_interpreter(path) + if arg: + fragment += ' ' + arg + + return fragment, path.startswith('python') + + def get_kwargs(self, **kwargs): + interpreter_fragment, is_python = self._get_interpreter() + return super(ScriptPlanner, self).get_kwargs( + interpreter_fragment=interpreter_fragment, + is_python=is_python, + **kwargs + ) + + +class JsonArgsPlanner(ScriptPlanner): + """ + Script that has its interpreter directive and the task arguments + substituted into its source as a JSON string. + """ + runner_name = 'JsonArgsRunner' + + def detect(self): + return module_common.REPLACER_JSONARGS in self._inv.module_source + + +class WantJsonPlanner(ScriptPlanner): + """ + If a module has the string WANT_JSON in it anywhere, Ansible treats it as a + non-native module that accepts a filename as its only command line + parameter. The filename is for a temporary file containing a JSON string + containing the module's parameters. The module needs to open the file, read + and parse the parameters, operate on the data, and print its return data as + a JSON encoded dictionary to stdout before exiting. + + These types of modules are self-contained entities. As of Ansible 2.1, + Ansible only modifies them to change a shebang line if present. + """ + runner_name = 'WantJsonRunner' + + def detect(self): + return b'WANT_JSON' in self._inv.module_source + + +class NewStylePlanner(ScriptPlanner): + """ + The Ansiballz framework differs from module replacer in that it uses real + Python imports of things in ansible/module_utils instead of merely + preprocessing the module. + """ + runner_name = 'NewStyleRunner' + marker = b'from ansible.module_utils.' + + def detect(self): + return self.marker in self._inv.module_source + + def _get_interpreter(self): + return None, None + + def get_push_files(self): + return super(NewStylePlanner, self).get_push_files() + [ + mitogen.core.to_text(path) + for fullname, path, is_pkg in self.get_module_map()['custom'] + ] + + def get_module_deps(self): + return self.get_module_map()['builtin'] + + #: Module names appearing in this set always require forking, usually due + #: to some terminal leakage that cannot be worked around in any sane + #: manner. + ALWAYS_FORK_MODULES = frozenset([ + 'dnf', # issue #280; py-dnf/hawkey need therapy + 'firewalld', # issue #570: ansible module_utils caches dbus conn + ]) + + def should_fork(self): + """ + In addition to asynchronous tasks, new-style modules should be forked + if: + + * the user specifies mitogen_task_isolation=fork, or + * the new-style module has a custom module search path, or + * the module is known to leak like a sieve. + """ + return ( + super(NewStylePlanner, self).should_fork() or + (self._inv.task_vars.get('mitogen_task_isolation') == 'fork') or + (self._inv.module_name in self.ALWAYS_FORK_MODULES) or + (len(self.get_module_map()['custom']) > 0) + ) + + def get_search_path(self): + return tuple( + path + for path in ansible_mitogen.loaders.module_utils_loader._get_paths( + subdirs=False + ) + if os.path.isdir(path) + ) + + _module_map = None + + def get_module_map(self): + if self._module_map is None: + self._module_map = self._inv.connection.parent.call_service( + service_name='ansible_mitogen.services.ModuleDepService', + method_name='scan', + + module_name='ansible_module_%s' % (self._inv.module_name,), + module_path=self._inv.module_path, + search_path=self.get_search_path(), + builtin_path=module_common._MODULE_UTILS_PATH, + context=self._inv.connection.context, + ) + return self._module_map + + def get_kwargs(self): + return super(NewStylePlanner, self).get_kwargs( + module_map=self.get_module_map(), + ) + + +class ReplacerPlanner(NewStylePlanner): + """ + The Module Replacer framework is the original framework implementing + new-style modules. It is essentially a preprocessor (like the C + Preprocessor for those familiar with that programming language). It does + straight substitutions of specific substring patterns in the module file. + There are two types of substitutions. + + * Replacements that only happen in the module file. These are public + replacement strings that modules can utilize to get helpful boilerplate + or access to arguments. + + "from ansible.module_utils.MOD_LIB_NAME import *" is replaced with the + contents of the ansible/module_utils/MOD_LIB_NAME.py. These should only + be used with new-style Python modules. + + "#<>" is equivalent to + "from ansible.module_utils.basic import *" and should also only apply to + new-style Python modules. + + "# POWERSHELL_COMMON" substitutes the contents of + "ansible/module_utils/powershell.ps1". It should only be used with + new-style Powershell modules. + """ + runner_name = 'ReplacerRunner' + + def detect(self): + return module_common.REPLACER in self._inv.module_source + + +class OldStylePlanner(ScriptPlanner): + runner_name = 'OldStyleRunner' + + def detect(self): + # Everything else. + return True + + +_planners = [ + BinaryPlanner, + # ReplacerPlanner, + NewStylePlanner, + JsonArgsPlanner, + WantJsonPlanner, + OldStylePlanner, +] + + +def get_module_data(name): + path = ansible_mitogen.loaders.module_loader.find_plugin(name, '') + if path is None: + raise ansible.errors.AnsibleError(NO_MODULE_MSG % (name,)) + + with open(path, 'rb') as fp: + source = fp.read() + return mitogen.core.to_text(path), source + + +def _propagate_deps(invocation, planner, context): + invocation.connection.parent.call_service( + service_name='mitogen.service.PushFileService', + method_name='propagate_paths_and_modules', + context=context, + paths=planner.get_push_files(), + modules=planner.get_module_deps(), + ) + + +def _invoke_async_task(invocation, planner): + job_id = '%016x' % random.randint(0, 2**64) + context = invocation.connection.spawn_isolated_child() + _propagate_deps(invocation, planner, context) + + with mitogen.core.Receiver(context.router) as started_recv: + call_recv = context.call_async( + ansible_mitogen.target.run_module_async, + job_id=job_id, + timeout_secs=invocation.timeout_secs, + started_sender=started_recv.to_sender(), + kwargs=planner.get_kwargs(), + ) + + # Wait for run_module_async() to crash, or for AsyncRunner to indicate + # the job file has been written. + for msg in mitogen.select.Select([started_recv, call_recv]): + if msg.receiver is call_recv: + # It can only be an exception. + raise msg.unpickle() + break + + return { + 'stdout': json.dumps({ + # modules/utilities/logic/async_wrapper.py::_run_module(). + 'changed': True, + 'started': 1, + 'finished': 0, + 'ansible_job_id': job_id, + }) + } + + +def _invoke_isolated_task(invocation, planner): + context = invocation.connection.spawn_isolated_child() + _propagate_deps(invocation, planner, context) + try: + return context.call( + ansible_mitogen.target.run_module, + kwargs=planner.get_kwargs(), + ) + finally: + context.shutdown() + + +def _get_planner(invocation): + for klass in _planners: + planner = klass(invocation) + if planner.detect(): + LOG.debug('%r accepted %r (filename %r)', planner, + invocation.module_name, invocation.module_path) + return planner + LOG.debug('%r rejected %r', planner, invocation.module_name) + raise ansible.errors.AnsibleError(NO_METHOD_MSG + repr(invocation)) + + +def invoke(invocation): + """ + Find a Planner subclass corresnding to `invocation` and use it to invoke + the module. + + :param Invocation invocation: + :returns: + Module return dict. + :raises ansible.errors.AnsibleError: + Unrecognized/unsupported module type. + """ + (invocation.module_path, + invocation.module_source) = get_module_data(invocation.module_name) + planner = _get_planner(invocation) + + if invocation.wrap_async: + response = _invoke_async_task(invocation, planner) + elif planner.should_fork(): + response = _invoke_isolated_task(invocation, planner) + else: + _propagate_deps(invocation, planner, invocation.connection.context) + response = invocation.connection.get_chain().call( + ansible_mitogen.target.run_module, + kwargs=planner.get_kwargs(), + ) + + return invocation.action._postprocess_response(response) diff --git a/mitogen-0.2.7/ansible_mitogen/planner.pyc b/mitogen-0.2.7/ansible_mitogen/planner.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11bb62eb5dd0d21b90723ce574675bfe1967efe5 GIT binary patch literal 19326 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 G^nU;nL (3,): + viewkeys = dict.keys +elif sys.version_info > (2, 7): + viewkeys = dict.viewkeys +else: + viewkeys = lambda dct: set(dct) + + +def dict_diff(old, new): + """ + Return a dict representing the differences between the dicts `old` and + `new`. Deleted keys appear as a key with the value :data:`None`, added and + changed keys appear as a key with the new value. + """ + old_keys = viewkeys(old) + new_keys = viewkeys(dict(new)) + out = {} + for key in new_keys - old_keys: + out[key] = new[key] + for key in old_keys - new_keys: + out[key] = None + for key in old_keys & new_keys: + if old[key] != new[key]: + out[key] = new[key] + return out + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'local' + + def get_default_cwd(self): + # https://github.com/ansible/ansible/issues/14489 + return self.loader_basedir + + def get_default_env(self): + """ + Vanilla Ansible local commands execute with an environment inherited + from WorkerProcess, we must emulate that. + """ + return dict_diff( + old=ansible_mitogen.process.MuxProcess.original_env, + new=os.environ, + ) diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxc.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxc.py new file mode 100644 index 000000000..696c9ab --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxc.py @@ -0,0 +1,44 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import os.path +import sys + +try: + import ansible_mitogen +except ImportError: + base_dir = os.path.dirname(__file__) + sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..'))) + del base_dir + +import ansible_mitogen.connection + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'lxc' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxd.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxd.py new file mode 100644 index 000000000..95e692a --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxd.py @@ -0,0 +1,44 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import os.path +import sys + +try: + import ansible_mitogen +except ImportError: + base_dir = os.path.dirname(__file__) + sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..'))) + del base_dir + +import ansible_mitogen.connection + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'lxd' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_machinectl.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_machinectl.py new file mode 100644 index 000000000..0f5a0d2 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_machinectl.py @@ -0,0 +1,44 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import os.path +import sys + +try: + import ansible_mitogen.connection +except ImportError: + base_dir = os.path.dirname(__file__) + sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..'))) + del base_dir + +import ansible_mitogen.connection + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'machinectl' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_setns.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_setns.py new file mode 100644 index 000000000..20c6f13 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_setns.py @@ -0,0 +1,44 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import os.path +import sys + +try: + import ansible_mitogen.connection +except ImportError: + base_dir = os.path.dirname(__file__) + sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..'))) + del base_dir + +import ansible_mitogen.connection + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'setns' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.py new file mode 100644 index 000000000..df0e87c --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.py @@ -0,0 +1,65 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import os.path +import sys + +DOCUMENTATION = """ + author: David Wilson + connection: mitogen_ssh + short_description: Connect over SSH via Mitogen + description: + - This connects using an OpenSSH client controlled by the Mitogen for + Ansible extension. It accepts every option the vanilla ssh plugin + accepts. + version_added: "2.5" + options: +""" + +import ansible.plugins.connection.ssh + +try: + import ansible_mitogen.connection +except ImportError: + base_dir = os.path.dirname(__file__) + sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..'))) + del base_dir + +import ansible_mitogen.connection + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'ssh' + vanilla_class = ansible.plugins.connection.ssh.Connection + + @staticmethod + def _create_control_path(*args, **kwargs): + """Forward _create_control_path() to the implementation in ssh.py.""" + # https://github.com/dw/mitogen/issues/342 + return Connection.vanilla_class._create_control_path(*args, **kwargs) diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.pyc b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bbfc09c03076ba68b3ff484de141e00c5a60446 GIT binary patch literal 1727 zcmcgsO>P@E7=0YcvaQIDYj;r;NI_(!E<7p%6h)0Rb(*Az7qJ1!McQeB0X3vGoQz0; zq~r?7&RzEmy--ikRnO216dz|4r9jsq1cChc{l51&oqrDcf7O5gQlMQu0O#j`{}X@& zBts3%88QY&=LB_vtP}4N)Lmp<$PTidjirZd7wetiA0K$C$V(M+Z3@X?2g>~W53kLK#=Ugm9Y26urE>>Fd*ShxB z@W=A@d}@O*x>)+usPH8E7S}%H-ac@y?glg6wV$A#A(Ln2zUzgf%YI(l&+RV5x{A!^LWhrW{OdyBgY+WnKr)wTcwMory ztb3P@^QXq^X|1^WH7Mh?H7UOcToi>`1kcq>&@o%UOwSw;O9^ph><+%}amFJPGJfB;+tm(K< z4o(Vr`4u^u~4 zhQw1l@69qSoyzku!4gNaJ|!&#n#KA?X=@+Zetz}rhgUByub*CDTwR9GwwRx_|b}r&)>xFaHMd`$*hzF@1QP#3|qa$5JB?%>$+C_nB z=fy!z#CSD>iAC`;Q1Ar$0V7>x?0!1`r zgcH){R8#JB(d8~W>F9dgd9U*fCtISNE!`6CqmOPndgqnrZM?pr0R1i9b(!*@>vF&Y z4ANP8Gd)zU8maLY%I7aYd5V1o#V|;3rAIjb6~WnI-J z3Q@L<^$HVhWOZd}VXR{sW8JRwNgI-;rKKPtZX9x{jks_DR3bQVfJ4FsgS6VzlXTs+9eLLh<{Cbb zJ14$@A3(E?p&-s!mNlMvJM-R~4S$S>-^}-qm)Jc$02dd4{R$ujQUL?x0*Qd&{{SXH z68c&I(?ik&g~;}h^}Aru1^b`_Yz5K~+kk^)2pWQVjK3bB`n-#?TilR_3=>5=Ie-}< z8F!NoIi@kP39=(($4Glf_dvI?6(Tu7+V|B{WOqO(SOrK2_yTN!_sJO(UrhG_XjVEE z0NezCTH4aoj-<{vW#!&l#zax?$gK!$-Fz|iV*?(`X(-fs}JY&?-`k0ehsSBS>b;W9T!dKd`aCTw# zV)078d_DI*>%vmy98>s9Nn}cuQf1qP{i)L3`v<;L?5E#6+!bV-^cvRWJl%IyO+J_@ z+j8yDZhUju`jalaaliNUUzX(^d)nTV=MUKYQvgR|Bu2*v;$b)uXTeB}!u!EbnMLV8 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/process.py b/mitogen-0.2.7/ansible_mitogen/process.py new file mode 100644 index 000000000..e4e61e8 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/process.py @@ -0,0 +1,358 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import atexit +import errno +import logging +import os +import signal +import socket +import sys +import time + +try: + import faulthandler +except ImportError: + faulthandler = None + +import mitogen +import mitogen.core +import mitogen.debug +import mitogen.master +import mitogen.parent +import mitogen.service +import mitogen.unix +import mitogen.utils + +import ansible +import ansible.constants as C +import ansible_mitogen.logging +import ansible_mitogen.services + +from mitogen.core import b +import ansible_mitogen.affinity + + +LOG = logging.getLogger(__name__) + +ANSIBLE_PKG_OVERRIDE = ( + u"__version__ = %r\n" + u"__author__ = %r\n" +) + + +def clean_shutdown(sock): + """ + Shut the write end of `sock`, causing `recv` in the worker process to wake + up with a 0-byte read and initiate mux process exit, then wait for a 0-byte + read from the read end, which will occur after the the child closes the + descriptor on exit. + + This is done using :mod:`atexit` since Ansible lacks any more sensible hook + to run code during exit, and unless some synchronization exists with + MuxProcess, debug logs may appear on the user's terminal *after* the prompt + has been printed. + """ + sock.shutdown(socket.SHUT_WR) + sock.recv(1) + + +def getenv_int(key, default=0): + """ + Get an integer-valued environment variable `key`, if it exists and parses + as an integer, otherwise return `default`. + """ + try: + return int(os.environ.get(key, str(default))) + except ValueError: + return default + + +def save_pid(name): + """ + When debugging and profiling, it is very annoying to poke through the + process list to discover the currently running Ansible and MuxProcess IDs, + especially when trying to catch an issue during early startup. So here, if + a magic environment variable set, stash them in hidden files in the CWD:: + + alias muxpid="cat .ansible-mux.pid" + alias anspid="cat .ansible-controller.pid" + + gdb -p $(muxpid) + perf top -p $(anspid) + """ + if os.environ.get('MITOGEN_SAVE_PIDS'): + with open('.ansible-%s.pid' % (name,), 'w') as fp: + fp.write(str(os.getpid())) + + +class MuxProcess(object): + """ + Implement a subprocess forked from the Ansible top-level, as a safe place + to contain the Mitogen IO multiplexer thread, keeping its use of the + logging package (and the logging package's heavy use of locks) far away + from the clutches of os.fork(), which is used continuously by the + multiprocessing package in the top-level process. + + The problem with running the multiplexer in that process is that should the + multiplexer thread be in the process of emitting a log entry (and holding + its lock) at the point of fork, in the child, the first attempt to log any + log entry using the same handler will deadlock the child, as in the memory + image the child received, the lock will always be marked held. + + See https://bugs.python.org/issue6721 for a thorough description of the + class of problems this worker is intended to avoid. + """ + + #: In the top-level process, this references one end of a socketpair(), + #: which the MuxProcess blocks reading from in order to determine when + #: the master process dies. Once the read returns, the MuxProcess will + #: begin shutting itself down. + worker_sock = None + + #: In the worker process, this references the other end of + #: :py:attr:`worker_sock`. + child_sock = None + + #: In the top-level process, this is the PID of the single MuxProcess + #: that was spawned. + worker_pid = None + + #: A copy of :data:`os.environ` at the time the multiplexer process was + #: started. It's used by mitogen_local.py to find changes made to the + #: top-level environment (e.g. vars plugins -- issue #297) that must be + #: applied to locally executed commands and modules. + original_env = None + + #: In both processes, this is the temporary UNIX socket used for + #: forked WorkerProcesses to contact the MuxProcess + unix_listener_path = None + + #: Singleton. + _instance = None + + @classmethod + def start(cls, _init_logging=True): + """ + Arrange for the subprocess to be started, if it is not already running. + + The parent process picks a UNIX socket path the child will use prior to + fork, creates a socketpair used essentially as a semaphore, then blocks + waiting for the child to indicate the UNIX socket is ready for use. + + :param bool _init_logging: + For testing, if :data:`False`, don't initialize logging. + """ + if cls.worker_sock is not None: + return + + if faulthandler is not None: + faulthandler.enable() + + mitogen.utils.setup_gil() + cls.unix_listener_path = mitogen.unix.make_socket_path() + cls.worker_sock, cls.child_sock = socket.socketpair() + atexit.register(lambda: clean_shutdown(cls.worker_sock)) + mitogen.core.set_cloexec(cls.worker_sock.fileno()) + mitogen.core.set_cloexec(cls.child_sock.fileno()) + + cls.profiling = os.environ.get('MITOGEN_PROFILING') is not None + if cls.profiling: + mitogen.core.enable_profiling() + if _init_logging: + ansible_mitogen.logging.setup() + + cls.original_env = dict(os.environ) + cls.child_pid = os.fork() + if cls.child_pid: + save_pid('controller') + ansible_mitogen.logging.set_process_name('top') + ansible_mitogen.affinity.policy.assign_controller() + cls.child_sock.close() + cls.child_sock = None + mitogen.core.io_op(cls.worker_sock.recv, 1) + else: + save_pid('mux') + ansible_mitogen.logging.set_process_name('mux') + ansible_mitogen.affinity.policy.assign_muxprocess() + cls.worker_sock.close() + cls.worker_sock = None + self = cls() + self.worker_main() + + def worker_main(self): + """ + The main function of for the mux process: setup the Mitogen broker + thread and ansible_mitogen services, then sleep waiting for the socket + connected to the parent to be closed (indicating the parent has died). + """ + self._setup_master() + self._setup_services() + + try: + # Let the parent know our listening socket is ready. + mitogen.core.io_op(self.child_sock.send, b('1')) + # Block until the socket is closed, which happens on parent exit. + mitogen.core.io_op(self.child_sock.recv, 1) + finally: + self.broker.shutdown() + self.broker.join() + + # Test frameworks living somewhere higher on the stack of the + # original parent process may try to catch sys.exit(), so do a C + # level exit instead. + os._exit(0) + + def _enable_router_debug(self): + if 'MITOGEN_ROUTER_DEBUG' in os.environ: + self.router.enable_debug() + + def _enable_stack_dumps(self): + secs = getenv_int('MITOGEN_DUMP_THREAD_STACKS', default=0) + if secs: + mitogen.debug.dump_to_logger(secs=secs) + + def _setup_simplejson(self, responder): + """ + We support serving simplejson for Python 2.4 targets on Ansible 2.3, at + least so the package's own CI Docker scripts can run without external + help, however newer versions of simplejson no longer support Python + 2.4. Therefore override any installed/loaded version with a + 2.4-compatible version we ship in the compat/ directory. + """ + responder.whitelist_prefix('simplejson') + + # issue #536: must be at end of sys.path, in case existing newer + # version is already loaded. + compat_path = os.path.join(os.path.dirname(__file__), 'compat') + sys.path.append(compat_path) + + for fullname, is_pkg, suffix in ( + (u'simplejson', True, '__init__.py'), + (u'simplejson.decoder', False, 'decoder.py'), + (u'simplejson.encoder', False, 'encoder.py'), + (u'simplejson.scanner', False, 'scanner.py'), + ): + path = os.path.join(compat_path, 'simplejson', suffix) + fp = open(path, 'rb') + try: + source = fp.read() + finally: + fp.close() + + responder.add_source_override( + fullname=fullname, + path=path, + source=source, + is_pkg=is_pkg, + ) + + def _setup_responder(self, responder): + """ + Configure :class:`mitogen.master.ModuleResponder` to only permit + certain packages, and to generate custom responses for certain modules. + """ + responder.whitelist_prefix('ansible') + responder.whitelist_prefix('ansible_mitogen') + self._setup_simplejson(responder) + + # Ansible 2.3 is compatible with Python 2.4 targets, however + # ansible/__init__.py is not. Instead, executor/module_common.py writes + # out a 2.4-compatible namespace package for unknown reasons. So we + # copy it here. + responder.add_source_override( + fullname='ansible', + path=ansible.__file__, + source=(ANSIBLE_PKG_OVERRIDE % ( + ansible.__version__, + ansible.__author__, + )).encode(), + is_pkg=True, + ) + + def _setup_master(self): + """ + Construct a Router, Broker, and mitogen.unix listener + """ + self.broker = mitogen.master.Broker(install_watcher=False) + self.router = mitogen.master.Router( + broker=self.broker, + max_message_size=4096 * 1048576, + ) + self._setup_responder(self.router.responder) + mitogen.core.listen(self.broker, 'shutdown', self.on_broker_shutdown) + mitogen.core.listen(self.broker, 'exit', self.on_broker_exit) + self.listener = mitogen.unix.Listener( + router=self.router, + path=self.unix_listener_path, + backlog=C.DEFAULT_FORKS, + ) + self._enable_router_debug() + self._enable_stack_dumps() + + def _setup_services(self): + """ + Construct a ContextService and a thread to service requests for it + arriving from worker processes. + """ + self.pool = mitogen.service.Pool( + router=self.router, + services=[ + mitogen.service.FileService(router=self.router), + mitogen.service.PushFileService(router=self.router), + ansible_mitogen.services.ContextService(self.router), + ansible_mitogen.services.ModuleDepService(self.router), + ], + size=getenv_int('MITOGEN_POOL_SIZE', default=32), + ) + LOG.debug('Service pool configured: size=%d', self.pool.size) + + def on_broker_shutdown(self): + """ + Respond to broker shutdown by beginning service pool shutdown. Do not + join on the pool yet, since that would block the broker thread which + then cannot clean up pending handlers, which is required for the + threads to exit gracefully. + """ + # In normal operation we presently kill the process because there is + # not yet any way to cancel connect(). + self.pool.stop(join=self.profiling) + + def on_broker_exit(self): + """ + Respond to the broker thread about to exit by sending SIGTERM to + ourself. In future this should gracefully join the pool, but TERM is + fine for now. + """ + if not self.profiling: + # In normal operation we presently kill the process because there is + # not yet any way to cancel connect(). When profiling, threads + # including the broker must shut down gracefully, otherwise pstats + # won't be written. + os.kill(os.getpid(), signal.SIGTERM) diff --git a/mitogen-0.2.7/ansible_mitogen/process.pyc b/mitogen-0.2.7/ansible_mitogen/process.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fde91e7a19a83a07614cb34f4323824eee62427 GIT binary patch literal 11763 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/runner.py b/mitogen-0.2.7/ansible_mitogen/runner.py new file mode 100644 index 000000000..30c36be --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/runner.py @@ -0,0 +1,928 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +These classes implement execution for each style of Ansible module. They are +instantiated in the target context by way of target.py::run_module(). + +Each class in here has a corresponding Planner class in planners.py that knows +how to build arguments for it, preseed related data, etc. +""" + +import atexit +import codecs +import imp +import os +import re +import shlex +import shutil +import sys +import tempfile +import traceback +import types + +import mitogen.core +import ansible_mitogen.target # TODO: circular import +from mitogen.core import b +from mitogen.core import bytes_partition +from mitogen.core import str_partition +from mitogen.core import str_rpartition +from mitogen.core import to_text + +try: + import ctypes +except ImportError: + # Python 2.4 + ctypes = None + +try: + import json +except ImportError: + # Python 2.4 + import simplejson as json + +try: + # Cannot use cStringIO as it does not support Unicode. + from StringIO import StringIO +except ImportError: + from io import StringIO + +try: + from shlex import quote as shlex_quote +except ImportError: + from pipes import quote as shlex_quote + +# Absolute imports for <2.5. +logging = __import__('logging') + + +# Prevent accidental import of an Ansible module from hanging on stdin read. +import ansible.module_utils.basic +ansible.module_utils.basic._ANSIBLE_ARGS = '{}' + +# For tasks that modify /etc/resolv.conf, non-Debian derivative glibcs cache +# resolv.conf at startup and never implicitly reload it. Cope with that via an +# explicit call to res_init() on each task invocation. BSD-alikes export it +# directly, Linux #defines it as "__res_init". +libc__res_init = None +if ctypes: + libc = ctypes.CDLL(None) + for symbol in 'res_init', '__res_init': + try: + libc__res_init = getattr(libc, symbol) + except AttributeError: + pass + +iteritems = getattr(dict, 'iteritems', dict.items) +LOG = logging.getLogger(__name__) + + +if mitogen.core.PY3: + shlex_split = shlex.split +else: + def shlex_split(s, comments=False): + return [mitogen.core.to_text(token) + for token in shlex.split(str(s), comments=comments)] + + +class EnvironmentFileWatcher(object): + """ + Usually Ansible edits to /etc/environment and ~/.pam_environment are + reflected in subsequent tasks if become:true or SSH multiplexing is + disabled, due to sudo and/or SSH reinvoking pam_env. Rather than emulate + existing semantics, do our best to ensure edits are always reflected. + + This can't perfectly replicate the existing behaviour, but it can safely + update and remove keys that appear to originate in `path`, and that do not + conflict with any existing environment key inherited from elsewhere. + + A more robust future approach may simply be to arrange for the persistent + interpreter to restart when a change is detected. + """ + def __init__(self, path): + self.path = os.path.expanduser(path) + #: Inode data at time of last check. + self._st = self._stat() + #: List of inherited keys appearing to originated from this file. + self._keys = [key for key, value in self._load() + if value == os.environ.get(key)] + LOG.debug('%r installed; existing keys: %r', self, self._keys) + + def __repr__(self): + return 'EnvironmentFileWatcher(%r)' % (self.path,) + + def _stat(self): + try: + return os.stat(self.path) + except OSError: + return None + + def _load(self): + try: + fp = codecs.open(self.path, 'r', encoding='utf-8') + try: + return list(self._parse(fp)) + finally: + fp.close() + except IOError: + return [] + + def _parse(self, fp): + """ + linux-pam-1.3.1/modules/pam_env/pam_env.c#L207 + """ + for line in fp: + # ' #export foo=some var ' -> ['#export', 'foo=some var '] + bits = shlex_split(line, comments=True) + if (not bits) or bits[0].startswith('#'): + continue + + if bits[0] == u'export': + bits.pop(0) + + key, sep, value = str_partition(u' '.join(bits), u'=') + if key and sep: + yield key, value + + def _on_file_changed(self): + LOG.debug('%r: file changed, reloading', self) + for key, value in self._load(): + if key in os.environ: + LOG.debug('%r: existing key %r=%r exists, not setting %r', + self, key, os.environ[key], value) + else: + LOG.debug('%r: setting key %r to %r', self, key, value) + self._keys.append(key) + os.environ[key] = value + + def _remove_existing(self): + """ + When a change is detected, remove keys that existed in the old file. + """ + for key in self._keys: + if key in os.environ: + LOG.debug('%r: removing old key %r', self, key) + del os.environ[key] + self._keys = [] + + def check(self): + """ + Compare the :func:`os.stat` for the pam_env style environmnt file + `path` with the previous result `old_st`, which may be :data:`None` if + the previous stat attempt failed. Reload its contents if the file has + changed or appeared since last attempt. + + :returns: + New :func:`os.stat` result. The new call to :func:`reload_env` should + pass it as the value of `old_st`. + """ + st = self._stat() + if self._st == st: + return + + self._st = st + self._remove_existing() + + if st is None: + LOG.debug('%r: file has disappeared', self) + else: + self._on_file_changed() + +_pam_env_watcher = EnvironmentFileWatcher('~/.pam_environment') +_etc_env_watcher = EnvironmentFileWatcher('/etc/environment') + + +def utf8(s): + """ + Coerce an object to bytes if it is Unicode. + """ + if isinstance(s, mitogen.core.UnicodeType): + s = s.encode('utf-8') + return s + + +def reopen_readonly(fp): + """ + Replace the file descriptor belonging to the file object `fp` with one + open on the same file (`fp.name`), but opened with :py:data:`os.O_RDONLY`. + This enables temporary files to be executed on Linux, which usually throws + ``ETXTBUSY`` if any writeable handle exists pointing to a file passed to + `execve()`. + """ + fd = os.open(fp.name, os.O_RDONLY) + os.dup2(fd, fp.fileno()) + os.close(fd) + + +class Runner(object): + """ + Ansible module runner. After instantiation (with kwargs supplied by the + corresponding Planner), `.run()` is invoked, upon which `setup()`, + `_run()`, and `revert()` are invoked, with the return value of `_run()` + returned by `run()`. + + Subclasses may override `_run`()` and extend `setup()` and `revert()`. + + :param str module: + Name of the module to execute, e.g. "shell" + :param mitogen.core.Context service_context: + Context to which we should direct FileService calls. For now, always + the connection multiplexer process on the controller. + :param str json_args: + Ansible module arguments. A mixture of user and internal keys created + by :meth:`ansible.plugins.action.ActionBase._execute_module`. + + This is passed as a string rather than a dict in order to mimic the + implicit bytes/str conversion behaviour of a 2.x controller running + against a 3.x target. + :param str good_temp_dir: + The writeable temporary directory for this user account reported by + :func:`ansible_mitogen.target.init_child` passed via the controller. + This is specified explicitly to remain compatible with Ansible<2.5, and + for forked tasks where init_child never runs. + :param dict env: + Additional environment variables to set during the run. Keys with + :data:`None` are unset if present. + :param str cwd: + If not :data:`None`, change to this directory before executing. + :param mitogen.core.ExternalContext econtext: + When `detach` is :data:`True`, a reference to the ExternalContext the + runner is executing in. + :param bool detach: + When :data:`True`, indicate the runner should detach the context from + its parent after setup has completed successfully. + """ + def __init__(self, module, service_context, json_args, good_temp_dir, + extra_env=None, cwd=None, env=None, econtext=None, + detach=False): + self.module = module + self.service_context = service_context + self.econtext = econtext + self.detach = detach + self.args = json.loads(mitogen.core.to_text(json_args)) + self.good_temp_dir = good_temp_dir + self.extra_env = extra_env + self.env = env + self.cwd = cwd + #: If not :data:`None`, :meth:`get_temp_dir` had to create a temporary + #: directory for this run, because we're in an asynchronous task, or + #: because the originating action did not create a directory. + self._temp_dir = None + + def get_temp_dir(self): + path = self.args.get('_ansible_tmpdir') + if path is not None: + return path + + if self._temp_dir is None: + self._temp_dir = tempfile.mkdtemp( + prefix='ansible_mitogen_runner_', + dir=self.good_temp_dir, + ) + + return self._temp_dir + + def revert_temp_dir(self): + if self._temp_dir is not None: + ansible_mitogen.target.prune_tree(self._temp_dir) + self._temp_dir = None + + def setup(self): + """ + Prepare for running a module, including fetching necessary dependencies + from the parent, as :meth:`run` may detach prior to beginning + execution. The base implementation simply prepares the environment. + """ + self._setup_cwd() + self._setup_environ() + + def _setup_cwd(self): + """ + For situations like sudo to a non-privileged account, CWD could be + $HOME of the old account, which could have mode go=, which means it is + impossible to restore the old directory, so don't even try. + """ + if self.cwd: + os.chdir(self.cwd) + + def _setup_environ(self): + """ + Apply changes from /etc/environment files before creating a + TemporaryEnvironment to snapshot environment state prior to module run. + """ + _pam_env_watcher.check() + _etc_env_watcher.check() + env = dict(self.extra_env or {}) + if self.env: + env.update(self.env) + self._env = TemporaryEnvironment(env) + + def revert(self): + """ + Revert any changes made to the process after running a module. The base + implementation simply restores the original environment. + """ + self._env.revert() + self.revert_temp_dir() + + def _run(self): + """ + The _run() method is expected to return a dictionary in the form of + ActionBase._low_level_execute_command() output, i.e. having:: + + { + "rc": int, + "stdout": "stdout data", + "stderr": "stderr data" + } + """ + raise NotImplementedError() + + def run(self): + """ + Set up the process environment in preparation for running an Ansible + module. This monkey-patches the Ansible libraries in various places to + prevent it from trying to kill the process on completion, and to + prevent it from reading sys.stdin. + + :returns: + Module result dictionary. + """ + self.setup() + if self.detach: + self.econtext.detach() + + try: + return self._run() + finally: + self.revert() + + +class AtExitWrapper(object): + """ + issue #397, #454: Newer Ansibles use :func:`atexit.register` to trigger + tmpdir cleanup when AnsibleModule.tmpdir is responsible for creating its + own temporary directory, however with Mitogen processes are preserved + across tasks, meaning cleanup must happen earlier. + + Patch :func:`atexit.register`, catching :func:`shutil.rmtree` calls so they + can be executed on task completion, rather than on process shutdown. + """ + # Wrapped in a dict to avoid instance method decoration. + original = { + 'register': atexit.register + } + + def __init__(self): + assert atexit.register == self.original['register'], \ + "AtExitWrapper installed twice." + atexit.register = self._atexit__register + self.deferred = [] + + def revert(self): + """ + Restore the original :func:`atexit.register`. + """ + assert atexit.register == self._atexit__register, \ + "AtExitWrapper not installed." + atexit.register = self.original['register'] + + def run_callbacks(self): + while self.deferred: + func, targs, kwargs = self.deferred.pop() + try: + func(*targs, **kwargs) + except Exception: + LOG.exception('While running atexit callbacks') + + def _atexit__register(self, func, *targs, **kwargs): + """ + Intercept :func:`atexit.register` calls, diverting any to + :func:`shutil.rmtree` into a private list. + """ + if func == shutil.rmtree: + self.deferred.append((func, targs, kwargs)) + return + + self.original['register'](func, *targs, **kwargs) + + +class ModuleUtilsImporter(object): + """ + :param list module_utils: + List of `(fullname, path, is_pkg)` tuples. + """ + def __init__(self, context, module_utils): + self._context = context + self._by_fullname = dict( + (fullname, (path, is_pkg)) + for fullname, path, is_pkg in module_utils + ) + self._loaded = set() + sys.meta_path.insert(0, self) + + def revert(self): + sys.meta_path.remove(self) + for fullname in self._loaded: + sys.modules.pop(fullname, None) + + def find_module(self, fullname, path=None): + if fullname in self._by_fullname: + return self + + def load_module(self, fullname): + path, is_pkg = self._by_fullname[fullname] + source = ansible_mitogen.target.get_small_file(self._context, path) + code = compile(source, path, 'exec', 0, 1) + mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) + mod.__file__ = "master:%s" % (path,) + mod.__loader__ = self + if is_pkg: + mod.__path__ = [] + mod.__package__ = str(fullname) + else: + mod.__package__ = str(str_rpartition(to_text(fullname), '.')[0]) + exec(code, mod.__dict__) + self._loaded.add(fullname) + return mod + + +class TemporaryEnvironment(object): + """ + Apply environment changes from `env` until :meth:`revert` is called. Values + in the dict may be :data:`None` to indicate the relevant key should be + deleted. + """ + def __init__(self, env=None): + self.original = dict(os.environ) + self.env = env or {} + for key, value in iteritems(self.env): + key = mitogen.core.to_text(key) + value = mitogen.core.to_text(value) + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = str(value) + + def revert(self): + """ + Revert changes made by the module to the process environment. This must + always run, as some modules (e.g. git.py) set variables like GIT_SSH + that must be cleared out between runs. + """ + os.environ.clear() + os.environ.update(self.original) + + +class TemporaryArgv(object): + def __init__(self, argv): + self.original = sys.argv[:] + sys.argv[:] = map(str, argv) + + def revert(self): + sys.argv[:] = self.original + + +class NewStyleStdio(object): + """ + Patch ansible.module_utils.basic argument globals. + """ + def __init__(self, args, temp_dir): + self.temp_dir = temp_dir + self.original_stdout = sys.stdout + self.original_stderr = sys.stderr + self.original_stdin = sys.stdin + sys.stdout = StringIO() + sys.stderr = StringIO() + encoded = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + ansible.module_utils.basic._ANSIBLE_ARGS = utf8(encoded) + sys.stdin = StringIO(mitogen.core.to_text(encoded)) + + self.original_get_path = getattr(ansible.module_utils.basic, + 'get_module_path', None) + ansible.module_utils.basic.get_module_path = self._get_path + + def _get_path(self): + return self.temp_dir + + def revert(self): + ansible.module_utils.basic.get_module_path = self.original_get_path + sys.stdout = self.original_stdout + sys.stderr = self.original_stderr + sys.stdin = self.original_stdin + ansible.module_utils.basic._ANSIBLE_ARGS = '{}' + + +class ProgramRunner(Runner): + """ + Base class for runners that run external programs. + + :param str path: + Absolute path to the program file on the master, as it can be retrieved + via :class:`mitogen.service.FileService`. + :param bool emulate_tty: + If :data:`True`, execute the program with `stdout` and `stderr` merged + into a single pipe, emulating Ansible behaviour when an SSH TTY is in + use. + """ + def __init__(self, path, emulate_tty=None, **kwargs): + super(ProgramRunner, self).__init__(**kwargs) + self.emulate_tty = emulate_tty + self.path = path + + def setup(self): + super(ProgramRunner, self).setup() + self._setup_program() + + def _get_program_filename(self): + """ + Return the filename used for program on disk. Ansible uses the original + filename for non-Ansiballz runs, and "ansible_module_+filename for + Ansiballz runs. + """ + return os.path.basename(self.path) + + program_fp = None + + def _setup_program(self): + """ + Create a temporary file containing the program code. The code is + fetched via :meth:`_get_program`. + """ + filename = self._get_program_filename() + path = os.path.join(self.get_temp_dir(), filename) + self.program_fp = open(path, 'wb') + self.program_fp.write(self._get_program()) + self.program_fp.flush() + os.chmod(self.program_fp.name, int('0700', 8)) + reopen_readonly(self.program_fp) + + def _get_program(self): + """ + Fetch the module binary from the master if necessary. + """ + return ansible_mitogen.target.get_small_file( + context=self.service_context, + path=self.path, + ) + + def _get_program_args(self): + """ + Return any arguments to pass to the program. + """ + return [] + + def revert(self): + """ + Delete the temporary program file. + """ + if self.program_fp: + self.program_fp.close() + super(ProgramRunner, self).revert() + + def _get_argv(self): + """ + Return the final argument vector used to execute the program. + """ + return [ + self.args.get('_ansible_shell_executable', '/bin/sh'), + '-c', + self._get_shell_fragment(), + ] + + def _get_shell_fragment(self): + return "%s %s" % ( + shlex_quote(self.program_fp.name), + ' '.join(map(shlex_quote, self._get_program_args())), + ) + + def _run(self): + try: + rc, stdout, stderr = ansible_mitogen.target.exec_args( + args=self._get_argv(), + emulate_tty=self.emulate_tty, + ) + except Exception: + LOG.exception('While running %s', self._get_argv()) + e = sys.exc_info()[1] + return { + u'rc': 1, + u'stdout': u'', + u'stderr': u'%s: %s' % (type(e), e), + } + + return { + u'rc': rc, + u'stdout': mitogen.core.to_text(stdout), + u'stderr': mitogen.core.to_text(stderr), + } + + +class ArgsFileRunner(Runner): + def setup(self): + super(ArgsFileRunner, self).setup() + self._setup_args() + + def _setup_args(self): + """ + Create a temporary file containing the module's arguments. The + arguments are formatted via :meth:`_get_args`. + """ + self.args_fp = tempfile.NamedTemporaryFile( + prefix='ansible_mitogen', + suffix='-args', + dir=self.get_temp_dir(), + ) + self.args_fp.write(utf8(self._get_args_contents())) + self.args_fp.flush() + reopen_readonly(self.program_fp) + + def _get_args_contents(self): + """ + Return the module arguments formatted as JSON. + """ + return json.dumps(self.args) + + def _get_program_args(self): + return [self.args_fp.name] + + def revert(self): + """ + Delete the temporary argument file. + """ + self.args_fp.close() + super(ArgsFileRunner, self).revert() + + +class BinaryRunner(ArgsFileRunner, ProgramRunner): + pass + + +class ScriptRunner(ProgramRunner): + def __init__(self, interpreter_fragment, is_python, **kwargs): + super(ScriptRunner, self).__init__(**kwargs) + self.interpreter_fragment = interpreter_fragment + self.is_python = is_python + + b_ENCODING_STRING = b('# -*- coding: utf-8 -*-') + + def _get_program(self): + return self._rewrite_source( + super(ScriptRunner, self)._get_program() + ) + + def _get_argv(self): + return [ + self.args.get('_ansible_shell_executable', '/bin/sh'), + '-c', + self._get_shell_fragment(), + ] + + def _get_shell_fragment(self): + """ + Scripts are eligible for having their hashbang line rewritten, and to + be executed via /bin/sh using the ansible_*_interpreter value used as a + shell fragment prefixing to the invocation. + """ + return "%s %s %s" % ( + self.interpreter_fragment, + shlex_quote(self.program_fp.name), + ' '.join(map(shlex_quote, self._get_program_args())), + ) + + def _rewrite_source(self, s): + """ + Mutate the source according to the per-task parameters. + """ + # While Ansible rewrites the #! using ansible_*_interpreter, it is + # never actually used to execute the script, instead it is a shell + # fragment consumed by shell/__init__.py::build_module_command(). + new = [b('#!') + utf8(self.interpreter_fragment)] + if self.is_python: + new.append(self.b_ENCODING_STRING) + + _, _, rest = bytes_partition(s, b('\n')) + new.append(rest) + return b('\n').join(new) + + +class NewStyleRunner(ScriptRunner): + """ + Execute a new-style Ansible module, where Module Replacer-related tricks + aren't required. + """ + #: path => new-style module bytecode. + _code_by_path = {} + + def __init__(self, module_map, **kwargs): + super(NewStyleRunner, self).__init__(**kwargs) + self.module_map = module_map + + def _setup_imports(self): + """ + Ensure the local importer and PushFileService has everything for the + Ansible module before setup() completes, but before detach() is called + in an asynchronous task. + + The master automatically streams modules towards us concurrent to the + runner invocation, however there is no public API to synchronize on the + completion of those preloads. Instead simply reuse the importer's + synchronization mechanism by importing everything the module will need + prior to detaching. + """ + for fullname, _, _ in self.module_map['custom']: + mitogen.core.import_module(fullname) + for fullname in self.module_map['builtin']: + mitogen.core.import_module(fullname) + + def _setup_excepthook(self): + """ + Starting with Ansible 2.6, some modules (file.py) install a + sys.excepthook and never clean it up. So we must preserve the original + excepthook and restore it after the run completes. + """ + self.original_excepthook = sys.excepthook + + def setup(self): + super(NewStyleRunner, self).setup() + + self._stdio = NewStyleStdio(self.args, self.get_temp_dir()) + # It is possible that not supplying the script filename will break some + # module, but this has never been a bug report. Instead act like an + # interpreter that had its script piped on stdin. + self._argv = TemporaryArgv(['']) + self._importer = ModuleUtilsImporter( + context=self.service_context, + module_utils=self.module_map['custom'], + ) + self._setup_imports() + self._setup_excepthook() + self.atexit_wrapper = AtExitWrapper() + if libc__res_init: + libc__res_init() + + def _revert_excepthook(self): + sys.excepthook = self.original_excepthook + + def revert(self): + self.atexit_wrapper.revert() + self._argv.revert() + self._stdio.revert() + self._revert_excepthook() + super(NewStyleRunner, self).revert() + + def _get_program_filename(self): + """ + See ProgramRunner._get_program_filename(). + """ + return 'ansible_module_' + os.path.basename(self.path) + + def _setup_args(self): + pass + + # issue #555: in old times it was considered good form to reload sys and + # change the default encoding. This hack was removed from Ansible long ago, + # but not before permeating into many third party modules. + PREHISTORIC_HACK_RE = re.compile( + b(r'reload\s*\(\s*sys\s*\)\s*' + r'sys\s*\.\s*setdefaultencoding\([^)]+\)') + ) + + def _setup_program(self): + source = ansible_mitogen.target.get_small_file( + context=self.service_context, + path=self.path, + ) + self.source = self.PREHISTORIC_HACK_RE.sub(b(''), source) + + def _get_code(self): + try: + return self._code_by_path[self.path] + except KeyError: + return self._code_by_path.setdefault(self.path, compile( + # Py2.4 doesn't support kwargs. + self.source, # source + "master:" + self.path, # filename + 'exec', # mode + 0, # flags + True, # dont_inherit + )) + + if mitogen.core.PY3: + main_module_name = '__main__' + else: + main_module_name = b('__main__') + + def _handle_magic_exception(self, mod, exc): + """ + Beginning with Ansible >2.6, some modules (file.py) install a + sys.excepthook which is a closure over AnsibleModule, redirecting the + magical exception to AnsibleModule.fail_json(). + + For extra special needs bonus points, the class is not defined in + module_utils, but is defined in the module itself, meaning there is no + type for isinstance() that outlasts the invocation. + """ + klass = getattr(mod, 'AnsibleModuleError', None) + if klass and isinstance(exc, klass): + mod.module.fail_json(**exc.results) + + def _run_code(self, code, mod): + try: + if mitogen.core.PY3: + exec(code, vars(mod)) + else: + exec('exec code in vars(mod)') + except Exception: + self._handle_magic_exception(mod, sys.exc_info()[1]) + raise + + def _run(self): + mod = types.ModuleType(self.main_module_name) + mod.__package__ = None + # Some Ansible modules use __file__ to find the Ansiballz temporary + # directory. We must provide some temporary path in __file__, but we + # don't want to pointlessly write the module to disk when it never + # actually needs to exist. So just pass the filename as it would exist. + mod.__file__ = os.path.join( + self.get_temp_dir(), + 'ansible_module_' + os.path.basename(self.path), + ) + + code = self._get_code() + rc = 2 + try: + try: + self._run_code(code, mod) + except SystemExit: + exc = sys.exc_info()[1] + rc = exc.args[0] + except Exception: + # This writes to stderr by default. + traceback.print_exc() + rc = 1 + + finally: + self.atexit_wrapper.run_callbacks() + + return { + u'rc': rc, + u'stdout': mitogen.core.to_text(sys.stdout.getvalue()), + u'stderr': mitogen.core.to_text(sys.stderr.getvalue()), + } + + +class JsonArgsRunner(ScriptRunner): + JSON_ARGS = b('<>') + + def _get_args_contents(self): + return json.dumps(self.args).encode() + + def _rewrite_source(self, s): + return ( + super(JsonArgsRunner, self)._rewrite_source(s) + .replace(self.JSON_ARGS, self._get_args_contents()) + ) + + +class WantJsonRunner(ArgsFileRunner, ScriptRunner): + pass + + +class OldStyleRunner(ArgsFileRunner, ScriptRunner): + def _get_args_contents(self): + """ + Mimic the argument formatting behaviour of + ActionBase._execute_module(). + """ + return ' '.join( + '%s=%s' % (key, shlex_quote(str(self.args[key]))) + for key in self.args + ) + ' ' # Bug-for-bug :( diff --git a/mitogen-0.2.7/ansible_mitogen/runner.pyc b/mitogen-0.2.7/ansible_mitogen/runner.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c4ee0e590fd968b602289564a88270e0c06b946 GIT binary patch literal 36946 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/services.py b/mitogen-0.2.7/ansible_mitogen/services.py new file mode 100644 index 000000000..a7c0e46 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/services.py @@ -0,0 +1,537 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +Classes in this file define Mitogen 'services' that run (initially) within the +connection multiplexer process that is forked off the top-level controller +process. + +Once a worker process connects to a multiplexer process +(Connection._connect()), it communicates with these services to establish new +connections, grant access to files by children, and register for notification +when a child has completed a job. +""" + +from __future__ import absolute_import +from __future__ import unicode_literals + +import logging +import os +import os.path +import sys +import threading + +import ansible.constants + +import mitogen +import mitogen.service +import mitogen.utils +import ansible_mitogen.loaders +import ansible_mitogen.module_finder +import ansible_mitogen.target + + +LOG = logging.getLogger(__name__) + +# Force load of plugin to ensure ConfigManager has definitions loaded. Done +# during module import to ensure a single-threaded environment; PluginLoader +# is not thread-safe. +ansible_mitogen.loaders.shell_loader.get('sh') + + +if sys.version_info[0] == 3: + def reraise(tp, value, tb): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value +else: + exec( + "def reraise(tp, value, tb=None):\n" + " raise tp, value, tb\n" + ) + + +def _get_candidate_temp_dirs(): + try: + # >=2.5 + options = ansible.constants.config.get_plugin_options('shell', 'sh') + remote_tmp = options.get('remote_tmp') or ansible.constants.DEFAULT_REMOTE_TMP + system_tmpdirs = options.get('system_tmpdirs', ('/var/tmp', '/tmp')) + except AttributeError: + # 2.3 + remote_tmp = ansible.constants.DEFAULT_REMOTE_TMP + system_tmpdirs = ('/var/tmp', '/tmp') + + return mitogen.utils.cast([remote_tmp] + list(system_tmpdirs)) + + +def key_from_dict(**kwargs): + """ + Return a unique string representation of a dict as quickly as possible. + Used to generated deduplication keys from a request. + """ + out = [] + stack = [kwargs] + while stack: + obj = stack.pop() + if isinstance(obj, dict): + stack.extend(sorted(obj.items())) + elif isinstance(obj, (list, tuple)): + stack.extend(obj) + else: + out.append(str(obj)) + return ''.join(out) + + +class Error(Exception): + pass + + +class ContextService(mitogen.service.Service): + """ + Used by workers to fetch the single Context instance corresponding to a + connection configuration, creating the matching connection if it does not + exist. + + For connection methods and their parameters, see: + https://mitogen.readthedocs.io/en/latest/api.html#context-factories + + This concentrates connections in the top-level process, which may become a + bottleneck. The bottleneck can be removed using per-CPU connection + processes and arranging for the worker to select one according to a hash of + the connection parameters (sharding). + """ + max_interpreters = int(os.getenv('MITOGEN_MAX_INTERPRETERS', '20')) + + def __init__(self, *args, **kwargs): + super(ContextService, self).__init__(*args, **kwargs) + self._lock = threading.Lock() + #: Records the :meth:`get` result dict for successful calls, returned + #: for identical subsequent calls. Keyed by :meth:`key_from_dict`. + self._response_by_key = {} + #: List of :class:`mitogen.core.Latch` awaiting the result for a + #: particular key. + self._latches_by_key = {} + #: Mapping of :class:`mitogen.core.Context` -> reference count. Each + #: call to :meth:`get` increases this by one. Calls to :meth:`put` + #: decrease it by one. + self._refs_by_context = {} + #: List of contexts in creation order by via= parameter. When + #: :attr:`max_interpreters` is reached, the most recently used context + #: is destroyed to make room for any additional context. + self._lru_by_via = {} + #: :func:`key_from_dict` result by Context. + self._key_by_context = {} + #: Mapping of Context -> parent Context + self._via_by_context = {} + + @mitogen.service.expose(mitogen.service.AllowParents()) + @mitogen.service.arg_spec({ + 'context': mitogen.core.Context + }) + def reset(self, context): + """ + Return a reference, forcing close and discard of the underlying + connection. Used for 'meta: reset_connection' or when some other error + is detected. + """ + LOG.debug('%r.reset(%r)', self, context) + self._lock.acquire() + try: + self._shutdown_unlocked(context) + finally: + self._lock.release() + + @mitogen.service.expose(mitogen.service.AllowParents()) + @mitogen.service.arg_spec({ + 'context': mitogen.core.Context + }) + def put(self, context): + """ + Return a reference, making it eligable for recycling once its reference + count reaches zero. + """ + LOG.debug('%r.put(%r)', self, context) + self._lock.acquire() + try: + if self._refs_by_context.get(context, 0) == 0: + LOG.warning('%r.put(%r): refcount was 0. shutdown_all called?', + self, context) + return + self._refs_by_context[context] -= 1 + finally: + self._lock.release() + + def _produce_response(self, key, response): + """ + Reply to every waiting request matching a configuration key with a + response dictionary, deleting the list of waiters when done. + + :param str key: + Result of :meth:`key_from_dict` + :param dict response: + Response dictionary + :returns: + Number of waiters that were replied to. + """ + self._lock.acquire() + try: + latches = self._latches_by_key.pop(key) + count = len(latches) + for latch in latches: + latch.put(response) + finally: + self._lock.release() + return count + + def _forget_context_unlocked(self, context): + key = self._key_by_context.get(context) + if key is None: + LOG.debug('%r: attempt to forget unknown %r', self, context) + return + + self._response_by_key.pop(key, None) + self._latches_by_key.pop(key, None) + self._key_by_context.pop(context, None) + self._refs_by_context.pop(context, None) + self._via_by_context.pop(context, None) + self._lru_by_via.pop(context, None) + + def _shutdown_unlocked(self, context, lru=None, new_context=None): + """ + Arrange for `context` to be shut down, and optionally add `new_context` + to the LRU list while holding the lock. + """ + LOG.info('%r._shutdown_unlocked(): shutting down %r', self, context) + context.shutdown() + via = self._via_by_context.get(context) + if via: + lru = self._lru_by_via.get(via) + if lru: + if context in lru: + lru.remove(context) + if new_context: + lru.append(new_context) + self._forget_context_unlocked(context) + + def _update_lru_unlocked(self, new_context, spec, via): + """ + Update the LRU ("MRU"?) list associated with the connection described + by `kwargs`, destroying the most recently created context if the list + is full. Finally add `new_context` to the list. + """ + self._via_by_context[new_context] = via + + lru = self._lru_by_via.setdefault(via, []) + if len(lru) < self.max_interpreters: + lru.append(new_context) + return + + for context in reversed(lru): + if self._refs_by_context[context] == 0: + break + else: + LOG.warning('via=%r reached maximum number of interpreters, ' + 'but they are all marked as in-use.', via) + return + + self._shutdown_unlocked(context, lru=lru, new_context=new_context) + + def _update_lru(self, new_context, spec, via): + self._lock.acquire() + try: + self._update_lru_unlocked(new_context, spec, via) + finally: + self._lock.release() + + @mitogen.service.expose(mitogen.service.AllowParents()) + def dump(self): + """ + For testing, return a list of dicts describing every currently + connected context. + """ + return [ + { + 'context_name': context.name, + 'via': getattr(self._via_by_context.get(context), + 'name', None), + 'refs': self._refs_by_context.get(context), + } + for context, key in sorted(self._key_by_context.items(), + key=lambda c_k: c_k[0].context_id) + ] + + @mitogen.service.expose(mitogen.service.AllowParents()) + def shutdown_all(self): + """ + For testing use, arrange for all connections to be shut down. + """ + self._lock.acquire() + try: + for context in list(self._key_by_context): + self._shutdown_unlocked(context) + finally: + self._lock.release() + + def _on_context_disconnect(self, context): + """ + Respond to Context disconnect event by deleting any record of the no + longer reachable context. This method runs in the Broker thread and + must not to block. + """ + self._lock.acquire() + try: + LOG.info('%r: Forgetting %r due to stream disconnect', self, context) + self._forget_context_unlocked(context) + finally: + self._lock.release() + + ALWAYS_PRELOAD = ( + 'ansible.module_utils.basic', + 'ansible.module_utils.json_utils', + 'ansible.release', + 'ansible_mitogen.runner', + 'ansible_mitogen.target', + 'mitogen.fork', + 'mitogen.service', + ) + + def _send_module_forwards(self, context): + self.router.responder.forward_modules(context, self.ALWAYS_PRELOAD) + + _candidate_temp_dirs = None + + def _get_candidate_temp_dirs(self): + """ + Return a list of locations to try to create the single temporary + directory used by the run. This simply caches the (expensive) plugin + load of :func:`_get_candidate_temp_dirs`. + """ + if self._candidate_temp_dirs is None: + self._candidate_temp_dirs = _get_candidate_temp_dirs() + return self._candidate_temp_dirs + + def _connect(self, key, spec, via=None): + """ + Actual connect implementation. Arranges for the Mitogen connection to + be created and enqueues an asynchronous call to start the forked task + parent in the remote context. + + :param key: + Deduplication key representing the connection configuration. + :param spec: + Connection specification. + :returns: + Dict like:: + + { + 'context': mitogen.core.Context or None, + 'via': mitogen.core.Context or None, + 'init_child_result': { + 'fork_context': mitogen.core.Context, + 'home_dir': str or None, + }, + 'msg': str or None + } + + Where `context` is a reference to the newly constructed context, + `init_child_result` is the result of executing + :func:`ansible_mitogen.target.init_child` in that context, `msg` is + an error message and the remaining fields are :data:`None`, or + `msg` is :data:`None` and the remaining fields are set. + """ + try: + method = getattr(self.router, spec['method']) + except AttributeError: + raise Error('unsupported method: %(transport)s' % spec) + + context = method(via=via, unidirectional=True, **spec['kwargs']) + if via and spec.get('enable_lru'): + self._update_lru(context, spec, via) + + # Forget the context when its disconnect event fires. + mitogen.core.listen(context, 'disconnect', + lambda: self._on_context_disconnect(context)) + + self._send_module_forwards(context) + init_child_result = context.call( + ansible_mitogen.target.init_child, + log_level=LOG.getEffectiveLevel(), + candidate_temp_dirs=self._get_candidate_temp_dirs(), + ) + + if os.environ.get('MITOGEN_DUMP_THREAD_STACKS'): + from mitogen import debug + context.call(debug.dump_to_logger) + + self._key_by_context[context] = key + self._refs_by_context[context] = 0 + return { + 'context': context, + 'via': via, + 'init_child_result': init_child_result, + 'msg': None, + } + + def _wait_or_start(self, spec, via=None): + latch = mitogen.core.Latch() + key = key_from_dict(via=via, **spec) + self._lock.acquire() + try: + response = self._response_by_key.get(key) + if response is not None: + self._refs_by_context[response['context']] += 1 + latch.put(response) + return latch + + latches = self._latches_by_key.setdefault(key, []) + first = len(latches) == 0 + latches.append(latch) + finally: + self._lock.release() + + if first: + # I'm the first requestee, so I will create the connection. + try: + response = self._connect(key, spec, via=via) + count = self._produce_response(key, response) + # Only record the response for non-error results. + self._response_by_key[key] = response + # Set the reference count to the number of waiters. + self._refs_by_context[response['context']] += count + except Exception: + self._produce_response(key, sys.exc_info()) + + return latch + + disconnect_msg = ( + 'Channel was disconnected while connection attempt was in progress; ' + 'this may be caused by an abnormal Ansible exit, or due to an ' + 'unreliable target.' + ) + + @mitogen.service.expose(mitogen.service.AllowParents()) + @mitogen.service.arg_spec({ + 'stack': list + }) + def get(self, msg, stack): + """ + Return a Context referring to an established connection with the given + configuration, establishing new connections as necessary. + + :param list stack: + Connection descriptions. Each element is a dict containing 'method' + and 'kwargs' keys describing the Router method and arguments. + Subsequent elements are proxied via the previous. + + :returns dict: + * context: mitogen.parent.Context or None. + * init_child_result: Result of :func:`init_child`. + * msg: StreamError exception text or None. + * method_name: string failing method name. + """ + via = None + for spec in stack: + try: + result = self._wait_or_start(spec, via=via).get() + if isinstance(result, tuple): # exc_info() + reraise(*result) + via = result['context'] + except mitogen.core.ChannelError: + return { + 'context': None, + 'init_child_result': None, + 'method_name': spec['method'], + 'msg': self.disconnect_msg, + } + except mitogen.core.StreamError as e: + return { + 'context': None, + 'init_child_result': None, + 'method_name': spec['method'], + 'msg': str(e), + } + + return result + + +class ModuleDepService(mitogen.service.Service): + """ + Scan a new-style module and produce a cached mapping of module_utils names + to their resolved filesystem paths. + """ + invoker_class = mitogen.service.SerializedInvoker + + def __init__(self, *args, **kwargs): + super(ModuleDepService, self).__init__(*args, **kwargs) + self._cache = {} + + def _get_builtin_names(self, builtin_path, resolved): + return [ + mitogen.core.to_text(fullname) + for fullname, path, is_pkg in resolved + if os.path.abspath(path).startswith(builtin_path) + ] + + def _get_custom_tups(self, builtin_path, resolved): + return [ + (mitogen.core.to_text(fullname), + mitogen.core.to_text(path), + is_pkg) + for fullname, path, is_pkg in resolved + if not os.path.abspath(path).startswith(builtin_path) + ] + + @mitogen.service.expose(policy=mitogen.service.AllowParents()) + @mitogen.service.arg_spec({ + 'module_name': mitogen.core.UnicodeType, + 'module_path': mitogen.core.FsPathTypes, + 'search_path': tuple, + 'builtin_path': mitogen.core.FsPathTypes, + 'context': mitogen.core.Context, + }) + def scan(self, module_name, module_path, search_path, builtin_path, context): + key = (module_name, search_path) + if key not in self._cache: + resolved = ansible_mitogen.module_finder.scan( + module_name=module_name, + module_path=module_path, + search_path=tuple(search_path) + (builtin_path,), + ) + builtin_path = os.path.abspath(builtin_path) + builtin = self._get_builtin_names(builtin_path, resolved) + custom = self._get_custom_tups(builtin_path, resolved) + self._cache[key] = { + 'builtin': builtin, + 'custom': custom, + } + return self._cache[key] diff --git a/mitogen-0.2.7/ansible_mitogen/services.pyc b/mitogen-0.2.7/ansible_mitogen/services.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7669a6bf3b04489b34dd4e50a23e3c7cbf9aed9b GIT binary patch literal 18260 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 A>Hq)$ literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/strategy.py b/mitogen-0.2.7/ansible_mitogen/strategy.py new file mode 100644 index 000000000..b9211fc --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/strategy.py @@ -0,0 +1,296 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +import os +import signal +import threading + +import mitogen.core +import ansible_mitogen.affinity +import ansible_mitogen.loaders +import ansible_mitogen.mixins +import ansible_mitogen.process + +import ansible +import ansible.executor.process.worker + + +ANSIBLE_VERSION_MIN = '2.3' +ANSIBLE_VERSION_MAX = '2.7' +NEW_VERSION_MSG = ( + "Your Ansible version (%s) is too recent. The most recent version\n" + "supported by Mitogen for Ansible is %s.x. Please check the Mitogen\n" + "release notes to see if a new version is available, otherwise\n" + "subscribe to the corresponding GitHub issue to be notified when\n" + "support becomes available.\n" + "\n" + " https://mitogen.rtfd.io/en/latest/changelog.html\n" + " https://github.com/dw/mitogen/issues/\n" +) +OLD_VERSION_MSG = ( + "Your version of Ansible (%s) is too old. The oldest version supported by " + "Mitogen for Ansible is %s." +) + + +def _assert_supported_release(): + """ + Throw AnsibleError with a descriptive message in case of being loaded into + an unsupported Ansible release. + """ + v = ansible.__version__ + + if v[:len(ANSIBLE_VERSION_MIN)] < ANSIBLE_VERSION_MIN: + raise ansible.errors.AnsibleError( + OLD_VERSION_MSG % (v, ANSIBLE_VERSION_MIN) + ) + + if v[:len(ANSIBLE_VERSION_MAX)] > ANSIBLE_VERSION_MAX: + raise ansible.errors.AnsibleError( + NEW_VERSION_MSG % (ansible.__version__, ANSIBLE_VERSION_MAX) + ) + + +def _patch_awx_callback(): + """ + issue #400: AWX loads a display callback that suffers from thread-safety + issues. Detect the presence of older AWX versions and patch the bug. + """ + # AWX uses sitecustomize.py to force-load this package. If it exists, we're + # running under AWX. + try: + from awx_display_callback.events import EventContext + from awx_display_callback.events import event_context + except ImportError: + return + + if hasattr(EventContext(), '_local'): + # Patched version. + return + + def patch_add_local(self, **kwargs): + tls = vars(self._local) + ctx = tls.setdefault('_ctx', {}) + ctx.update(kwargs) + + EventContext._local = threading.local() + EventContext.add_local = patch_add_local + +_patch_awx_callback() + + +def wrap_action_loader__get(name, *args, **kwargs): + """ + While the mitogen strategy is active, trap action_loader.get() calls, + augmenting any fetched class with ActionModuleMixin, which replaces various + helper methods inherited from ActionBase with implementations that avoid + the use of shell fragments wherever possible. + + This is used instead of static subclassing as it generalizes to third party + action modules outside the Ansible tree. + """ + klass = action_loader__get(name, class_only=True) + if klass: + bases = (ansible_mitogen.mixins.ActionModuleMixin, klass) + adorned_klass = type(str(name), bases, {}) + if kwargs.get('class_only'): + return adorned_klass + return adorned_klass(*args, **kwargs) + + +def wrap_connection_loader__get(name, *args, **kwargs): + """ + While the strategy is active, rewrite connection_loader.get() calls for + some transports into requests for a compatible Mitogen transport. + """ + if name in ('docker', 'kubectl', 'jail', 'local', 'lxc', + 'lxd', 'machinectl', 'setns', 'ssh'): + name = 'mitogen_' + name + return connection_loader__get(name, *args, **kwargs) + + +def wrap_worker__run(*args, **kwargs): + """ + While the strategy is active, rewrite connection_loader.get() calls for + some transports into requests for a compatible Mitogen transport. + """ + # Ignore parent's attempts to murder us when we still need to write + # profiling output. + if mitogen.core._profile_hook.__name__ != '_profile_hook': + signal.signal(signal.SIGTERM, signal.SIG_IGN) + + ansible_mitogen.logging.set_process_name('task') + ansible_mitogen.affinity.policy.assign_worker() + return mitogen.core._profile_hook('WorkerProcess', + lambda: worker__run(*args, **kwargs) + ) + + +class StrategyMixin(object): + """ + This mix-in enhances any built-in strategy by arranging for various Mitogen + services to be initialized in the Ansible top-level process, and for worker + processes to grow support for using those top-level services to communicate + with and execute modules on remote hosts. + + Mitogen: + + A private Broker IO multiplexer thread is created to dispatch IO + between the local Router and any connected streams, including streams + connected to Ansible WorkerProcesses, and SSH commands implementing + connections to remote machines. + + A Router is created that implements message dispatch to any locally + registered handlers, and message routing for remote streams. Router is + the junction point through which WorkerProceses and remote SSH contexts + can communicate. + + Router additionally adds message handlers for a variety of base + services, review the Standard Handles section of the How It Works guide + in the documentation. + + A ContextService is installed as a message handler in the master + process and run on a private thread. It is responsible for accepting + requests to establish new SSH connections from worker processes, and + ensuring precisely one connection exists and is reused for subsequent + playbook steps. The service presently runs in a single thread, so to + begin with, new SSH connections are serialized. + + Finally a mitogen.unix listener is created through which WorkerProcess + can establish a connection back into the master process, in order to + avail of ContextService. A UNIX listener socket is necessary as there + is no more sane mechanism to arrange for IPC between the Router in the + master process, and the corresponding Router in the worker process. + + Ansible: + + PluginLoader monkey patches are installed to catch attempts to create + connection and action plug-ins. + + For connection plug-ins, if the desired method is "local" or "ssh", it + is redirected to the "mitogen" connection plug-in. That plug-in + implements communication via a UNIX socket connection to the top-level + Ansible process, and uses ContextService running in the top-level + process to actually establish and manage the connection. + + For action plug-ins, the original class is looked up as usual, but a + new subclass is created dynamically in order to mix-in + ansible_mitogen.target.ActionModuleMixin, which overrides many of the + methods usually inherited from ActionBase in order to replace them with + pure-Python equivalents that avoid the use of shell. + + In particular, _execute_module() is overridden with an implementation + that uses ansible_mitogen.target.run_module() executed in the target + Context. run_module() implements module execution by importing the + module as if it were a normal Python module, and capturing its output + in the remote process. Since the Mitogen module loader is active in the + remote process, all the heavy lifting of transferring the action module + and its dependencies are automatically handled by Mitogen. + """ + def _install_wrappers(self): + """ + Install our PluginLoader monkey patches and update global variables + with references to the real functions. + """ + global action_loader__get + action_loader__get = ansible_mitogen.loaders.action_loader.get + ansible_mitogen.loaders.action_loader.get = wrap_action_loader__get + + global connection_loader__get + connection_loader__get = ansible_mitogen.loaders.connection_loader.get + ansible_mitogen.loaders.connection_loader.get = wrap_connection_loader__get + + global worker__run + worker__run = ansible.executor.process.worker.WorkerProcess.run + ansible.executor.process.worker.WorkerProcess.run = wrap_worker__run + + def _remove_wrappers(self): + """ + Uninstall the PluginLoader monkey patches. + """ + ansible_mitogen.loaders.action_loader.get = action_loader__get + ansible_mitogen.loaders.connection_loader.get = connection_loader__get + ansible.executor.process.worker.WorkerProcess.run = worker__run + + def _add_plugin_paths(self): + """ + Add the Mitogen plug-in directories to the ModuleLoader path, avoiding + the need for manual configuration. + """ + base_dir = os.path.join(os.path.dirname(__file__), 'plugins') + ansible_mitogen.loaders.connection_loader.add_directory( + os.path.join(base_dir, 'connection') + ) + ansible_mitogen.loaders.action_loader.add_directory( + os.path.join(base_dir, 'action') + ) + + def _queue_task(self, host, task, task_vars, play_context): + """ + Many PluginLoader caches are defective as they are only populated in + the ephemeral WorkerProcess. Touch each plug-in path before forking to + ensure all workers receive a hot cache. + """ + ansible_mitogen.loaders.module_loader.find_plugin( + name=task.action, + mod_type='', + ) + ansible_mitogen.loaders.connection_loader.get( + name=play_context.connection, + class_only=True, + ) + ansible_mitogen.loaders.action_loader.get( + name=task.action, + class_only=True, + ) + + return super(StrategyMixin, self)._queue_task( + host=host, + task=task, + task_vars=task_vars, + play_context=play_context, + ) + + def run(self, iterator, play_context, result=0): + """ + Arrange for a mitogen.master.Router to be available for the duration of + the strategy's real run() method. + """ + _assert_supported_release() + + ansible_mitogen.process.MuxProcess.start() + run = super(StrategyMixin, self).run + self._add_plugin_paths() + self._install_wrappers() + try: + return mitogen.core._profile_hook('Strategy', + lambda: run(iterator, play_context) + ) + finally: + self._remove_wrappers() diff --git a/mitogen-0.2.7/ansible_mitogen/strategy.pyc b/mitogen-0.2.7/ansible_mitogen/strategy.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0453cc0adafc2887df5646d44db0d0e57e2dc9f6 GIT binary patch literal 11444 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 G^8W&E$tcMH literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/target.py b/mitogen-0.2.7/ansible_mitogen/target.py new file mode 100644 index 000000000..652b5ad --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/target.py @@ -0,0 +1,777 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +Helper functions intended to be executed on the target. These are entrypoints +for file transfer, module execution and sundry bits like changing file modes. +""" + +import errno +import grp +import operator +import os +import pwd +import re +import signal +import stat +import subprocess +import sys +import tempfile +import traceback +import types + +# Absolute imports for <2.5. +logging = __import__('logging') + +import mitogen.core +import mitogen.fork +import mitogen.parent +import mitogen.service +from mitogen.core import b + +try: + import json +except ImportError: + import simplejson as json + +try: + reduce +except NameError: + # Python 3.x. + from functools import reduce + +try: + BaseException +except NameError: + # Python 2.4 + BaseException = Exception + + +# Ansible since PR #41749 inserts "import __main__" into +# ansible.module_utils.basic. Mitogen's importer will refuse such an import, so +# we must setup a fake "__main__" before that module is ever imported. The +# str() is to cast Unicode to bytes on Python 2.6. +if not sys.modules.get(str('__main__')): + sys.modules[str('__main__')] = types.ModuleType(str('__main__')) + +import ansible.module_utils.json_utils +import ansible_mitogen.runner + + +LOG = logging.getLogger(__name__) + +MAKE_TEMP_FAILED_MSG = ( + u"Unable to find a useable temporary directory. This likely means no\n" + u"system-supplied TMP directory can be written to, or all directories\n" + u"were mounted on 'noexec' filesystems.\n" + u"\n" + u"The following paths were tried:\n" + u" %(paths)s\n" + u"\n" + u"Please check '-vvv' output for a log of individual path errors." +) + +# Python 2.4/2.5 cannot support fork+threads whatsoever, it doesn't even fix up +# interpreter state. So 2.4/2.5 interpreters start .local() contexts for +# isolation instead. Since we don't have any crazy memory sharing problems to +# avoid, there is no virginal fork parent either. The child is started directly +# from the login/become process. In future this will be default everywhere, +# fork is brainwrong from the stone age. +FORK_SUPPORTED = sys.version_info >= (2, 6) + +#: Initialized to an econtext.parent.Context pointing at a pristine fork of +#: the target Python interpreter before it executes any code or imports. +_fork_parent = None + +#: Set by :func:`init_child` to the name of a writeable and executable +#: temporary directory accessible by the active user account. +good_temp_dir = None + + +def subprocess__Popen__close_fds(self, but): + """ + issue #362, #435: subprocess.Popen(close_fds=True) aka. + AnsibleModule.run_command() loops the entire FD space on Python<3.2. + CentOS>5 ships with 1,048,576 FDs by default, resulting in huge (>500ms) + latency starting children. Therefore replace Popen._close_fds on Linux with + a version that is O(fds) rather than O(_SC_OPEN_MAX). + """ + try: + names = os.listdir(u'/proc/self/fd') + except OSError: + # May fail if acting on a container that does not have /proc mounted. + self._original_close_fds(but) + return + + for name in names: + if not name.isdigit(): + continue + + fd = int(name, 10) + if fd > 2 and fd != but: + try: + os.close(fd) + except OSError: + pass + + +if ( + sys.platform.startswith(u'linux') and + sys.version < u'3.0' and + hasattr(subprocess.Popen, u'_close_fds') and + not mitogen.is_master +): + subprocess.Popen._original_close_fds = subprocess.Popen._close_fds + subprocess.Popen._close_fds = subprocess__Popen__close_fds + + +def get_small_file(context, path): + """ + Basic in-memory caching module fetcher. This generates one roundtrip for + every previously unseen file, so it is only a temporary solution. + + :param context: + Context we should direct FileService requests to. For now (and probably + forever) this is just the top-level Mitogen connection manager process. + :param path: + Path to fetch from FileService, must previously have been registered by + a privileged context using the `register` command. + :returns: + Bytestring file data. + """ + pool = mitogen.service.get_or_create_pool(router=context.router) + service = pool.get_service(u'mitogen.service.PushFileService') + return service.get(path) + + +def transfer_file(context, in_path, out_path, sync=False, set_owner=False): + """ + Streamily download a file from the connection multiplexer process in the + controller. + + :param mitogen.core.Context context: + Reference to the context hosting the FileService that will transmit the + file. + :param bytes in_path: + FileService registered name of the input file. + :param bytes out_path: + Name of the output path on the local disk. + :param bool sync: + If :data:`True`, ensure the file content and metadat are fully on disk + before renaming the temporary file over the existing file. This should + ensure in the case of system crash, either the entire old or new file + are visible post-reboot. + :param bool set_owner: + If :data:`True`, look up the metadata username and group on the local + system and file the file owner using :func:`os.fchmod`. + """ + out_path = os.path.abspath(out_path) + fd, tmp_path = tempfile.mkstemp(suffix='.tmp', + prefix='.ansible_mitogen_transfer-', + dir=os.path.dirname(out_path)) + fp = os.fdopen(fd, 'wb', mitogen.core.CHUNK_SIZE) + LOG.debug('transfer_file(%r) temporary file: %s', out_path, tmp_path) + + try: + try: + ok, metadata = mitogen.service.FileService.get( + context=context, + path=in_path, + out_fp=fp, + ) + if not ok: + raise IOError('transfer of %r was interrupted.' % (in_path,)) + + set_file_mode(tmp_path, metadata['mode'], fd=fp.fileno()) + if set_owner: + set_file_owner(tmp_path, metadata['owner'], metadata['group'], + fd=fp.fileno()) + finally: + fp.close() + + if sync: + os.fsync(fp.fileno()) + os.rename(tmp_path, out_path) + except BaseException: + os.unlink(tmp_path) + raise + + os.utime(out_path, (metadata['atime'], metadata['mtime'])) + + +def prune_tree(path): + """ + Like shutil.rmtree(), but log errors rather than discard them, and do not + waste multiple os.stat() calls discovering whether the object can be + deleted, just try deleting it instead. + """ + try: + os.unlink(path) + return + except OSError: + e = sys.exc_info()[1] + if not (os.path.isdir(path) and + e.args[0] in (errno.EPERM, errno.EISDIR)): + LOG.error('prune_tree(%r): %s', path, e) + return + + try: + # Ensure write access for readonly directories. Ignore error in case + # path is on a weird filesystem (e.g. vfat). + os.chmod(path, int('0700', 8)) + except OSError: + e = sys.exc_info()[1] + LOG.warning('prune_tree(%r): %s', path, e) + + try: + for name in os.listdir(path): + if name not in ('.', '..'): + prune_tree(os.path.join(path, name)) + os.rmdir(path) + except OSError: + e = sys.exc_info()[1] + LOG.error('prune_tree(%r): %s', path, e) + + +def is_good_temp_dir(path): + """ + Return :data:`True` if `path` can be used as a temporary directory, logging + any failures that may cause it to be unsuitable. If the directory doesn't + exist, we attempt to create it using :func:`os.makedirs`. + """ + if not os.path.exists(path): + try: + os.makedirs(path, mode=int('0700', 8)) + except OSError: + e = sys.exc_info()[1] + LOG.debug('temp dir %r unusable: did not exist and attempting ' + 'to create it failed: %s', path, e) + return False + + try: + tmp = tempfile.NamedTemporaryFile( + prefix='ansible_mitogen_is_good_temp_dir', + dir=path, + ) + except (OSError, IOError): + e = sys.exc_info()[1] + LOG.debug('temp dir %r unusable: %s', path, e) + return False + + try: + try: + os.chmod(tmp.name, int('0700', 8)) + except OSError: + e = sys.exc_info()[1] + LOG.debug('temp dir %r unusable: chmod failed: %s', path, e) + return False + + try: + # access(.., X_OK) is sufficient to detect noexec. + if not os.access(tmp.name, os.X_OK): + raise OSError('filesystem appears to be mounted noexec') + except OSError: + e = sys.exc_info()[1] + LOG.debug('temp dir %r unusable: %s', path, e) + return False + finally: + tmp.close() + + return True + + +def find_good_temp_dir(candidate_temp_dirs): + """ + Given a list of candidate temp directories extracted from ``ansible.cfg``, + combine it with the Python-builtin list of candidate directories used by + :mod:`tempfile`, then iteratively try each until one is found that is both + writeable and executable. + + :param list candidate_temp_dirs: + List of candidate $variable-expanded and tilde-expanded directory paths + that may be usable as a temporary directory. + """ + paths = [os.path.expandvars(os.path.expanduser(p)) + for p in candidate_temp_dirs] + paths.extend(tempfile._candidate_tempdir_list()) + + for path in paths: + if is_good_temp_dir(path): + LOG.debug('Selected temp directory: %r (from %r)', path, paths) + return path + + raise IOError(MAKE_TEMP_FAILED_MSG % { + 'paths': '\n '.join(paths), + }) + + +@mitogen.core.takes_econtext +def init_child(econtext, log_level, candidate_temp_dirs): + """ + Called by ContextService immediately after connection; arranges for the + (presently) spotless Python interpreter to be forked, where the newly + forked interpreter becomes the parent of any newly forked future + interpreters. + + This is necessary to prevent modules that are executed in-process from + polluting the global interpreter state in a way that effects explicitly + isolated modules. + + :param int log_level: + Logging package level active in the master. + :param list[str] candidate_temp_dirs: + List of $variable-expanded and tilde-expanded directory names to add to + candidate list of temporary directories. + + :returns: + Dict like:: + + { + 'fork_context': mitogen.core.Context or None, + 'good_temp_dir': ... + 'home_dir': str + } + + Where `fork_context` refers to the newly forked 'fork parent' context + the controller will use to start forked jobs, and `home_dir` is the + home directory for the active user account. + """ + # Copying the master's log level causes log messages to be filtered before + # they reach LogForwarder, thus reducing an influx of tiny messges waking + # the connection multiplexer process in the master. + LOG.setLevel(log_level) + logging.getLogger('ansible_mitogen').setLevel(log_level) + + # issue #536: if the json module is available, remove simplejson from the + # importer whitelist to avoid confusing certain Ansible modules. + if json.__name__ == 'json': + econtext.importer.whitelist.remove('simplejson') + + global _fork_parent + if FORK_SUPPORTED: + mitogen.parent.upgrade_router(econtext) + _fork_parent = econtext.router.fork() + + global good_temp_dir + good_temp_dir = find_good_temp_dir(candidate_temp_dirs) + + return { + u'fork_context': _fork_parent, + u'home_dir': mitogen.core.to_text(os.path.expanduser('~')), + u'good_temp_dir': good_temp_dir, + } + + +@mitogen.core.takes_econtext +def spawn_isolated_child(econtext): + """ + For helper functions executed in the fork parent context, arrange for + the context's router to be upgraded as necessary and for a new child to be + prepared. + + The actual fork occurs from the 'virginal fork parent', which does not have + any Ansible modules loaded prior to fork, to avoid conflicts resulting from + custom module_utils paths. + """ + mitogen.parent.upgrade_router(econtext) + if FORK_SUPPORTED: + context = econtext.router.fork() + else: + context = econtext.router.local() + LOG.debug('create_fork_child() -> %r', context) + return context + + +def run_module(kwargs): + """ + Set up the process environment in preparation for running an Ansible + module. This monkey-patches the Ansible libraries in various places to + prevent it from trying to kill the process on completion, and to prevent it + from reading sys.stdin. + """ + runner_name = kwargs.pop('runner_name') + klass = getattr(ansible_mitogen.runner, runner_name) + impl = klass(**mitogen.core.Kwargs(kwargs)) + return impl.run() + + +def _get_async_dir(): + return os.path.expanduser( + os.environ.get('ANSIBLE_ASYNC_DIR', '~/.ansible_async') + ) + + +class AsyncRunner(object): + def __init__(self, job_id, timeout_secs, started_sender, econtext, kwargs): + self.job_id = job_id + self.timeout_secs = timeout_secs + self.started_sender = started_sender + self.econtext = econtext + self.kwargs = kwargs + self._timed_out = False + self._init_path() + + def _init_path(self): + async_dir = _get_async_dir() + if not os.path.exists(async_dir): + os.makedirs(async_dir) + self.path = os.path.join(async_dir, self.job_id) + + def _update(self, dct): + """ + Update an async job status file. + """ + LOG.info('%r._update(%r, %r)', self, self.job_id, dct) + dct.setdefault('ansible_job_id', self.job_id) + dct.setdefault('data', '') + + fp = open(self.path + '.tmp', 'w') + try: + fp.write(json.dumps(dct)) + finally: + fp.close() + os.rename(self.path + '.tmp', self.path) + + def _on_sigalrm(self, signum, frame): + """ + Respond to SIGALRM (job timeout) by updating the job file and killing + the process. + """ + msg = "Job reached maximum time limit of %d seconds." % ( + self.timeout_secs, + ) + self._update({ + "failed": 1, + "finished": 1, + "msg": msg, + }) + self._timed_out = True + self.econtext.broker.shutdown() + + def _install_alarm(self): + signal.signal(signal.SIGALRM, self._on_sigalrm) + signal.alarm(self.timeout_secs) + + def _run_module(self): + kwargs = dict(self.kwargs, **{ + 'detach': True, + 'econtext': self.econtext, + 'emulate_tty': False, + }) + return run_module(kwargs) + + def _parse_result(self, dct): + filtered, warnings = ( + ansible.module_utils.json_utils. + _filter_non_json_lines(dct['stdout']) + ) + result = json.loads(filtered) + result.setdefault('warnings', []).extend(warnings) + result['stderr'] = dct['stderr'] or result.get('stderr', '') + self._update(result) + + def _run(self): + """ + 1. Immediately updates the status file to mark the job as started. + 2. Installs a timer/signal handler to implement the time limit. + 3. Runs as with run_module(), writing the result to the status file. + + :param dict kwargs: + Runner keyword arguments. + :param str job_id: + String job ID. + :param int timeout_secs: + If >0, limit the task's maximum run time. + """ + self._update({ + 'started': 1, + 'finished': 0, + 'pid': os.getpid() + }) + self.started_sender.send(True) + + if self.timeout_secs > 0: + self._install_alarm() + + dct = self._run_module() + if not self._timed_out: + # After SIGALRM fires, there is a window between broker responding + # to shutdown() by killing the process, and work continuing on the + # main thread. If main thread was asleep in at least + # basic.py/select.select(), an EINTR will be raised. We want to + # discard that exception. + try: + self._parse_result(dct) + except Exception: + self._update({ + "failed": 1, + "msg": traceback.format_exc(), + "data": dct['stdout'], # temporary notice only + "stderr": dct['stderr'] + }) + + def run(self): + try: + try: + self._run() + except Exception: + self._update({ + "failed": 1, + "msg": traceback.format_exc(), + }) + finally: + self.econtext.broker.shutdown() + + +@mitogen.core.takes_econtext +def run_module_async(kwargs, job_id, timeout_secs, started_sender, econtext): + """ + Execute a module with its run status and result written to a file, + terminating on the process on completion. This function must run in a child + forked using :func:`create_fork_child`. + + @param mitogen.core.Sender started_sender: + A sender that will receive :data:`True` once the job has reached a + point where its initial job file has been written. This is required to + avoid a race where an overly eager controller can check for a task + before it has reached that point in execution, which is possible at + least on Python 2.4, where forking is not available for async tasks. + """ + arunner = AsyncRunner( + job_id, + timeout_secs, + started_sender, + econtext, + kwargs + ) + arunner.run() + + +def get_user_shell(): + """ + For commands executed directly via an SSH command-line, SSH looks up the + user's shell via getpwuid() and only defaults to /bin/sh if that field is + missing or empty. + """ + try: + pw_shell = pwd.getpwuid(os.geteuid()).pw_shell + except KeyError: + pw_shell = None + + return pw_shell or '/bin/sh' + + +def exec_args(args, in_data='', chdir=None, shell=None, emulate_tty=False): + """ + Run a command in a subprocess, emulating the argument handling behaviour of + SSH. + + :param list[str]: + Argument vector. + :param bytes in_data: + Optional standard input for the command. + :param bool emulate_tty: + If :data:`True`, arrange for stdout and stderr to be merged into the + stdout pipe and for LF to be translated into CRLF, emulating the + behaviour of a TTY. + :return: + (return code, stdout bytes, stderr bytes) + """ + LOG.debug('exec_args(%r, ..., chdir=%r)', args, chdir) + assert isinstance(args, list) + + if emulate_tty: + stderr = subprocess.STDOUT + else: + stderr = subprocess.PIPE + + proc = subprocess.Popen( + args=args, + stdout=subprocess.PIPE, + stderr=stderr, + stdin=subprocess.PIPE, + cwd=chdir, + ) + stdout, stderr = proc.communicate(in_data) + + if emulate_tty: + stdout = stdout.replace(b('\n'), b('\r\n')) + return proc.returncode, stdout, stderr or b('') + + +def exec_command(cmd, in_data='', chdir=None, shell=None, emulate_tty=False): + """ + Run a command in a subprocess, emulating the argument handling behaviour of + SSH. + + :param bytes cmd: + String command line, passed to user's shell. + :param bytes in_data: + Optional standard input for the command. + :return: + (return code, stdout bytes, stderr bytes) + """ + assert isinstance(cmd, mitogen.core.UnicodeType) + return exec_args( + args=[get_user_shell(), '-c', cmd], + in_data=in_data, + chdir=chdir, + shell=shell, + emulate_tty=emulate_tty, + ) + + +def read_path(path): + """ + Fetch the contents of a filesystem `path` as bytes. + """ + return open(path, 'rb').read() + + +def set_file_owner(path, owner, group=None, fd=None): + if owner: + uid = pwd.getpwnam(owner).pw_uid + else: + uid = os.geteuid() + + if group: + gid = grp.getgrnam(group).gr_gid + else: + gid = os.getegid() + + if fd is not None and hasattr(os, 'fchown'): + os.fchown(fd, (uid, gid)) + else: + # Python<2.6 + os.chown(path, (uid, gid)) + + +def write_path(path, s, owner=None, group=None, mode=None, + utimes=None, sync=False): + """ + Writes bytes `s` to a filesystem `path`. + """ + path = os.path.abspath(path) + fd, tmp_path = tempfile.mkstemp(suffix='.tmp', + prefix='.ansible_mitogen_transfer-', + dir=os.path.dirname(path)) + fp = os.fdopen(fd, 'wb', mitogen.core.CHUNK_SIZE) + LOG.debug('write_path(path=%r) temporary file: %s', path, tmp_path) + + try: + try: + if mode: + set_file_mode(tmp_path, mode, fd=fp.fileno()) + if owner or group: + set_file_owner(tmp_path, owner, group, fd=fp.fileno()) + fp.write(s) + finally: + fp.close() + + if sync: + os.fsync(fp.fileno()) + os.rename(tmp_path, path) + except BaseException: + os.unlink(tmp_path) + raise + + if utimes: + os.utime(path, utimes) + + +CHMOD_CLAUSE_PAT = re.compile(r'([uoga]*)([+\-=])([ugo]|[rwx]*)') +CHMOD_MASKS = { + 'u': stat.S_IRWXU, + 'g': stat.S_IRWXG, + 'o': stat.S_IRWXO, + 'a': (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO), +} +CHMOD_BITS = { + 'u': {'r': stat.S_IRUSR, 'w': stat.S_IWUSR, 'x': stat.S_IXUSR}, + 'g': {'r': stat.S_IRGRP, 'w': stat.S_IWGRP, 'x': stat.S_IXGRP}, + 'o': {'r': stat.S_IROTH, 'w': stat.S_IWOTH, 'x': stat.S_IXOTH}, + 'a': { + 'r': (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH), + 'w': (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH), + 'x': (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + } +} + + +def apply_mode_spec(spec, mode): + """ + Given a symbolic file mode change specification in the style of chmod(1) + `spec`, apply changes in the specification to the numeric file mode `mode`. + """ + for clause in mitogen.core.to_text(spec).split(','): + match = CHMOD_CLAUSE_PAT.match(clause) + who, op, perms = match.groups() + for ch in who or 'a': + mask = CHMOD_MASKS[ch] + bits = CHMOD_BITS[ch] + cur_perm_bits = mode & mask + new_perm_bits = reduce(operator.or_, (bits[p] for p in perms), 0) + mode &= ~mask + if op == '=': + mode |= new_perm_bits + elif op == '+': + mode |= new_perm_bits | cur_perm_bits + else: + mode |= cur_perm_bits & ~new_perm_bits + return mode + + +def set_file_mode(path, spec, fd=None): + """ + Update the permissions of a file using the same syntax as chmod(1). + """ + if isinstance(spec, int): + new_mode = spec + elif not mitogen.core.PY3 and isinstance(spec, long): + new_mode = spec + elif spec.isdigit(): + new_mode = int(spec, 8) + else: + mode = os.stat(path).st_mode + new_mode = apply_mode_spec(spec, mode) + + if fd is not None and hasattr(os, 'fchmod'): + os.fchmod(fd, new_mode) + else: + os.chmod(path, new_mode) + + +def file_exists(path): + """ + Return :data:`True` if `path` exists. This is a wrapper function over + :func:`os.path.exists`, since its implementation module varies across + Python versions. + """ + return os.path.exists(path) diff --git a/mitogen-0.2.7/ansible_mitogen/target.pyc b/mitogen-0.2.7/ansible_mitogen/target.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6999c9f1ca683ecc395edc32a6b53eddd2b7baa GIT binary patch literal 24402 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 a^!ZXe^qfz923t>)}Nex;(r5Zts-Cm literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/transport_config.py b/mitogen-0.2.7/ansible_mitogen/transport_config.py new file mode 100644 index 000000000..ad1cab3 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/transport_config.py @@ -0,0 +1,636 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import +from __future__ import unicode_literals + +""" +Mitogen extends Ansible's target configuration mechanism in several ways that +require some care: + +* Per-task configurables in Ansible like ansible_python_interpreter are + connection-layer configurables in Mitogen. They must be extracted during each + task execution to form the complete connection-layer configuration. + +* Mitogen has extra configurables not supported by Ansible at all, such as + mitogen_ssh_debug_level. These are extracted the same way as + ansible_python_interpreter. + +* Mitogen allows connections to be delegated to other machines. Ansible has no + internal framework for this, and so Mitogen must figure out a delegated + connection configuration all on its own. It cannot reuse much of the Ansible + machinery for building a connection configuration, as that machinery is + deeply spread out and hard-wired to expect Ansible's usual mode of operation. + +For normal and delegate_to connections, Ansible's PlayContext is reused where +possible to maximize compatibility, but for proxy hops, configurations are +built up using the HostVars magic class to call VariableManager.get_vars() +behind the scenes on our behalf. Where Ansible has multiple sources of a +configuration item, for example, ansible_ssh_extra_args, Mitogen must (ideally +perfectly) reproduce how Ansible arrives at its value, without using mechanisms +that are hard-wired or change across Ansible versions. + +That is what this file is for. It exports two spec classes, one that takes all +information from PlayContext, and another that takes (almost) all information +from HostVars. +""" + +import abc +import os +import ansible.utils.shlex +import ansible.constants as C + +from ansible.module_utils.six import with_metaclass + + +import mitogen.core + + +def parse_python_path(s): + """ + Given the string set for ansible_python_interpeter, parse it using shell + syntax and return an appropriate argument vector. + """ + if s: + return ansible.utils.shlex.shlex_split(s) + + +def optional_secret(value): + """ + Wrap `value` in :class:`mitogen.core.Secret` if it is not :data:`None`, + otherwise return :data:`None`. + """ + if value is not None: + return mitogen.core.Secret(value) + + +def first_true(it, default=None): + """ + Return the first truthy element from `it`. + """ + for elem in it: + if elem: + return elem + return default + + +class Spec(with_metaclass(abc.ABCMeta, object)): + """ + A source for variables that comprise a connection configuration. + """ + + @abc.abstractmethod + def transport(self): + """ + The name of the Ansible plug-in implementing the connection. + """ + + @abc.abstractmethod + def inventory_name(self): + """ + The name of the target being connected to as it appears in Ansible's + inventory. + """ + + @abc.abstractmethod + def remote_addr(self): + """ + The network address of the target, or for container and other special + targets, some other unique identifier. + """ + + @abc.abstractmethod + def remote_user(self): + """ + The username of the login account on the target. + """ + + @abc.abstractmethod + def password(self): + """ + The password of the login account on the target. + """ + + @abc.abstractmethod + def become(self): + """ + :data:`True` if privilege escalation should be active. + """ + + @abc.abstractmethod + def become_method(self): + """ + The name of the Ansible become method to use. + """ + + @abc.abstractmethod + def become_user(self): + """ + The username of the target account for become. + """ + + @abc.abstractmethod + def become_pass(self): + """ + The password of the target account for become. + """ + + @abc.abstractmethod + def port(self): + """ + The port of the login service on the target machine. + """ + + @abc.abstractmethod + def python_path(self): + """ + Path to the Python interpreter on the target machine. + """ + + @abc.abstractmethod + def private_key_file(self): + """ + Path to the SSH private key file to use to login. + """ + + @abc.abstractmethod + def ssh_executable(self): + """ + Path to the SSH executable. + """ + + @abc.abstractmethod + def timeout(self): + """ + The generic timeout for all connections. + """ + + @abc.abstractmethod + def ansible_ssh_timeout(self): + """ + The SSH-specific timeout for a connection. + """ + + @abc.abstractmethod + def ssh_args(self): + """ + The list of additional arguments that should be included in an SSH + invocation. + """ + + @abc.abstractmethod + def become_exe(self): + """ + The path to the executable implementing the become method on the remote + machine. + """ + + @abc.abstractmethod + def sudo_args(self): + """ + The list of additional arguments that should be included in a become + invocation. + """ + # TODO: split out into sudo_args/become_args. + + @abc.abstractmethod + def mitogen_via(self): + """ + The value of the mitogen_via= variable for this connection. Indicates + the connection should be established via an intermediary. + """ + + @abc.abstractmethod + def mitogen_kind(self): + """ + The type of container to use with the "setns" transport. + """ + + @abc.abstractmethod + def mitogen_mask_remote_name(self): + """ + Specifies whether to set a fixed "remote_name" field. The remote_name + is the suffix of `argv[0]` for remote interpreters. By default it + includes identifying information from the local process, which may be + undesirable in some circumstances. + """ + + @abc.abstractmethod + def mitogen_docker_path(self): + """ + The path to the "docker" program for the 'docker' transport. + """ + + @abc.abstractmethod + def mitogen_kubectl_path(self): + """ + The path to the "kubectl" program for the 'docker' transport. + """ + + @abc.abstractmethod + def mitogen_lxc_path(self): + """ + The path to the "lxc" program for the 'lxd' transport. + """ + + @abc.abstractmethod + def mitogen_lxc_attach_path(self): + """ + The path to the "lxc-attach" program for the 'lxc' transport. + """ + + @abc.abstractmethod + def mitogen_lxc_info_path(self): + """ + The path to the "lxc-info" program for the 'lxc' transport. + """ + + @abc.abstractmethod + def mitogen_machinectl_path(self): + """ + The path to the "machinectl" program for the 'setns' transport. + """ + + @abc.abstractmethod + def mitogen_ssh_debug_level(self): + """ + The SSH debug level. + """ + + @abc.abstractmethod + def mitogen_ssh_compression(self): + """ + Whether SSH compression is enabled. + """ + + @abc.abstractmethod + def extra_args(self): + """ + Connection-specific arguments. + """ + + +class PlayContextSpec(Spec): + """ + PlayContextSpec takes almost all its information as-is from Ansible's + PlayContext. It is used for normal connections and delegate_to connections, + and should always be accurate. + """ + def __init__(self, connection, play_context, transport, inventory_name): + self._connection = connection + self._play_context = play_context + self._transport = transport + self._inventory_name = inventory_name + + def transport(self): + return self._transport + + def inventory_name(self): + return self._inventory_name + + def remote_addr(self): + return self._play_context.remote_addr + + def remote_user(self): + return self._play_context.remote_user + + def become(self): + return self._play_context.become + + def become_method(self): + return self._play_context.become_method + + def become_user(self): + return self._play_context.become_user + + def become_pass(self): + return optional_secret(self._play_context.become_pass) + + def password(self): + return optional_secret(self._play_context.password) + + def port(self): + return self._play_context.port + + def python_path(self): + s = self._connection.get_task_var('ansible_python_interpreter') + # #511, #536: executor/module_common.py::_get_shebang() hard-wires + # "/usr/bin/python" as the default interpreter path if no other + # interpreter is specified. + return parse_python_path(s or '/usr/bin/python') + + def private_key_file(self): + return self._play_context.private_key_file + + def ssh_executable(self): + return self._play_context.ssh_executable + + def timeout(self): + return self._play_context.timeout + + def ansible_ssh_timeout(self): + return ( + self._connection.get_task_var('ansible_timeout') or + self._connection.get_task_var('ansible_ssh_timeout') or + self.timeout() + ) + + def ssh_args(self): + return [ + mitogen.core.to_text(term) + for s in ( + getattr(self._play_context, 'ssh_args', ''), + getattr(self._play_context, 'ssh_common_args', ''), + getattr(self._play_context, 'ssh_extra_args', '') + ) + for term in ansible.utils.shlex.shlex_split(s or '') + ] + + def become_exe(self): + return self._play_context.become_exe + + def sudo_args(self): + return [ + mitogen.core.to_text(term) + for term in ansible.utils.shlex.shlex_split( + first_true(( + self._play_context.become_flags, + self._play_context.sudo_flags, + # Ansible 2.3. + getattr(C, 'DEFAULT_BECOME_FLAGS', ''), + getattr(C, 'DEFAULT_SUDO_FLAGS', '') + ), default='') + ) + ] + + def mitogen_via(self): + return self._connection.get_task_var('mitogen_via') + + def mitogen_kind(self): + return self._connection.get_task_var('mitogen_kind') + + def mitogen_mask_remote_name(self): + return self._connection.get_task_var('mitogen_mask_remote_name') + + def mitogen_docker_path(self): + return self._connection.get_task_var('mitogen_docker_path') + + def mitogen_kubectl_path(self): + return self._connection.get_task_var('mitogen_kubectl_path') + + def mitogen_lxc_path(self): + return self._connection.get_task_var('mitogen_lxc_path') + + def mitogen_lxc_attach_path(self): + return self._connection.get_task_var('mitogen_lxc_attach_path') + + def mitogen_lxc_info_path(self): + return self._connection.get_task_var('mitogen_lxc_info_path') + + def mitogen_machinectl_path(self): + return self._connection.get_task_var('mitogen_machinectl_path') + + def mitogen_ssh_debug_level(self): + return self._connection.get_task_var('mitogen_ssh_debug_level') + + def mitogen_ssh_compression(self): + return self._connection.get_task_var('mitogen_ssh_compression') + + def extra_args(self): + return self._connection.get_extra_args() + + +class MitogenViaSpec(Spec): + """ + MitogenViaSpec takes most of its information from the HostVars of the + running task. HostVars is a lightweight wrapper around VariableManager, so + it is better to say that VariableManager.get_vars() is the ultimate source + of MitogenViaSpec's information. + + Due to this, mitogen_via= hosts must have all their configuration + information represented as host and group variables. We cannot use any + per-task configuration, as all that data belongs to the real target host. + + Ansible uses all kinds of strange historical logic for calculating + variables, including making their precedence configurable. MitogenViaSpec + must ultimately reimplement all of that logic. It is likely that if you are + having a configruation problem with connection delegation, the answer to + your problem lies in the method implementations below! + """ + def __init__(self, inventory_name, host_vars, become_method, become_user, + play_context): + """ + :param str inventory_name: + The inventory name of the intermediary machine, i.e. not the target + machine. + :param dict host_vars: + The HostVars magic dictionary provided by Ansible in task_vars. + :param str become_method: + If the mitogen_via= spec included a become method, the method it + specifies. + :param str become_user: + If the mitogen_via= spec included a become user, the user it + specifies. + :param PlayContext play_context: + For some global values **only**, the PlayContext used to describe + the real target machine. Values from this object are **strictly + restricted** to values that are Ansible-global, e.g. the passwords + specified interactively. + """ + self._inventory_name = inventory_name + self._host_vars = host_vars + self._become_method = become_method + self._become_user = become_user + # Dangerous! You may find a variable you want in this object, but it's + # almost certainly for the wrong machine! + self._dangerous_play_context = play_context + + def transport(self): + return ( + self._host_vars.get('ansible_connection') or + C.DEFAULT_TRANSPORT + ) + + def inventory_name(self): + return self._inventory_name + + def remote_addr(self): + # play_context.py::MAGIC_VARIABLE_MAPPING + return ( + self._host_vars.get('ansible_ssh_host') or + self._host_vars.get('ansible_host') or + self._inventory_name + ) + + def remote_user(self): + return ( + self._host_vars.get('ansible_ssh_user') or + self._host_vars.get('ansible_user') or + C.DEFAULT_REMOTE_USER + ) + + def become(self): + return bool(self._become_user) + + def become_method(self): + return ( + self._become_method or + self._host_vars.get('ansible_become_method') or + C.DEFAULT_BECOME_METHOD + ) + + def become_user(self): + return self._become_user + + def become_pass(self): + return optional_secret( + self._host_vars.get('ansible_become_password') or + self._host_vars.get('ansible_become_pass') + ) + + def password(self): + return optional_secret( + self._host_vars.get('ansible_ssh_pass') or + self._host_vars.get('ansible_password') + ) + + def port(self): + return ( + self._host_vars.get('ansible_ssh_port') or + self._host_vars.get('ansible_port') or + C.DEFAULT_REMOTE_PORT + ) + + def python_path(self): + s = self._host_vars.get('ansible_python_interpreter') + # #511, #536: executor/module_common.py::_get_shebang() hard-wires + # "/usr/bin/python" as the default interpreter path if no other + # interpreter is specified. + return parse_python_path(s or '/usr/bin/python') + + def private_key_file(self): + # TODO: must come from PlayContext too. + return ( + self._host_vars.get('ansible_ssh_private_key_file') or + self._host_vars.get('ansible_private_key_file') or + C.DEFAULT_PRIVATE_KEY_FILE + ) + + def ssh_executable(self): + return ( + self._host_vars.get('ansible_ssh_executable') or + C.ANSIBLE_SSH_EXECUTABLE + ) + + def timeout(self): + # TODO: must come from PlayContext too. + return C.DEFAULT_TIMEOUT + + def ansible_ssh_timeout(self): + return ( + self._host_vars.get('ansible_timeout') or + self._host_vars.get('ansible_ssh_timeout') or + self.timeout() + ) + + def ssh_args(self): + return [ + mitogen.core.to_text(term) + for s in ( + ( + self._host_vars.get('ansible_ssh_args') or + getattr(C, 'ANSIBLE_SSH_ARGS', None) or + os.environ.get('ANSIBLE_SSH_ARGS') + # TODO: ini entry. older versions. + ), + ( + self._host_vars.get('ansible_ssh_common_args') or + os.environ.get('ANSIBLE_SSH_COMMON_ARGS') + # TODO: ini entry. + ), + ( + self._host_vars.get('ansible_ssh_extra_args') or + os.environ.get('ANSIBLE_SSH_EXTRA_ARGS') + # TODO: ini entry. + ), + ) + for term in ansible.utils.shlex.shlex_split(s) + if s + ] + + def become_exe(self): + return ( + self._host_vars.get('ansible_become_exe') or + C.DEFAULT_BECOME_EXE + ) + + def sudo_args(self): + return [ + mitogen.core.to_text(term) + for s in ( + self._host_vars.get('ansible_sudo_flags') or '', + self._host_vars.get('ansible_become_flags') or '', + ) + for term in ansible.utils.shlex.shlex_split(s) + ] + + def mitogen_via(self): + return self._host_vars.get('mitogen_via') + + def mitogen_kind(self): + return self._host_vars.get('mitogen_kind') + + def mitogen_mask_remote_name(self): + return self._host_vars.get('mitogen_mask_remote_name') + + def mitogen_docker_path(self): + return self._host_vars.get('mitogen_docker_path') + + def mitogen_kubectl_path(self): + return self._host_vars.get('mitogen_kubectl_path') + + def mitogen_lxc_path(self): + return self.host_vars.get('mitogen_lxc_path') + + def mitogen_lxc_attach_path(self): + return self._host_vars.get('mitogen_lxc_attach_path') + + def mitogen_lxc_info_path(self): + return self._host_vars.get('mitogen_lxc_info_path') + + def mitogen_machinectl_path(self): + return self._host_vars.get('mitogen_machinectl_path') + + def mitogen_ssh_debug_level(self): + return self._host_vars.get('mitogen_ssh_debug_level') + + def mitogen_ssh_compression(self): + return self._host_vars.get('mitogen_ssh_compression') + + def extra_args(self): + return [] # TODO diff --git a/mitogen-0.2.7/ansible_mitogen/transport_config.pyc b/mitogen-0.2.7/ansible_mitogen/transport_config.pyc new file mode 100644 index 0000000000000000000000000000000000000000..149aa3945e1765feaceb54571e1b4a3c7887dc1b GIT binary patch literal 27622 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen.egg-info/PKG-INFO b/mitogen-0.2.7/mitogen.egg-info/PKG-INFO new file mode 100644 index 000000000..f63ba91 --- /dev/null +++ b/mitogen-0.2.7/mitogen.egg-info/PKG-INFO @@ -0,0 +1,23 @@ +Metadata-Version: 1.1 +Name: mitogen +Version: 0.2.7 +Summary: Library for writing distributed self-replicating programs. +Home-page: https://github.com/dw/mitogen/ +Author: David Wilson +Author-email: UNKNOWN +License: New BSD +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Environment :: Console +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: System :: Distributed Computing +Classifier: Topic :: System :: Systems Administration diff --git a/mitogen-0.2.7/mitogen.egg-info/SOURCES.txt b/mitogen-0.2.7/mitogen.egg-info/SOURCES.txt new file mode 100644 index 000000000..c2abd33 --- /dev/null +++ b/mitogen-0.2.7/mitogen.egg-info/SOURCES.txt @@ -0,0 +1,78 @@ +LICENSE +MANIFEST.in +README.md +setup.cfg +setup.py +ansible_mitogen/__init__.py +ansible_mitogen/affinity.py +ansible_mitogen/connection.py +ansible_mitogen/loaders.py +ansible_mitogen/logging.py +ansible_mitogen/mixins.py +ansible_mitogen/module_finder.py +ansible_mitogen/parsing.py +ansible_mitogen/planner.py +ansible_mitogen/process.py +ansible_mitogen/runner.py +ansible_mitogen/services.py +ansible_mitogen/strategy.py +ansible_mitogen/target.py +ansible_mitogen/transport_config.py +ansible_mitogen/compat/__init__.py +ansible_mitogen/compat/simplejson/__init__.py +ansible_mitogen/compat/simplejson/decoder.py +ansible_mitogen/compat/simplejson/encoder.py +ansible_mitogen/compat/simplejson/scanner.py +ansible_mitogen/plugins/__init__.py +ansible_mitogen/plugins/action/__init__.py +ansible_mitogen/plugins/action/mitogen_get_stack.py +ansible_mitogen/plugins/connection/__init__.py +ansible_mitogen/plugins/connection/mitogen_doas.py +ansible_mitogen/plugins/connection/mitogen_docker.py +ansible_mitogen/plugins/connection/mitogen_jail.py +ansible_mitogen/plugins/connection/mitogen_kubectl.py +ansible_mitogen/plugins/connection/mitogen_local.py +ansible_mitogen/plugins/connection/mitogen_lxc.py +ansible_mitogen/plugins/connection/mitogen_lxd.py +ansible_mitogen/plugins/connection/mitogen_machinectl.py +ansible_mitogen/plugins/connection/mitogen_setns.py +ansible_mitogen/plugins/connection/mitogen_ssh.py +ansible_mitogen/plugins/connection/mitogen_su.py +ansible_mitogen/plugins/connection/mitogen_sudo.py +ansible_mitogen/plugins/strategy/__init__.py +ansible_mitogen/plugins/strategy/mitogen.py +ansible_mitogen/plugins/strategy/mitogen_free.py +ansible_mitogen/plugins/strategy/mitogen_host_pinned.py +ansible_mitogen/plugins/strategy/mitogen_linear.py +mitogen/__init__.py +mitogen/core.py +mitogen/debug.py +mitogen/doas.py +mitogen/docker.py +mitogen/fakessh.py +mitogen/fork.py +mitogen/jail.py +mitogen/kubectl.py +mitogen/lxc.py +mitogen/lxd.py +mitogen/master.py +mitogen/minify.py +mitogen/os_fork.py +mitogen/parent.py +mitogen/profiler.py +mitogen/select.py +mitogen/service.py +mitogen/setns.py +mitogen/ssh.py +mitogen/su.py +mitogen/sudo.py +mitogen/unix.py +mitogen/utils.py +mitogen.egg-info/PKG-INFO +mitogen.egg-info/SOURCES.txt +mitogen.egg-info/dependency_links.txt +mitogen.egg-info/not-zip-safe +mitogen.egg-info/top_level.txt +mitogen/compat/__init__.py +mitogen/compat/pkgutil.py +mitogen/compat/tokenize.py \ No newline at end of file diff --git a/mitogen-0.2.7/mitogen.egg-info/dependency_links.txt b/mitogen-0.2.7/mitogen.egg-info/dependency_links.txt new file mode 100644 index 000000000..8b13789 --- /dev/null +++ b/mitogen-0.2.7/mitogen.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/mitogen-0.2.7/mitogen.egg-info/not-zip-safe b/mitogen-0.2.7/mitogen.egg-info/not-zip-safe new file mode 100644 index 000000000..8b13789 --- /dev/null +++ b/mitogen-0.2.7/mitogen.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/mitogen-0.2.7/mitogen.egg-info/top_level.txt b/mitogen-0.2.7/mitogen.egg-info/top_level.txt new file mode 100644 index 000000000..2360b3f --- /dev/null +++ b/mitogen-0.2.7/mitogen.egg-info/top_level.txt @@ -0,0 +1,2 @@ +ansible_mitogen +mitogen diff --git a/mitogen-0.2.7/mitogen/__init__.py b/mitogen-0.2.7/mitogen/__init__.py new file mode 100644 index 000000000..47fe4d3 --- /dev/null +++ b/mitogen-0.2.7/mitogen/__init__.py @@ -0,0 +1,120 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +On the Mitogen master, this is imported from ``mitogen/__init__.py`` as would +be expected. On the slave, it is built dynamically during startup. +""" + + +#: Library version as a tuple. +__version__ = (0, 2, 7) + + +#: This is :data:`False` in slave contexts. Previously it was used to prevent +#: re-execution of :mod:`__main__` in single file programs, however that now +#: happens automatically. +is_master = True + + +#: This is `0` in a master, otherwise it is the master-assigned ID unique to +#: the slave context used for message routing. +context_id = 0 + + +#: This is :data:`None` in a master, otherwise it is the master-assigned ID +#: unique to the slave's parent context. +parent_id = None + + +#: This is an empty list in a master, otherwise it is a list of parent context +#: IDs ordered from most direct to least direct. +parent_ids = [] + + +import os +_default_profiling = os.environ.get('MITOGEN_PROFILING') is not None +del os + + +def main(log_level='INFO', profiling=_default_profiling): + """ + Convenience decorator primarily useful for writing discardable test + scripts. + + In the master process, when `func` is defined in the :mod:`__main__` + module, arranges for `func(router)` to be invoked immediately, with + :py:class:`mitogen.master.Router` construction and destruction handled just + as in :py:func:`mitogen.utils.run_with_router`. In slaves, this function + does nothing. + + :param str log_level: + Logging package level to configure via + :py:func:`mitogen.utils.log_to_file`. + + :param bool profiling: + If :py:data:`True`, equivalent to setting + :py:attr:`mitogen.master.Router.profiling` prior to router + construction. This causes ``/tmp`` files to be created everywhere at + the end of a successful run with :py:mod:`cProfile` output for every + thread. + + Example: + + :: + + import mitogen + import requests + + def get_url(url): + return requests.get(url).text + + @mitogen.main() + def main(router): + z = router.ssh(hostname='k3') + print(z.call(get_url, 'https://example.org/'))))) + + """ + + def wrapper(func): + if func.__module__ != '__main__': + return func + import mitogen.parent + import mitogen.utils + if profiling: + mitogen.core.enable_profiling() + mitogen.master.Router.profiling = profiling + utils.log_to_file(level=log_level) + return mitogen.core._profile_hook( + 'app.main', + utils.run_with_router, + func, + ) + return wrapper diff --git a/mitogen-0.2.7/mitogen/__init__.pyc b/mitogen-0.2.7/mitogen/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dda1609737cf4a8544c732d4a31dded28188d8d5 GIT binary patch literal 2505 zcmb_dO>Y}F5S_Jb*_NG;CPk4W(TiokSrzS}fRGdcf+R4I*nypsT9ivKM=Q!JazoC_ zQIMQmG&%S0_0-?cAK+rRYbgoPQ%f7GB{}X^Y@M3pXFb_1V-ziH(=C-0${WWI!?~jTdL5uAgb6 zXdD}hTA0RqICFZKNjX!-N|{BVnbUQN2SRzTY=I#L5)t~|)r5XO6Xe7h3S;N)oWW)@ z)J74Oivw|CXcBRT)nb^JDum(n6{bya=}CmiMDA=LU*}|;70PO%aW$VPt4q{kTDQkl zfum+rKFtcWCR2vg*H*G?*~FNoOi0Xw(54oD#hzkt+F=mZ(ZtxIC2&|NuVx~U7o{s? ziE}K6(U`|`R}_2`mCDamfg)}*54p2rQ`8>C+^D7bzlF)jRg!uGk zQt^r_Ggo!MAhK*gvntC3A0f1fkb6{Iu{cM+xR{_vp_&}!!NrbN3pW-@ggWQMwPX2pyX^6G zje$b*9S@CTQ6S0Mm;Kt8`z!u=c%<6fNPN?;jKJFX3GR(+*76;SwPE zn8f)8OuHaA!2(ch2f70v0WKj)TJs+F4Bp#e7ndD4?Qa0U5p4m$weUp6C0>tZ6?jio zRm0;h01$T%DH~~$l5PUPHQAN&Xo+rovJ@pDN_Fl$(ro}h%i<}QxldiTkF6)ZHkw4+ z0I;&*h#IUNj%`)D(OV0Ka^l=M-2#9wL@4Th3?pg}_m(#3W`~_ErRIcP^tls-2LLdb zxET(nrCJOMLj#Rf=@$3TRKP)tasRvY`}D`_*{AI>db;qcs?bjv@E*e^x03Z_H`z_P z$&KVray!{hI!QN)F8kccPVVr7!#LqAX*RfZtv3F9Xqm@~ei2v#0IPo7eS^m?v%n&>8u6MiLjriBuP3|R~ F&Ob0lvi<-7 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/__init__.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7945eae0141e54e6bae0cdc2d2cfe9bfff5c856 GIT binary patch literal 2198 zcmZ`)OK;pZ5GJ+qz8t$w+5$bYz1Ryl+M)$oV37nt(f9f#2p^SVTR%j-r=F_#$D*ndms9I<0Izb*&rVL(&b$k zOatEI{kJ$C^6(gq1{c2%j}&n;AkT%<87Pu7>%i<2Hxrile_m+gfRm}wIZ2Yd**B(C zDB&oLie-`zX34oOB_Eywf!7734tPXbXe-$Q_K9#F@3a)s5x!I`7b%l+NqA|5%7}H$ zxUz_bqs}LP;!lCU^$ejhy&|t9GeSkYMhH+)-VZ4wX)8XsS{cera0lS9?zKK5;&Qc?Qxm) zUVBu3nl)(YgtU<<74@cR<6{zeLh1~#ZBltHZ>e^UYp_IVHxnvr37iznu$)+Dh}0RC zumCx!H+y-ev&=`LVCfmlfYi&Lhg7Sn$VvlbA=qmB|GDXrou*SEA-QSyRBP$OHWgB+ zY;E{pT7z-s*d#eIB_#U+_24@Gi!=&@E)y2F%Znr-{s^{Bgwz1@wF(O`%kvo+AgswzFTTt{aiXV$5nHA{F@1uY zQq|c~pRZ2D^hJGsNQf?7QMx+yYS5KuZf1b-mb%BUSzbt(G!-yupI(~wLE5gd(r%{> zyeh#u+vs`WFeMo{S{k`m8aZ0?@5O*CjasQi8Mt~^L--M4 z^d4X0`Nq6Ud#*npyhptA8^)+!AM()k=bM-4W9Jx+HV&))RY$2i=m=LEb-7&hSy9yF zjBZs!N}EnYX}o)NiuR2ztQK>7YhSLs`Io4k&@O+%>$>!poGbMsog zRa>np(3#d}@n`>O8Ka;IeUGSmt@Tk*4OX4N`0wK%=%0Jzna*K6muxxCgd1}xq+Z@V zWfsP*-raAb@1h^CM%TYsRqx!eq5w1Z5%|i-1P^hD`?!xoJj6qM3wLpVLwxb2PnhVQ z)G3qpLDcG*+X$gi$Lt`4YLASMWiuOGoAn=|D)bPKyrs}v+TGqZUwHM+sz)i;DW%mW zr3)}tXhmr}5SF%#4pXgsZd1YIK@$}JI$XWlD$us-fm#ToRn^JB#dj!&DJ!L0hp0AK gyLatYJJ-M0ME}7z-a$VVhG93{sDGV4-oZiVUqob)UH||9 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/core.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/core.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb7492e46130c0211389d59b2ff0be25adc0fd9d GIT binary patch literal 102045 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

^=nb^+;gQ}B z=Q~)Sz1o3j@55*rV369;R+P@%k}C4Upydah@Eg!s461@oG3x4+!yR5L*LbyDwdvXz zHq3<)An#FZC@Q%yQW9mte_$r6>C^?U*V9bqKcYjuJj#7#>?GZ=Qs6x!st`MRc2mad zXICy5F}m&bdWHpiKI20BBmp-@Z+T(G?Rb7KYPjdzcG%x?!w$rEH&$L-u6peaQVx(r z4_QaviKPliKS0tt3IF}R3K>H-Duj+IzH zV||Hn7Tiquww>5rXWJ1xak{0%>6R0_V;^VRJg)3j_nELxvF%b)Qk{eg`y(Xx2xo69 zuI)`HrG387@mp-|QQNu9Q(78MEkuol=&ZXC(V7RT`3jK^ih*ay%!v9%?K*JkOrv8m;3 z8dg!WX))iZs*|w=PF?T~7P+x4tR4O)j~)C5k#5PKf0VJ|BW*8$?kAhJn~(pjyXt{& z?GNI5i4Ze!Vd@EN))Y8>I*2r&I#2mslI{)0*vGQbGk%g+IsG<F?{gu z%e}De_2}w7wl1yd7FwIpS~^9y5LAvy#^>0y>evL791tSG<#7OCvZ)uz}H~mc+4q`pf`uIR&ea#Kq z?SUd5(=&trmL&eyp&T6>CVm+^7CC{1dJV?{+8=c+VOsmM%wpRd1#9?EFA!twu7oSl zUSxdn>Xu8o9wV|tLF~SA^YY`*L7C-iv>&*cmdFh|L>)3RMNHYs88?jArMm4$lEgVY z7i$uc-5AAc1&ZG&R)cmNs)l=WT~bp@#sd{d;fAYS+14HQZhKpi>$&}&=LchLoN>Ki zt00TSA#aAaq-bPXEr)Qr`Vf!~xJ4sYr92z~TT&8K;;;op-f{-D%&GAjSDzkTW?-Hi z`{1!V#}e?gGo*4WLo$7%Y`9iy=_d72x70&GAdc+G&BE6M z1CiRkxRW|P*_qS>r?pnzM+b8!zTBzuDxcvBCoioPqsV0ntb5MD0(Sz$G2brVvHQmp zShqRm3F7u~B5u@}TG5{|KGnk8xH2uBg@~SyXn+x+FUA$r``>K;=slrzp6EXiRGkQQ2x*vtm@7XT3O9XIH#I zyx#Kln4Z^nG|X==tQ15yk_N?jF&OE|&Oo5ST(<`brYM?WX7;K(X~ndtDg^rGO3N2% zO%J-|iwIU8(A^3YO47RvksMq1X#Zpox6odw{CH~{+lVv6$}?QS{*>bt{FDdh#QGlt4tfwA%QV^G|jaUXkZgH$+m zA}p)0Cf*964_`8HPx>#0qonFW^`QgRI=$0Fg5 zyOuEwMpsUS8JdH(x=)CCO~&=Iif|#u8%ZE#y;lm-m>Ogc=~W}D=wM^s!E#|?uv|yD z8e+k~#^Qy)IYxlG5NNQ)60NGgS?B3-BZszPZ$m~cX#l*=5A1Pn{H~wMw~Cj!O1`z8 zkB#@N2%X%c-x=MnzUR-4_4-|-0Fsd*5aft zizcHpfX2XZhrms-6z~QZ8LB?du4$YyVkbOiUFSU8w{u$apO!&c*QMKmhlY?*TlxK%NRA-- z;tk?((~EtmF1EC+>xLR-o&^FuyAnnXc}KQy$rVT$K8^*f@viK2AYg>SWZPgW)=4`n zTn}CdnnD%LbwahVLa@5!ZVsZDq|ZD3p5ON4Y!JeY*Ht)JTX&;yQ)-FG-IQJsX?1%v zXs_dtm{v1Q9hz>0L5sR*kZuY_@lvN9#J#j~xwUxd=H-j&+_@-{3hX+GoVc(|7iyO; zwk}+~cImZC7XZB-k(N3=?=F*kIvvTl)$WB@8JJoOcAoEUD z-#jwVd51V--)R4E&R2V3n~cPpp^y#uKWJB`K_H~+ZTZ2PVG(-Lh4!B?jvu*gZxG3R z3B#zNq$gIuY!!QT8(MaP6w;YeLmAt7ZJAbY#W159152fXuyN_!;<+nJE3Wb&%7W(% zTpA_;W4w-@sI8o-DF*9mZx(lT(CJ786esVjdxIz@#Y9Pc0p!?h<<)HKuBKKPrL}9V zrE5#)uU@mBjNcq`Kr3%^5HDz;*~V{g>dcWJNB*x4%? zg|iix!}Cx;3$j`PTP0R^N;s!jT)eErPCau}0Ip;pa=8Fyr$t6P&}yk^9kZ_r^Bwl`$_r_<+!YfDQvE-hcW`OS6R z_xtisr{n8au3uWo{OGi9Z^YB>CV9Vw9e3rlzSg?s^#)^Ix0LjvFsQF&rstK>JHZ^A z-E;{v>x(WmLk$;_DViYOw}5FWh`aWJ!giInmg_hP!xzuMmG!mxUF1&a^})F|C;EX~i64vN5TbNQ!IS zx|No$T)TR0F)azXI#|b4fRK*km1+ezuTM8o_I zAkX6v?T%U8;xqg>pW#RNJh%A$Flg)nood-nzrgH~rx_X^vwG$R4e~TAJdCvM^XPS7 zbOt@GR{1!*Z>9479cS!ZjtZjh0-(F@md zK?&3w+G5g%|7`Ai%-Qh!A$r5qyU#W(^&kW~&}{AYny_wJUxSJHINP@3N@Br1X~#}j zs~S%wtXtiy?Xw-uq0+4GO$#Ts_Gb1OoFkz9GQXBYd2cqJ+dH&x94Bziw9Q%^y8ndj z%_B@uUrH&s14^Y81Z>^>ujyI z5Y@CE`*2$E+VCTiX%+k4>idFNqO`mw=fyI0@IYENN<;AW zX&J&4umv&4X$5Vd#j8LGFXN6GW3-N9uf360j8}Hu)}A(29Hma*i`OUdN2R6V@;?EC zKyYNd$}K(zp9y}1&+$2a9L8&F&Kf>&z~OJ0=f||oxsnptMQfP%tbMj&tK$j7c5@ax z-O^rp%&(a*)hmj8&53pV3z*t3VrmtGQqtKmoRT^?U3E*(u&iV|RAOR&>Q@+Jb5q|P^ zf%>Z3TA@nnQ=+Zo_9cp*c)w-B9-2pAzH;;0;-#h5^&8hNU%7gzb@}C+!;g&Ny(sqL zsIhRm?~6MA`U258@fLjv!rSy0Q5MlRGs@w=C*%q*hY7&~?2zG@5Z|L@H0Z@fOOk1y z!96S4?|JZ|T-Cg@LEhladab?+flpfNVYmV7i43yqX5sY>ey~Q&mZqt?6B?Hv#8P!U z#1utth|n7uOB&j3zt@9qWu$x9&dF{NQ6vkTHDb92dSjG6zE-DGi2Yh+c-D079yBOg79`)&7df?K{0F{rBm(`u#x|KxTZi->s5gb-Jldm5_Kx{vV)z9M?=a{!y6NAk@s7V|( zXWB&L^f&dFaO1C|yV5dUu0^4W&8^dYSo4Iqr-Yl#&z5{O|d-x(9rYodsc2{)Z4B1n& z6Hbg`94mhnp>5g*w}KxsQAP6_9AOJu9o%YBm|ZAv^W^Af%{{uO>-o?f^$#(0LY?LX z3`+6=+!sdW1L4uaV472eDrZn(>y=xvcP?Wx8yXWX`Kl2$Q<;x2M~ zejN97w2KORZg@*7cRjoft{PE3e!CD7{m9imbcETYlATaVG)(Q0n$mOo10^3f4%PZ@ zb;gat!v753Oh9;9Oi?ckH=rc;V^@Um^U|z#AaBdeGdmh(=Rtur)?5o+@3yz)hKSYd zEAZq+To1{x@(ye_n(xky!egG!j}Tbz0Ku`uPAtLSw%}p{<0l~@8)%Q*Edb_y6~tulZZQNk5`l0rS9H*CQ2~S2V04eeevC4b~@nVNZ zcD_OPi6M46!`pd+p^tW1iH`DTsOBY|ca4D@A@N7=uwiZ+g#!FpK|6ZIzI!pI! zXVNHM!wMk$34TFXfg1eQPmQj^*!@fO*@L{F(`f=%cvOT!{bS4l(RitI{q?7wQU3(r z|0%v#g&*}1^OidB2yV_?F~NXvd>q-Mf=B&FjImd&e}xFg?KtObHiL1y<&Fcwdz;a$^O>xHWxfDgV5X<4JC z0}aeIx}Vl$D@R*=cwf+%7e~ZVx^hyu;+VK^mm?#H`l>iCPWZg}K;ZbS z`G7bnPVKVhgW`RZVS+c`Bi~^@h3BNN!LuW`apb&Tpy@l+FNJpiUd3Rm9XO--ng}fw9+vfOi-hf`=3Xc`W3x6khC!Sc) z776~g_7<%3v}}Astn~1RKfIop`qvnf#w;`)&4P|oErnpruVts7@!_d_NSUK~%d|H( zwj8M)iL&#{RpJQJuVdRJzrCP#6#v zjab_wM$7>ye#Y8{+bN8LG_aYg=WbqE`j8G*Qs=NnIuwh164EL~FZsb*%7ckfrH6_+ zk>la7Vua`zN|oV>yT?d(-RY@DH~RF61qKT0b{<*5=+<{mU=8< zU5mas8{i69*QRf!4NF~&9r|3}u+;UqM4u}gmU=5LFSFpWgp2BirQ*aAQ}ma0E6Xh5 zqK11COTgy$R$N_X@sywt{2lvEE@qcmeQtQ$IQZZk63r>{VgOMXPMu=j8+4sMk`=qK zSmR|J6phK+*Wo&v%z^!c6Ho5lAw`4Y4m8IC7$ESZ0AU?nim;}%(n5S$JEF}?r(3Pn zf!~Y$pw&{}0sZ8ClaUe&>WKw~lTD#7h<3PV;|d|q`bfjWK07}D3cnuYXg1RtA>+RW zcmV&&>?)rp1x=Y(=^vCe_*drh^v~hLV>!HxG?AlQh-pB%1kX^WX&vGQ@ekfiM!WDb zcrACrGMEthhpko|lKvuh^#q%~=C`%2DM!Ypi7U&kiJQd)gEl>+zrtgH`!lg$>F@~MK{fI87sKSdDN zVTtu6zHM(Efm=QV_qVOYhPfbk#$kwl+o-AQW3C1^9QtFBe+MB=*%LbLRH9w^e?4EA zQ6|7hqS;aa9AW}M1J59~`jpfyVZmLIGUHZ1I;Ix=fi z(j*pwn2Cc7o)xoHe~4WL0cKM*ZjrHbhM(jGG@JW!3Iytdf3(HT5qUE@7V|v|;-YUlxL}ct$joit*W%Y@Kb*K6-Ce~g}b9=!|JiRv~KyZjk|78?O zT?jY(NW$R}lewjtT_hsW+&FQfz%tMp=>5si7vPe=|nE9OT~kn!8-7wr;d?AiH4 zCLvC^+RZ#f_3M&P&Qe7d6mvSX8wyx;9n%wsP;z8|r!z%RzNx$iye1C!fpB9+q9-vv zm}|LMK+{HZh&%8*-*BF0Hd*7EWxhw_SuTLwBpi{NNcj{&m2HU;Z2W*u9HG|&_J>bp zNQ5q&uAe1u15Z&CVnE`|P^wTTsFG)0klE-f2_2;9Hb z|A9B~v1y4GB(<+zTU1}s_>ay9@>J0s2eI90i3ay4I44j*vRP;&H1l2NN37xO*d)}y zL1W+{#>jBaha+efe)V0x!_`BG+U)W@8%S=SVm5<^6V@fI%!Z|&jjh<~RuIb>)aY{+ zpGn(lXX%Szq$DFneTw3dG{Q#|q7M7Q@)YujdKF)Rn(sl{rdx2|yiMfo0Xt7!RO82B zDYAw~v+0i9cQeNhuoO&XEC+PyF$?E_7vLI?bDdZUGqmx(=RkW8905y0T<(@Os_M&e z1@`70zU{UPD-trts_ZkZ&q6$64olc%7TW&;<@-x$J(f=A2Fn4 ze($jHS}Vs#gq@UktT!_f!qv~mM-w|fwqvQEPwY3jCMw={jbfWjBa@vPq8(>)p}VJ@ z(m)d6)}4odM{Zw9WPBT?2|AC~Y8dv&IaaUk_`$l;$v7xt!p<0%x1!xqcN-m#NDLGl zHPoYpFA!czM*T2|OlpnMfe;fF6?)h4F@Q>qAScY2d^$WIafx}ba51RjEaxqcMDE_* z_=@3;Ry1PJUL-OuniHI58*RNc7>FT8$t(RUscVlr`{eQUOU*ek63q7Hh@v-(PxMWSz zS+MklPPAUpTDKA%nzUA&9j)*DP1tv)vI(gJNpUs1{C*ra;{9jmfn%())_lS~V^ztK zV200Ia4B%CLh#QXA453#A%qJ$4qsOb*sKebV*|mq;4tg@J@x2O*u=$ z_nWhRdO@9W@BY_lEQmTWnO{dVNWG$XhL5$n!@^gvo_g^SOnxxdp-dEum_D|Oaf{E7 zX*P6D4GL3Os#`a2*57-Oy7!*lr+n3LQJM~`Dh5V0p{iA7Qt9Y?s4}!*M?xIng7o`+ zsp2gSV!wx5{v+Oq`jpTVW!ad*Cjg+D!y6^BDi~?{M0y-ht}lR`U~8&nN54u=$+4>z zeYL8!s$I3KPWAXq&EhkanZvac4zF2`Rjt+@p%%u}tGSLD`B4fTGt{e-8PA$!Q4v+j zb~PnxyR2Ci(_)755Rur9>{rmWcz-2BhWfi!ZIahnjLesTnB=x80MRqFn&h;A!l4O; zAWtCi7X|9_N1Uul{+~!5m&ZmL1eT2h{=j2gRGw-rUYDEb?UoFDlXLU7ucSNZYh2dp zg}04na#_mExvWs;DVcZLh(n_ux|iHYvoV<66r%nAa)B? zR`L8r8O4a_gzu$hYVO@sPK2Q(%H@9WEC>~DF{u7pg{d~JWJ7J9?>UW_zOmW^bW|~6SMHaj0uQshys911a6ZHIqN!mrMTR! zP)KuCIByq0m!;v8Ia`YU+;JJM@IaoRApp?R3#!qmFDXc*bxzo*J~cr$QU|z&rj2Zl zfuR_5v}s+mUzA2*#c8G`Il&og7mB0Wz}MwFT862Zhu1x}}hCj#hf+96p>e3r~%Vt!=K*#X%?tA$%cqF1r>9y6 zF{!68KU0=%9o|h?%E;~H#WqS`K_2zBf;3*~kmi3>Q$tqK455tB%PfR^RD|-hM+sho zfY=&CUlCr1T(1*JkV$y!BYRpjLf;Fi?XuU4pzj$3S#M)A@;cf(PiD)Fehlw9f=!qG zV67(~&(tRwbE=syc>Gb?F~q^&xPIaBp1(nP%Yw+GFeT`}2W_3D053SuXw6F-nV%8O zrn5_ATXaBrw3Zib1?`OII%rEXo<>WVaL_C^CA$_wFPDwJm?|kyRk+!>$L{IO3WYG6 z1^nkN5x`K&j7d7@ce0h$7K0#kdttC9mAg7Z(g2M}iAj5tfwa*yjYYa>^{-C}*>epe zY!;qQVu6?N1%Y6mbfd647i*LCqABLm&C6}DMtI>bSQDcB!0V%IT5RnwSi}>q1+~OV zN{O8~Nm=l>%Q+)wE!E4Z)d`5?rsY2697!t%PLZ|>ngHxkIt4E-@_VnBHQs}GmX;_- zb6Rfo$N^@0B;!ar-3o)2S$iYt7EdaqKU6N>9>N=Po=hT8(43zG0@V)3Wec_}5Nw3k z;J@MF3{MqoB+OQL;qN!3-t*b>BlH`Bo#yrqW+Fhpe`%D4W^Ejt8aMZL1c{^M4&1mu zAJ{ySWqy^M$h9#tH?7u(npW$nrqz0S zV!@H`S>8c%^8b3I>(Tk?s|T-f!J)u!416b1U+(=C%Mca3j8(VLb!#F_S*<$$+Q0-k znuy2ZP|~3r30lj}bi+M|!VHi(HJZybQ1GxttU09`-=HM7I7}nL9a1Le?CQ#CbDmqi zIAf4`9U)6TwTp5GXH9@+_7bv_jkC2G2Jw1t3oKly;?sIS^#ZeHFQBBJACQ2H0F%>? zjz8Im@5GND@El&cQ7xOG4V^E1^kwca{B&V>!0W{fYlEPhM3>nMGoha~p*^5~BPo5% zkm5!Zt)E^GqZl5pAA1zfb`ab3=-p#dvVF__*aTTOd5g0C@?FSYx;ved(}2Rn5+g%! z`&J=mpRV8C3Kn}aS_2is&3EqxUgRo$dNW!pp07K^=@BMK%bQzB)Bi3QA)U&l{jcTc z=IF?g%`cD1xVcha=RyhRGZ{DGMy^H80qrr$x+T>}*j@=)XEXEG@K(NVP_9z)(TsZ- zNq1uylkvn++NAYSS7!o>NWqb}W(XVw7{Eb{c)bys?Z|>^jNo>JXCm_<1*#Mp)%Sxw z*!@AAmV+?%J6oABOa_w)VR(Tv_V=-P+B*rUhRb|-ECZC$?sS2Z>`IYKilBa6GS2aT z76PU2CyNP`n0g^i${j2Qkh70LrzH zHXW&NAijfAnSC%*1LAd$lszy)j3^Z_LHD-KLyiO& zBL;|b@Y0Ug^JV-bgpdZkubjzvdF_Q_)O{{K@$EHL#-eZ3r=ZyV9j%cbQJ3~+9d_m?;K^)@ev~K7~fn*YE-{EZQ7$|iY`425< zhPEADe=aU<)UxVx+LIEtL*qo@bgQbF*s7U0I{qf1SPJjRD5YQSN!iZ<7`V7tx;S(_ zr4Z|SHw5PkYNUrwFZALp8|XL=KTwFv;);>^GRqBMQsx~U?2(coGGpQ~xi)kQBw&q5rNajo zRAWuXr_FAB47MOT+8vUSv3rvZ0bK_l>^1T6R z;t9OX;_V=cgXaDXP7#^qj~wF`KgQ=cna=odCdYPbGS2y3;5iT|Xm5yY-#LRED9F#h z4VR>24vYClO+9QX-tlGj8cR&7ER(Io8orgU?dx#d23H!%_JBga?^@R^ox)v7Kt`~B z-kuKD$=VnNWpYc=D%k?tAjci8D$M=wLY*NOAn$&wHS1)N6(ybGfJnorgrM(30PNDz z<+{Ce^Je|#1#@m9whcm(w%-bnihG^mIFA$%$r zio#X~2m4*@G4jY(k2__@axBJcb7UX0tM^rFj#Ztm)@l#Xy<=KAFb|}agC=xLD>rpl zPI_$4=V<;$-h_alX<5x4lBz=2dg|~bm-Bn-s zNGw2g>+%p(Dq&}iGNmmrezYe0XFm@Z&7w!o5IV}EM=2vP5Ra+rfL!gHCmp zrzNKifK#$WdLI;c0^JM)N4&6;CZyN3eon{rjaX>Ff|Ko0Y!NBXeu@`OVtzT+`ufPj`b0G%BvUFJtfk?_R9w@eqIb^rR1<9KEmo3t*u=|r=x#gWc!h#v$3(l$gyFBmxd2HGE;^H`Vc z$i_utlnzF(+cXl5RbaKa2j2y(^F+~qjSpwD{)ralWCALmd|}XeQ87d>%KnG}>5ysT z^g?t7o+;T15eB3cI+#nx^`|kK%;%vxwjPDgL*+p9OIWtB;uK3=V9%#GWA~#6ki6== z!)s2}p#Vy~{$1g4G!bjw_lsY2xqO>fK@=B$Zjln9i%QPz;XuP8J)IpM%)aiPD zFSUDj+ISObS!eQTmN)&t?`)-&FrvhXX;}|bXH}}8KB?c9X(^I%5T!O6w4y=kL<13~ zr9tfXqO@v4A0qYt!dO<;a%*abYAu~=XLUshs@eppCc$NH;iqIY@Ga2hRqpGME)vM$ zpr`$~`==4Iov>CVE+-`<2Iy8#;5xEzR0PvSJ`>ie!7+H``?QZ=XXt%4vDKFowl^p2 z#3nak2i{BZp}qOU7Nvb_jdkECI4hjz5ifaIYhMUWl$W#@B=VyV58ugB?PSG2#|v$K z9sX1GBWX2@TuO`N6)!EeE?ir>d1>ipT5TC;zjR738-jkEt6_U1Ewv!}rVbLtsvWHC z{JE>Io_l?{b^hwP3ol*0vV2qR!aHnU?`10wVab2V^OUAFDo_IbP?VNiZE*OA)}lDF z7Bq?aCui96SJNus3-NsEJO$vQw23ByDl5d2<7(WjWGhl_8Y7Kp1eN4idjnWRrwh;_ z$@6^PndkF%nJn-#mR9E@UBK{gcI*nI4o(a&{LS&IPL8Ucgv<9vSv{%CVriVSi=tQ- zie{kzY{x-{&4fSCL}{7T%igo2N(qQ{MX@eY&eC$2Gkhj@TB5V5gC8F=Lc4{{NRG8`YtShL0wQ`8MKI#@F;RSpL zqao6Om_M#P52yzizVHqJ2d$Ucuu%ZUB(!r`;WQR+*cBX^}10pH&g0gj6y$6yE z`8~oYR(I4RP@*1!vK1R){ZU>YuU4>>v}iu&7piH+d#(0PPTD%!PephdZLHg3>%O3*9C;@0#y}5$?+I6cePrn z?Qix??7Rt<2OCwN($N5vPx=LHBZWf!1GGeN1z+B@iZ);~fksy6WcI_mqCAgg;e-^H zr#tx>EF?^9sP|E}5ZgCrIo1)lFFly;N8^A8mvZz;Y@9)(5#;EY#ay7&%^XOauwve_ z-r>W!*bYxq4mdkuZ;_K-Tz-q~@Gr8s@)i>gZG^q;Y?tC{cWSSOxSZ{BVyifwPRgPr z7_!4{OsSY!DQB&qm$z7q%skEs)SIHp+}oAJR(~ylZ`v%{5|w>xqoV#AJzE`pHeua4 zF|~&3Ex!{VqU7<2-beEIIh`V2{SGXwTP`xmy`?kE9S;8}KC)L5Gg(Wovf#AC;-ee2 zcQ_2t_%A;E4iE3A`)1$Lkp>L98{`R!`HE#S1kEj_6*UNeoNtm9WMqS21yP=MAg99$ zeaZ021*EGaABJeKs<#pnEh=5;tnjyG?g>E7<2b#;`|+5j>_jgz(bHW}#DU~zG{d~# zT$1*0G6DLpWrx;b!$))28ky?|zRmKu4<9II>fQ4E9#t$R4P|d@_|VA1l+9D?-d9-QQSC%h90I@$Y2Vjt)5?SCrxT1uPfh zf~gFV1)str9O1V!{Fn~CwT|)Q$mZuDIL@&wa?GFMs0mTChQ|(C;^EI|s4*O(QUB(Y zImLz~9-&kuYso5G*7W7rP?yve#LQtc6jM(E0UsN6UnnnRNK); zUB?YeeLS%#Z2#l*Oc{44oI>}Tq8wMcRZ)3f%Qw{}iY3*$0uFeijv_5s|#`2YGaMVQw1GP$=N-lIrt(m7Pk5jER8IylNI@K`=XQK4*So@^M zN8+=zB7u0uEcsJIyK7E|O6!CmF`{P)iY1n)F-IKgWS2wLY)$d8@{2)_kRA zRm;PZ`B9sABwu&Ps34NWi69#r*fmuH=}I`ItwYE)KgG7~*a}b6+C$-m;53G8nlK{| zuTr9nN@71xX%tQ|0dm-ntEx}(se|MSr`QQb6*nzWI>q+6D8H>+nc167Y!ncJF;3`q zDQ(?r?9P$IxyU|SL-Cv)RN(kB?m!)SQ=$_)(>e* z3I(7{Am3|1qkkS7jXH0dvj$tNULlf);&)nXR5$R0-4FZf7tp>b-U$;-YR7}V-p?sm zwX{T9%|1>tuAk$K{ReC%bc?8B<5&os#D)Tcf(5GaBi0dX#;WnrtsUoL(Hln@St%EDaxO8IU(TAhz%< zC_$&^udT;J_rU82_%+YfEiKMKqNpF5+(T@XHlR%GwZb-k0#^%n`+3^t#Y;Ccv4b|( zR9Gf@_}wt*R5Fc533)K@N+pPsN{86Y=t#*JXK+%nsYrrFq|*ZVEc4^`jOC1N?sxUB+*l$2>faG{gC}vCOLm^1?pbwi337*b%E+)mvvw+N@hk=$t1uaH?P% zRn>{76V{z6aBpIpd;_!HxxGUPN8l*HX5T`{>E021PD-t#2}???V+NFdc@5w0OK|0Q zf=Id(0F>ZZd@|u+uYnKxe@Ec1<4LJ|T>XpqRP-<6`}ZEeH%j|ZY8~G$Cv5LQ0@BLX z@%TMSB`I}}3NYaIPHDUPDhnQQSo{z%x~C1JYwf*PR5XyHkV0U+#^O`&aP?yBCe`?T z(5>xQoCVd`6;(>=44ui;(#ZK5kwkN(LZOz#{!;4N)Ioui;gQ?xs6A3J0t;$*99Ojd zL{~wGPH%>yUVm8gcN3sE1Qfmu*ehK^=>+vAVV6k8se_mjjg5%*)Vx)=pc5eh+0hK* z8~7v%cmsLcfL%JXCgT=mbA~KdZPDjeOKqS#fxD+6?-7UIYH5~|ghC3{%kyBQHMFBy zAOu8h_Whnrr**OoVlmUHJOo$mpmDhPkIIT^iAqAGwvusk8YD40LH1@SGz(fbJvh)h zO<>7%dSp8u#c@IqAJTHp^O}VmPgM&^@a8QPKgN+l3YapVv5r|a>j>e}XkP1bJU&_nm;`2?Azo1|+9W zb-UPGg}QT@mWGfGiPRH{jxVBYB36L3G)Vf#tEG89ye}vEqr09@2%J`~dq8|{KB1f6 z+=QrZJtAby)c5(e9hcPgxSZH1pp5WvnxeI&Np59gW<*2PcLF>Jz98s~aX+<;xJsdZ z6t_*x{0BK>pT^8HrUya4^VTs+_%l2@Hh&FEb*pFtK}5$7G!ngpzCM|ci)=YdYyQNPqNH`r?^<(&i9iqV&o$F5|OFhjQI}3Ub1rQ=zwO05Wzn3d? zk5jf0rf#h^>IE2BGT=Z=yHsB!LDnU)!Didr_-gV0I zT`^7Sd&!z)uH5Z{YH zLO`@muXpDpSQ#qW$Wap+6o<1nj=-S>K?cTY@}K3p@b^Fcc4gfP@gI^S@G*Z{A!#j3R#(HLODITA6!=kIKAx9FnJ`DIq1{Q4ssu%#D|Eu0pj1 zwLva1#<>c`W=s(Ar8}{N*Q<#|8F9`-s2!~~xT3T~0lo@l7PGB5z|ANv>&}9FH_E`q zb9{KXScrzc=U^DZk;YzR*<&+kv8Zd})!wCl^%c(87COfUGd`Rj>6T`Y_X-@Cb5N*M z3CTyNh0`b;wv`=QmxvJHTCA2myqxh<+mLqaMic3j!9)TfZPTa0a7CCUVb`x>EHoh0Oc*)W zbixTu{y1lBEd!@2ua>LRRhxbtLu^1ogxHX6Zq$8A68dSD#bL^c3bT4yRA5$@Q#vau zvZtE7sMB($!zni^Z<67j0tx4rVcm3=eIdOwnUB*Andz#KYaW4UE>g8=ZD)46&U=v$ ztFZJov$BGc5-Q_x!-?fK9ebU(bR^{;jxy<5CR~J0%f?zmm*Yb5r8*hk#=N1Zy^-5= zd8qo`#S2mYm`bonS7&?&ZphrH4vJ*c(=cN#Tq-FI3e8aK;b;-yOH&k^ffKXw%Q6ik zM6olH)8&=Cgx};q$DgCP!VM|=Ee|EaicwJZ5z;Z4^i;)7E855DVqSGeSCc3G7W7k- zB_mPy5gzU)Rx*qlMYX*Z7iAJ@jdfP`Y@dt)lf{MFP0l-XIfz(6#@rtSSs8F*rt&)E#^4%UlaQAU0$wFh5Vc2%yIIpuV4rg&x z?k2h*zB#i1M1N3(`H|=GFpklJay!CZh+e2$#Sq`c`1Xq^o0_ z*eH63x{H#RTCo*AqScH@AdNgsU|~=I)~KJXrQ!X0j;09b={f~uK>ND@$AQ0q^{e#5 zz8}lYLLaX+`(!|~b)Iv;LF!PF$MHo@t6AH>l3_na0?vHCzNYV-sloc@vjc;NxkaaA z9v(9)0kZW#B56_8Bh))0&oG@PaFFvLR0O1`y=2_8!yr3dsN(2%G~;r2U~%3I75qge zOrPqWk47ICM)uL12sc3}{yfn$z+@3!Lp37Ee+y+D?*Rn3{uDutpoDUDk*z zFsqciR)A~}6wHx*kS{aXs!b#*-?2?jcw1?Gn1wX=xKsGG$Nti-6o!N^RTzv1 z>E?|gFu(`?8*`%9;WoPY-2Q>m&_LzIX5$7xxsGf{0M?-ADI!UE2bkVR^TLF>N+4V_ zZ{B=;d`P;cq;@6U^deM$*DjnWFq1b&!T3);9{I=ti2hv)g6hA^%k(es7#bF-iFfq>CUcsN^RTN31ugD@so>C7iDB7VcJD;wJDFr3rs%prF zYLSZLY5l*08R=rBx$Q5LhW{-N0V+4AGex}h-5l9p=8XLs;GR-b&XsNGJ{(4MlnH`q~o2w`w)G@O{_p#I# zvr#mMgHvpsDGHenrg%HK-G7+YR^=v zwV9K2bxa}vt|y5Au5+Ujf#6M3P^Ks+V`Kty+$EP=L`K%q=@ABgDL+7H`2bHL4;8ZW zy#)!?cG_GRP~nC|8EMqM0=R3o`OzY!s*WXUfg4x>ADTC|ilDkpPXR;XER(l^$=a$B zw{9FI!4!(2Qc#kPyhBf`)OFq*SlkSS(e)8#eS$XWbQGS@uKDQO57tJI?ebuiGzstm zMvZeHF$NS1NLG2|QGK@oqU!Z6S&YmmC@4%$Zt9p)OaN_!LU%!D+_V}7Eq%OXJH}zl zr)WkBGq@PELBs-s?=`Y7_z#nwAS>yomudt6A%&*3BDceT006ppv(QeH{fAI=T0R6;#hsaVu+Uyt+dHHQzS0kMgaUwz|B22SN+o6_%EFfgM0}pG5G{?Df~7u?@IBk;q*>Q0Qt7G}6OM|Yq^*WO1BJyS zsjl8QBN+W|zDgt8WfZ6xR@a`&?NQEgWXFo#$eX&yt{{%~9uy&qki*NUIWnj;Gy~jo zkyA-~HW`q~9iKFU8`)XpHyGQg)Ah7gFfB{s&u7Pml+Ly2 zmh49bz<3u(P=A9n_8ve0*>=jX;30*yejlEiIF!XG>r<1Q$$x{x{x!V)GTwd#Z+{bS z=wbxF7YqjI_a6bfBdbhRS5lp+&Qxo3WsJGV)s3i>JH}jOIm{+&SyzQ-EOY^;DKUeR zO?xx4whvX~Y;#V`H4mA5W)v+%u{%e_F^b)}PaLP%ofG0D#qOLE_fzc71LDD5);w-X zE}fu=%=e~ct*aYqZD0pyxF}7VQ#+#8-B6?7y!-g?aBhOvStI-cJfDA^bu z()isV?$dSX*e29#ge##&(8%#OM*I)%wU~reKZWHZ{{tI-YOqHS51Yj+*x_OIh4riS z@~^zi*zk)NLZt>&cp(=<&bs6=smu1p1FAb?%Ey~Z!%+J4wkTRCa-(ollA|hR?8$sx zDbyQff+gv5D0vDPS)+$R1)D6BjyPMZEfjWafIuFj_x)Te1Od<+OwDV3$PW2Vb?wAz zITU-5Czyq|6z-)Qz1C+c+qh>cv0AmnYE36rYbLQ;vnb{Z*ZmD!y+tv2tb14|sKxUp z!>?u*$&ZR_?FuZ9cK7Ipqkb7r2`BqSXHB~6UVehX7pu#X(gst15FZPFKfZ78xF{uz zo}`hUWJrcW+8fqIFhhB}l9aZqYixV!&iSPJ4p+}(Y)|dCFP`w*=^X+$lkJMIw##q)$5&Z=zbNnUy$6!1eV$mY2dS<1B(@HB2>pSj(s1Fz5sDA}G+l|8z8B0{`p1$n``Zj&LPRx!>>A){(?cN~T;NU8 z`AzRm%bfidrZ;h1$*|Cqowz`RTB~>!I1eR^(wfe6g`3hDouyLK!1|010-$3}UaBRk zPt~k!Mr)u`&8awyy&gU)1amiQeI;*c0Sv{D;HI8Bt9~32ibZ+W$&$tDf8dPufEq9$ zIAkD9ph_jw8+MI9NIChz+JTli@K~m*%1106PM}Mz>sqE%)ytaa5Ar#<5gg4hbH;{- z!FV8gPW2!`;22O|COGltjQ2>TwO~SxN2$LA*%1xL8o?1>BVa$uF)-3g z3k9y%ZVwdV9Cb1FEc2h%l8j{K(MqVC_t(NoK5`v~OPL#7lO6)2I!Li3P&Kn>cvXh*_9W(kLf0^CZAe zvx!akX%OTI0+miv1+o1`>pneT_>L1{Z1Rv4~7m_&%K46>FKO`)7et-hjH&M11+o=8) z_R`d5EXe*}#iemZiiD}Zhz)&^oT=ui_{I!(D4!6VnvYmV_?%Vd!y^SZkj;N=qo=dr zgTJFUdLk{LaW*7NWx|p4zFSRrcPim>b4PiQhd7yaxOthxwx zH-Mp%#KuIo%?0u@qN` zT0=9)7>MkYxs#&`b}(s0~(@!3oHEh@pdobp?8gn)6q^9yZhtVP9jg(vGmY{ zrc6Tw_~mS4f%BVoeLT0YCvn6>1Q&%7hnp()5W=_@?56$ z0-f85O@-FuckFh z`h^b0cr&NA>WI|7-I??%OUqunE&K8BVweCPW*vc_134Bo^tIg2VGO{4fiR*Sz73@x zhr!a+46w7R1=9;rV<9^I=tA^pz3wQ;YuXcyeA1u^$d-?4&rd8LtX995j|+)s3c1_l zojEzFe1chw%KD_Ue$?v!|INJ%lpNQ29#~cVnChOM!C)|Wk_4L+L82jWC{dIxK_D#% zAS6iCfCfN88k6qnnW~xTo}RA3?P>tiZitEiTaXjlv{$xeM@eSZi5)qyeUj|M-rd+v zve`|tC-$zr@j2VxY?Nf3tQ>FbB>V6y-aX&<-+Qa7r$I`xvS*t_O+V^+AOHQI-=~^4 zo9%Ed3?vQ)Q=3MXxTZM&DGyK_v_g&VA6l94t9S>EO8o{l^kXq)j@2skX#I2iz~EYP z>wL!yx?gY|=V{zO@^k%sK9?`#Js+T8Wq*?VNV$B`f44vB&-sV_f}i&bJZ8MZYk8c* z>yU;BE9lhWgf{0e9bWFDuNlWn!Z*HHCet{e`;QQ~A0Rg17zcnq0?J3#L$2dA&L;@{ zNxGeeB{UVEDqL%*pbbuL9cfN7C{m`Mjv8oj)VHtCLg;AKL}DUd`=)isxNPob{t^rxKEwA*I~C}2h)Fi5W{8A-quyU4RjsjhIooF z=;@l<&YA=kb!4B-7IhF3k<0RI28nRACoNtaoG`&2@`bmha2PwHWKgD(BTWM|Q%RC$ z%wD>X*}WU9D(ZTYWJt3kzLi}UEJ`Y5E955=-vyr|p*w-~QqZ5YC#*RnsO?`W^161M_h8gIv>tle=3pnG%&0QUE~Abuop!yUK` z!NulkIvCH;I@StuFJOt6RA+LN;U*aVd`e%oH@Emo~d=nk&hm#~ai>aEHi*J1wp{P;)j@gcbshD7!aM=qXcF zqyRG^nC777O*BksO|5x)*068N_b;#nB1Oy{H`H2aIVY-@E{Iq#1h<;bd++JYFMCe- z;9Il^+NsnDJ>AE1rQo%fF9iZ#qf=Qyl*IdxK1b#hlaX$39WUcdwn+log_1$P74A(_ zi$I8A-H3pePecq}h%f7trk`qTzLarNCvct?rI8FH`d)}7t%m_%Fqt+QuaA9@r@GY? z<+`+rNISgxr_VP!fPvU-AcI)|c)4;Qbu#McFsQZ?YF&y26el(PlO&# ztO;|fN>eL>B6*G9p%}6ohvJ17m_-Uo+I0!R+B4>mvtr3!i%tEQl3fEy7 z28i6(G^7E8q_eXwJY>G>d$Sq&mF#TQdkAItxfz*T7x_ghYQ)PCml))B3od!*;{07t zogTP5g#ZS$+nEI$U0l5Dbzc~`I}-o_9=Nd&gE}su_HQ+JCm)wvlY@L*LD@I_&^uFu z0^%Mh@O}*BXMkSPPRJmyWx#`iCqI`chg#0-x=_%L=mV$(C@z=r)IINsM z_tH!Ct1n*p#HI5us;{Dvr8<9DF*m?^*hx_m_(1}3chK}>?84#f-rIjC`hj`y+G7cy zrti+eTY1dL=)H$!tYy0b!r@Bay<2A)=??5=9SNv;{a>^b`s)_4-!vJSp6Vhk9w;D; z__cQpTRfQ$)FX1}7HzwZX>rbtZ?OxX53Fj_s5HjWba-K@!+Q>F*o4xI18>zsSQ)qm z$nOVC9z4P@p#OLDG6A810AY&z{}sOdZEO&eMSAYa&CzLCN%!YDdv{ot=gi%ucd>Vg z>1P*ZvKTo#SW@Y@+(B4S?$p}_z*jChO;jjM%>u(cC{FhpaA(*yeF?NyI(a9V@Jpww z%e@#XakvuO^*~d>pIp+EN6!lnS3(7%$w$I~3v#nlT?)cR006nAaftHY@M=L3jVu;9 zgJ9m9OJ!G?@u2sDZf^=GAgPjZIpQ}oH9MFqL*FzR z7YoqylFB+9hw5k64qWU-3|WS4rzQ3>o6>q^5Mej#aDfNY%BTkw?(EYeUyyyt4QY*p z2WYi$S$qoj0y6+92WIkspm9spg~a$Grt9=L56j}D&gXy}7uO0Vl{mWDjGW_il>81_ z_1|LiKt~BY4*vqC3+y8QP#&SFf?xFWB4J>I#RP4qc2^^mr zpq{j#o*TG;2I!t8y<_gKr=E-R!t1)>bkD}XuYeZr3_Pr^>e|2qbt$kh-tewEu^$K) z5+e~$6862PB1*GV`v1{gEAHX6iam!3+9xuI%#(EQER!R z6GdQWH4@rcub~TBdqBp=Lmn^~rd|Fv-iZVVrLQMzDkPfnJ(g;QC2=L)br&bKGpQ0h z0(@l_ZA!+}ba0$GQ(%9ToT(X|Gqpc>lvz{m;w%e0tHa{UNb^yRZ_jJ7dak;~Z-55g zSPQ$=%U4I!qQz;@>xN8_HbN(ks>?tI5`8tng@Z7AdOd%!v9b=l zu@%+aiX%z-MVv9I;?!Kip`>@8uHIU2uB_tg%VW#R{@ea+*XuBry&tUOt~ z-jMwJE@0fmu560ByYy2ZXcIl^RS_BdIqR^I^z@n|Ci z@|=pc6haNvWnh3No-WaYrCe5?4n95dhi^tfbF~S*fDkvC{%CfkFVJifj_xAOW;43k zXp3hK7zH{<2jTA|)R9mu>rgO>4nP;7OSDp5#L2}Dge#??0&BU#5Y>02D;2fdj}-|? zXo!Uu4Kumn6Er8n$s0GLrc5|gmI-c1wH-DLBU8FX=q+3htv3~uQ)Vm)kx0ZNO2jon z1!Y!J>wd#ZN~CVtrr5>{b+sCFhlTpB)u8);#S@OUFC@ArIL74`_j=i|e%o~liyIL7 z5i)7Hv4_72!Slq4?#UDHxD8x>hDjJ9`3dO9;~i62Ouv{~!jpR#!Mw#&+vE(~jE(_5 z%Pz(1N_eB!gzmY#&Bm6ev99PuDa%xBNF7vmRT@rc%;Z07?=0V+HmxIY%9$>)jU(I% z0Rd5v8f9R$QRi99^(7TnZZ_^Iuml5v8F7cDlV+;dma1_NOdA}9!bsRXa9YU$HE={GG^9dFxYv(N+q?RY_w6qY@f^uu&DEzMa ztm8O8Y1%38^dHmhgc}M~cMSL`YL}ry{GZr-D}1bD$Hs6%pJLaX-6`5$&_CHn%nZ@D z>4l_})zWMJNqfP_-~Ug#LH!;YE<0q|4L@rdD%A}X{cqU~|JU1fgFeNV&Dm4kpnuAA z!?;;`(T>;iC{UoEE)`|Fd^Hi7Cc=aTUG%7AM89PMGRgt}n!U$-zoG{sWqN|^jEP{4 z5vZ{PZwo24PRm&Z)|M+Mga)`riUB}u?rfXz-c{r6R(EHEvw3ysZe$MUB>F3StlJOH+F=ZSa zK*0??gkQ@z!8D7u?B##Owjf4KGR;!E*aie76ufk>Tf6@i zP5Kx)CBXD^T7=cSTX@w%^WVwezA*4EIIo?(Q;74?8CxDN{yG$!O$-V_Zr6L&yMrX_ z3H2ng-~A0oJvk_H(SGu}bNj*okYjR|&+sfrKs0B`?Kw-^o~5nN(!TBtT=jDRJbu4( zN0iB;2wa3T&QxE%{L)L87O0e*P&IFXp-K7r@+;T$_f1f3)=E-X4tXQ9lTANTG(guW z#5W)WwddTA50#Q5%G$}aIfhc9(H(IEc(+9EcwgF|`XMmUaPi8T;$zJ4ix(tu1iFmWM_;;} zlA#&rrsjP7I((aPM85)!S$H;4zzj@K?ew%07cYsq!2q*m&SZegh@$b{&r4v61Kb>R zSQyev#>C%);iMn*;)Rrs+Mhh3#P|duzz;zgEQ}5m%0Jgj9R1|0%!>A59^>G5Fbj;EQMvsr=qJ%z!)na7GsvwWKaP*S%{*L zsc?TVeb){5?aT&KyKb=Wm^0_>dat^R2ZEW!IeZ_^?YfHx=_W(L;BjVKxr>K`L&4#D z&f<~aQ4o^da~F>W$AX9MIg7`Fhl5A%Ig1Z5AFB$MPp9$wCeDAZ+6IdrtmuT?K21>k zG(lP%*S1Nt2n)IKEU&bDonHMA~Y?r z71azoDL_t2b4 zZSr&zjw*6Bp=1o zfas~ls=2Zup1MYLD^yR*mDv!+ZwTVGcwYX1u2K`>HoMh!qu)e&h6;t2k={IoR5z5W zH#(B4D`Q48Gf=DSk~TSlB`w#}zQXNJV^bjM_Bi%TK7i?MKB~B6;5G_S7{Q{ARkkE{ z(~WX9H0fNom=*9euiose>1XXO@eHdy1!p1MLp>INh(5QFF_Wwt2Wgdv(bbtJOQ_Qv zp-%JM7Kpbcu%|g)%49HHkV2pqumRxwuvljS%CJ;7H#VH8%dL(#u_@#p^-VMfWzs-n z%lD_*DC0dr_Qs!Z98y=+9b%rk>bP-v?Bob{=35fqD|Wd#-wqocjCAB!!(odk9O$&@ z(2dyS!mDRoel{ru?0jj>I6pjCUumNvwjI`!K?N!{e9cS&j<~L9eJ~Jf?${h%;K`gu zJ8rcv16@WJQ9`M3n+egUjU~5Vr z9fMk}j-U?wv##kQAh)8rw&e&X#V2*{edD{#ik0%(X@sV0suvQW$!L1o#9a&HrRuGy z*A65ylhWzVbtSkkb`8p&S9{JyH46hOZ?^yA^{BVDj=+3xxr@SbZE2%>Gj{=YV!be( z2F~b8SZ&y|n3OJksjaAsw73Rz#qH{5L>QjsFun!!AbnP1o@Jq4QrG)>pH%V(%=Nzs zkIG1xeb5ezNw6eI6^7kt~z-q z_BP7u6Pg`?nV=f-vH&EMT?fdbabe(@_s9j^@x{Z;YF+x8K^CSRYag}UW1O)djHVMX z5$UHzF~AamP)T#O+9As`uy7Lr1ah4sS_3xzuzaZ#gql{MD5n~hlT!+e>0g0`IsvIv zByCY(C6dCfs+3jai2|?>{5+}k<~1vt@&GYLWfT5Gj;&rrv-=O-mlpFcT|57gufjg= z7hd@G8#iv)_RmNzxF>}sTHFVMg^qmNyKX$O50eee> z+)=Uz3Ih&U0#RYuRq-UwGH`Y(#Fx!WTI8++LX(!iGd(B_imL`19BM;uXP>AI;Pu-D zW8lQj{y~0cc9#Y7{keg&b6`Lc7Z5-Wio3=u{wCbQ?Q~ zc)Qw6%&0@@JT5N%Xc244Qj(_cNN0a#PVu0%K`)~^0&TObp<5We4j0oFF<9@TD)i=1 zRc|)h%}g5hxa+2GJ=-$+7oC3wzT@o47^)lru*6ChgimKiK{AJlK)DviCv}HCe?py> zK6(Cxs`aN&DE_K*))s09M_oP-TyzBCB zahGWUB+yY|`_%NlV^QNn1YmsvC$e?89;*p)B zfCQnUUfMaPAgbBo<+~ctzOLf6Gk+u z4dS&z%SPR;XcJXM7D-`WCQpZyc(X#iE52D|11 z0x+UZ>r~t@04dbG-c1ACNUm-t5*Lkezryc9_Q(1D0XyI5W_tPsIPf1DopaQM7?`K~ z64t#iLm?+RH6z7{YeKt{`S-6eZ6@{VOdwWjVVsPgXPzk>RNa+kb2zac-flNnx5Xx; zbx2K?N-SqIHQ7lCgC4^?v@><B^&aL@!W%S4TY{jjTnnUfW0K>JiUz{vzh;8BDhV z=q3*3gxeX8Vj*@y4Fn*FK>vi93{s#9Yt=ykbqsXtGii+u-D^V#P$aC10N{jiAq1-+ zfHJS3PKknGbYwxZX@T#X_e?V@K9TocUmPKhA72Obem!Vl~o=T1dZx5 z#Yzk?lL41`T77YViYqSv*M*J5DhUC|#tV&4r-K+T%JPnAfLY+hjl?BrRUC6P|1;~o znRBP1p+}u$Jr&Z5^vWoS+tG3>yA)ksZXw}3j*NyeU%epkY!X98fZ@#2LWG2JSWqYf zx*2xFjf$|loxXT!AQHWhv+DqD)s|ojz)03r)M@tV?@MOz*yZy-+_KGEMafYap^#o| zBO(2x`U|d;x&tUM)Ix1$bo!24GC8d{53@cPnd?5L*E^v-bJFSZOe6^}6Gc14vUk~j|z0hs-66iMs*)UTqZxTqg!OqI%%_CSo7|Y>1 z6QGSJ2YGcWE_~Kzw-rZ0t0}i=3PaeKIt;sf3joPD=xRLwZ(Ae4J>(i+5*<_-i_8WG z5o9#kZ!yEVcgbkB>ww}Rfebjx@q$!1cq95Dg+273TCFG+OJcm!i~BDjL?_n5c%iWw zjyGQzjrv{OFa(XmhEMq^P0qWwa1DX3hsQ;^7*tY+ZO2nk!~T_UR~B2Ft^?7ofG(hZ z1slT8|0dqe;jjbPJd6z$FvfHtY&lS5ggr1{9uc8GMlg)P0{f6JpZ5taFjXn}`~0K+ zRFwkRFO{G6-&uad&-0i}Pz?SG2E~9CwKpf6jARy8Ae@je*j)e$NFc1F17YQ0DwPio zm11UCxDH2UW73F5BS}&y91lXmgsQ$YE<&VzTEl_SZe-6qEji{mLkPTp)bAP!a3?doVm71R!lWWa`~Y3VyZU#*Y3uQS#4INs`> zdFGiFHNU+zA4ksw(MtCjMP>#e^1*9*dRrM*?46p>rL7}y+i8~&{Y!f=>l7m+bF_+sf zGco5Quja5K-6?EO!wee~fH8aBx$)7w6PGs1>P>BCm3JlsAT;I&1?>!~>`dWIpK@+| z^pnnwkK+8q`udl)l`kl8wnJ1ff(i%OYEuam5!s&St5@l=*Cpk42A~q%o+Z&^;3v># z3p+*t{Pi~pp5_uE=YWT?g{!4-z{5@#(3w<`;9v#iMk08YLz`xw#Y~|W0H9)PAnLOE z%qVauZJdSvao!skk5r?(PI41<@)YLAsZ*F4(+}2c5}Z*xgv3rE^~v{Ru5(uXXP8Tx zN*l6Z(KiEC^`PDWm}o3!E})(t!N?;p83DwBT!pwO6daEzf&h~?qcLe!t1lovYApe* zO51ZX17i<(2^e?RozAm)C(dn@;kd+q;@Kq|JphP`FxbI3zmZcPGR(uGsrP@_wlF3H zhfjL#Sl*_s3YV`~tMhPu334Bd;mu}nt3h}xgplmH;;^9r7*CQ&IA#$PN+J|CzSxa) zo@VvM51qSkG3o9i|!C^3*;(gB(9HrQB# z`RXOQ4>WS@Hcb946|JeprgqEeGt3(ptdli@goX{(-k!JO$(V+CwC2AKkLT1Xo35R&$!&06obzQn?6vKr@O6zBR*h! z+L@6vuI6Ou(MMd_*{{7PWuNV?gDu0+ormr{;9m$?2E!PA`5_d#DuTHHq)+YwJ_3)%^&T(E* zhPJadX!=S^97y7W)p-b33Dk5f;;CKhb?n-h%n4TM8gcg>b}@a8pf2`%Y6Pz>)MgpQ zPzT|2tVrWZG-FgCLdFgKx*nzqHp%dSKEMn`!od>fkJ?4D0#kC*^t0Ylqc**@Z2Tv# z<6OinM0tf%nkHDI@0Pto+6eR+j+Yt9hpSDEwLzZ5@}H#FF|!U!zMz@$)=pvl( z6?!@`=H=~p9V~jAQE+Cd9jzg0W~r)~*Jao#5Q$~rnxGw7%_z+o2~t1k<$|DJF90u} z+L6BtiaO7AVmNLK_{_L&d1n%i=7CeMPyw#gr|{jb)0z%6BJ;p$RqE66KH;F`kvRVh zzsaF6D#*P7G=5-L1AUr4DQf@ug^Sl}p884j`d`KZen19#LCUi$7!p(T$JZI09Tw|M z8`0)K(A#=ed;#E8AUU-9In0cFy&bI$OGu+@tTp0L{Z0H1HmJmKV1&R-16mJ&1zDU3 z!fssOgEq*)yNaoRVmJY*nt>zHtK={VkZ;`IZ^p@ZbHZj(AuKQE#Hwom2dB=OgUMg% zs#C~*9UtF^d3_7_f-^eV>yB6ku+{8n0CU@IpmdY%WY-P64Nw%4Za#4DB1PT3gTB++ zbC4sJi@VEu5ldy6Ur--LdwwTk+1`n6tvJlrmwT%U_3SFdW*(iEZ+2FrVNoG0y=CfL zK7~%ejijbIW9()0SslO;-9Da;X<6>9I|qJtJ^$vDee6<;Hn4^oe7&QVK%DD74h*oJ zLR@T3Xd8gzlmXn5X#ceTbN%_N>%F)Nt%ysMga80Js#62?4AgM|3)E{wFrf0wBM>yh z;-#zg>sMb=AOfbI%7HzcxcZS-uGOz!k;G!!f^UP!`8wK#APnA&oA(GkHfP%=MYUra z2isYA&8c~gQ^$1yRN&{i=Ir-#e!(yCx6FEjR_1#1>m%!p$-_6|!Fx13!Hk9{*stLUW;LbDf#5lky10vT!J~@@gX-d;;IYNS z!Q+cZf_E$)4W3v$7MxgoD0p)5;ozy@7at4W9Xxx_S$sS=aMh`O3qc&;Fx9>ITn%ay+@M-ny8WlO%rvj0oC3BBwmmmFF2;SRKygsFgpvR$X;#ZD`? z;WqMd!M-m_QeFc9avWE9F$s(~u1LL;rO+t7a<@x#CdI0^0_YE9{;W5Xc^iPby~3^9^L0nitTX@{K5zkyYYI%c6A-VEF7zrYtn=Ogtr z3f>;jeed@i=kK8VpshG1Bnz4`lbIoQN+VUoF5bvGM>k?JYeY>KInB^pfU?)h3n~z` zI1m0yFu(`-yTsIEMrL7`ye;ZlTpARE0%!(1)3LKt4vK?%x{3M{1FYByGh z?_lr9j6;)QC6xp7Y|4*Eon{;%O{;@Ic5iDU9)z%3$;mg2F>*2Xdxz~pw))O zn~ioa?5YpgCL}(X^v~?PzBlM2qO(l41K#(UfA^0pDEw zjlNrUXT3_U?Dii{HRqm#`?K1u2w9|EG}bo^72MhEH?g{0L{&bBhWIju!&mXNf5a6xwc5f3Wb|noS@iED$}NWu(mekio>xpP zA7SuAspm_b&pri~6f27`6M=a06;M zRFj{aOj6E~+8#{lT!K0#seX`?92gG}yeD19`8_-@V`=%k>w&3yNp)}6nNlEPIOh6)(KaqnGPi*`A4h8YnRXOy^tP-U z7W(sQ6X5Aa>>)mYszjg_0|m;UqTBN|4PhE!SJSI(e8$wE(#(g zse84tk^wK!@Ns@h?)^Iq*kpcx6jM8Qv)Pboh z9~sCvuU9siDFsNrJfgZD2%-?g@!c8brKIFxX2r4D-k7B5bNuZ>DyLGp-1{y(?)Q^k zefYk6A5zb470)^Yya&rnkv->eKOIoK?;!8;J_A7UL5Pe)3O+z5Z-+7Tu(+KrXz9-={ZJ`9vfaSQfcapg^t~9>6frl|hS;?x!hR=i4L zHJCl^0uA*YO+qg&&Xx$0tt0Y2_F60$sJZ=rWT$e9smv%!!ib{lZWE#}TBC76yRzt-)=TxK_!$p^Q>T&#( zp+^&45Aay&aZ}5u7Jf+OVM&g!HvzwmJO_cv&WZ(446CrAEy<_R_SCp1DsKOP9vbOA zY@1^*yHXow0pR8Ay07N%c>FIn$f=Vsr%}2wg0=jaR4;5sHziIhn(_#gm!&Nd^4vZ%n99$ECY`f8ROl z#O3==;Avrsj4ujP?0Mfg>PXB2S7M&)50p}-?Ipo9Tw3j%gsw#1Q;bYATSk0FuxwfS zY9OmM#JuflLqKI1^Ad`J`RXej(@WZB6?6J#v?Ar^&DG>I8ef@nN$0#usU)YLy-Krd zeq}~1CR-=GrN@Rn8^WP&c-&ar*$Hf&WjZ!0`MAvv0^zXcvLVs2Aj*IWxeZE`ra1@_ z#5^RZVnlDib4JO-nRRH%8f~NrIJKm`2#pThV3$^_y^bmUL0L1OObP?-li_710t#K1 z!{kZ#KHUEgi4u&bBXLV*+ui)mD5CLy$Xea(nnwpZrT}$|zjT>OS0CrxPVHno^JnMl&I8P8_Zvmm(2vypxVb@&X3~ z9RXnnMbqd-oo-=VHfCu8jVQ{Tvo*bV>C1Ix)&YMS$uHUFG7>X&AEqA7WJzvC?dHmM zwgWDj9|iq1K+-ut-w8C zmSk!Pw3B$&A7WuKppGD+7kVucm%rxT7*&UvLGc+BW? zLyL5>6H;TqZ(|n~uA=)=jgHr~TTy~fB8_S9f+lk}K9e0TrwoF~dZSy{%YLI5uScpG zZ!evxDuMnKG+*8E)rL~doABbPXmg}Lsw27wO`*g@YOFAQN@od;swtVCtyb-QR8KA~ z;R7hAmQ1>>dD$m0LqN_t-|W_HT1cI-fRV=;x!U-L>txbvG+i}ZT?j4+KPx~Bn@L)Z zyyR?{En7&OQXPX<6E=xp0y4089lr}?bXOPfMoF2JyqcYExPsKdaMnOpHs${8c1cA{hwP{H6@a?QacgMN! zSnF_j6dt`~O-Xt6Xc^Qz(RFDrWNpUpu{PrqTFXt{(O9$b5f~lc26J=-+KzfR)3W9a zYg}k!Qr$IbZ&V!xq(Vx#`unv==S1f^*q?58Sb(gL z?3=mDRLC|svZ^OH$)?VH;%=?~6lQeNM^?>&$23tW!k~XxyHWv}wcZn%f^>}jL!C&Q zo^e!#A7OJFp7>@L<&hsoFc`CGGYSA4PQ8I@&aH-G@`jP3TpEnFaw4drh$8uDZ9zB# zt-tdkytW@S7C6vT=@5s*+#7gDomcbB7Y7eo-p~(=_g40J-nlWOUEQ9>vn|SiprOX0 zllXIwCe4%@%Jl3OWDwKwkXUjYKUo`CeY@v4KZ5Z=pQk%V@SMJmNi*^>_9~g@hh<0q zgYZ=BC0ZUhfrls(v5)eohPvRq_AVeyzbX+ZC%Tx_Dh~3@lF#d$Ghh3+Pog@JS@NHT zlnw*HaiJ@9Ql^Xtn{T@W{EF2`ZFU*iW6r!C%V8qT1m6g2*_owe_}5pK;Rp%Mf(Z?l z3qm6p%bLq{oqkmawUZ>DGu4fzkS<6d2rwig3|)-%uxC*vlXNfh98E_upa*BlIA8_t?Nf2=$7#0X4zA$iG zIgoTAjEKCSz!=H z9pAsH+UVJ);r_q7dy5p0X>{{iyy z={p1Eubs7pJH_ix=LwB-b5?Tm;eIDTbUtuR6MWJc6jd;ATZJyR#X+$(vEg-t0sTOM zXObfZ1$7VcRMb3m@5!%#?XbV+x?dPL;FoXFF}z(tC=Ib5eIMnGvih%ejlkqig_XXp z%pNJ{*SV9r@%xXEvq&UcuVd{(E!DWt*2S1Xjsd#O)M3=7WQbx24t?W#Qm?>sG^=HN zdJ$uH{uVk84$|SIUKyBp4Byd6WrkVc*wu8Da)IM8Pgc2+o}bY7`qSue6ka)==N@s9 zdtCI&#MwdW+cZiC-HHb{|FqkmHbX%k)Q$j50~^e_@6xfq2gXI>=XcRdg^j#=K_j)n zt?#r7$kAlq0WHi^Um*lb39NM_;Q}BF`m%!j**p1xH^^_~)Mw+VooVU07N#DcV+rDL z6{Bw-_TR{!EMblx6y6Q#^u(5 z4Nv{vz}cC@INTMSGr7;%T^C`jz}xWO6#PElK27rdhin@hG-rhLmNt0PRe-xW#J^ta z@SAS8KPc$G&W=E<9~pS9qk}^0s6DQ*a}2&z9rP$PKAeV|$rK=@7(iDeF5&cZU@e#Q z5G3hp1WY=PnsH*IrGA5KhqRaEGd&@w1$SU85@8%3zNWRso82wKU?vNRRFo%Y5fxdb zOWRGlOrzS4ZiR}v`#O9pnnM{GVR|WI(wg?gqufT*f>fr zv!0)&WegVrfYe+f=_c=45m^Vcq2URVMGIIMYHy$_2%Rz1Si>5fc= ziu;F=&(T0#oaA-cNK^k5mgwb1K&u_>$QoaRnjiE?L@s0*@7K(}UA6MoyY9u(V3{KU z{C^FSkTP^Cj-opCVT#BkjR6ajT7VgWG`>_835Dz5#|f_DSF(UI81*U0VJh4pUr;B>;5gA4+QY0Kn?4rWuq)y7IIK#GDExIFEJno(;;R z`_C$(2!z?746^UE8V=Q)Y3VwD?MKr2YiVJUo<-V;zw}^^;Rgv4kpj#g3)|sM&>X-j zu`UkgavB4iNOet^AH7bHprI`jgaSegb0<0rAXj?E%A*RWyX(s$t9YuLc@lEiX| z0M1)JGaoGVuzYOV@?g^YLFA_(F6aBesLAiod(1Vj__O{@-t!B7xqQsu4^lxCkCrcT zFYL*rhkV{Iq8`dWTK*uiQWuz)Sbos!AWMRgEH@EaO|G5ctB^V&oM{cSv(j#8dQlZ z{#fw%J!kQ7@Q&aKqKcmgp1kKQ9%W`kZCI3*uRr8Ky&`m#%sye#DI!;xO_?kPe1Qx= zU|-)vT-%IDp5srD(ye_H-?Wr&akMS0(Q0%24<_(~Fy*ZO;iiR)CNQ*S;2Jm}C1~YZ zsIAHmh(>~x?t#;is;*qiZ=sqi*FrIAu2o*8jS^3`Dg&oAg}CV(sP+NS*R(Dk^^_6_ zL2dRG7Oq|FPa6-mWQNteVNsG3g(-6Xuvl-lf$-F5G{ zaSlEZUERhK+y2j3>8i{4o}GX0nP=zUTU~G546ADj+n~9++FSvGeW;Owb$P-w1i!9c zPn5$=lnx@vy1iY!(QAfXK>N1aRn_Z|$w41z94I*6EL`?RHlxl)xcxM>T&x&rW}TX+ zkbYXTVu7sxdB30jtqPCIq*k`m; zLeFu&6`jIdgJRk9-Tood{MpX7$^d2scBfZLmov>+&^)GT!I5#t8+hu;f!E5zQtA>* zh<1t7nrQj>e&7w zs-gPOsQmNQtLcIhiB%=breN7&El;w$Z13%<6(x5l9AG0CNwf~*MoVf{Q^is`{%X_Y z6VHEt4xIL+4ARdD=K+eVMjYSCYz7vSO!#E3V}_f#N)NzKjTs7%3LnByppR=}mRR3q zw?C(cL*_Q@V8E2LBJx2DE|N+sxq7V$W5(BV{U5Mx`X}#em=N}Kd%EIn`r95z%)T^N zmNd9O+Thfp*uvx?vF&dro6Sf|sTkPvGFlj_v{RUM`v+{>rdY7hqYg)4J4n?0Y=9-xF+x=^R(UT~;Acmp@c-9^S) zz5$ECK88-|)PY>fKp?g#2sfYVnq}&0GYC^?NlU;+QO)IEoDA$Ffy`ujD4xnVnrxmv z0&p>sb5e#OBfBXaBDhWxQ&szm;s^;Ljcp5#q0@ztSpagGNFm{?dJEb~vzyLPAkqpL zpihSLYxBr9sRqDHYIfFYNw?Zog_OcSa-5=72hMAAmgCeNcr7Qxa9VSTIZ?|A$0@TQ z$1O*oH&y6fbX(r(KcPD}fuTx;hQaB0drQ4Q44hg{h7w4lhPiHU(~KfOCypvWz`xkA zqi6#70edcFryk_CeO}P)#?D>m^RCW-1o^Cwc;pVey{_xrCk^eLT$}^0BKYG6xzD-- zsnYm0JG9b%5`)1)&s~E0V#bBs8a$U+;54+e_G8%dnNkkfX%wUdsg$=@!YvbZ8;{sB z4tiQo0agQL@M4lH>y?Wy2sP%C1N&opM)C#jYADpslX)k0H_GZ;B*cPW1r{T4k8+<> zki`>}-dbM34MzZSVZzru*4PXSgal8P+~LG6)!5n!)u*kPbv?pm_8-^XOu!TZ+a%*j zA5<$4_E6S(){Z@f5rpfT_!%pEnlOKID(t53MXkdOgXJ?UC)eoVK2XW-_(gZRP-b;g zJ{fe$o;G-b0cV3JY)gbP2n=%F>72?tv6Hl;R>Or_)P>%KfvDROVlCJIe%oD$^y(3Z z>YLgUXuabV<{d$>CF4Z$Ae(FJvE<*JwuPU~I9YIsqZX-^@aZI(z&P1d2!^e|D(A_d z0sN{7g&}vE-J9Tw0jnURyN)TQ8W!qse|;W}OYOBF0jY}Hujp>(4w;E;O&Exv*oo#6 z69(%i0w%YgG%M*Ys_#JEOekRBez%GO)pFyB>sqnu|8KTsU({2#0qU2vb`n7lUOXfP zHRIFPC5T2sgI~IA>Ne5f_H3jT0!aj@A0$cw`@0>+(~IqX5)5q zv$rXHUkScH1Q0LA+-oF&rXBq(+-yhDRz2#hByRo(tm%ZcZeuz?D>hSy_|la8<9ZZ8 zB>MDUuyfZhyOrFbWVR&xWR@dqkH9#+&FU{QfjTYkFp?vlNR}r_j7M+|iR!GZ0LU0gLc4a7tF-;Oq3JJhW2bd=Id#nhB-xD zGd3(4f(=eQ^tc^2QMG4F8SKg8^>DKrwr_^rL?)v^K$3-5Ir4&LKqWCb+&HIjehxQ# zzO7I-qm&1N8dd)b2+2`(GW4qwOY{vEd4C2YpM_-;$K697vH&0Ue7Bf0X>@i5m?P{g$Uw$+xDKHh z8F96GZu``2VBF?W-Ho&^mkDejAp%SkaYQJct~;IMc_;Qa%Ica@d&B8X5^oB|=Iv zxel2Y)RIc$kzTxp9tEKgq4?A?Rt#d+HSi7{CJ9|P@NPs+pwqkaK(wymzIqU)FZ;LA zX)HA_R!BKeVOqGhh=&Wnwc#PjHOKF5N~@YK+?`{Xq84#9q=y8A{y~w5j`)aG!Hl2?ux2zk{mToO<@Y z)5>?By$`)cQbQyaFv(iK0;;=iY>+3D6)Ls9SkVAA5EgjWlvAS`QIO86UI*(ok*Gy-Rk|eFaU3^Eip}Z?2Z8{&shG+DEbp zISe__;QcdJCC-_-IHMgv!X4s@^z9U8T&XnG+Nim%?X(P74D165uw&9|zP7~ud>uNV z$kWjD2o#7o+Dg%tNLh|rdHI< zK98Lqcpih@pJ46j;{erPtGUGHf4~)iwD;Tgis|@}K<*gi{bOU}(4c;n;1>l7qgfI9 zjKOFGKqMeWKQ3Hy^eo9Dv4LN?s8ISqzKHbUoW;ZxC^@vrgYow3p5q+A{iB@KFY0h{ zKJS$e`jh^Y?}LHbFZ*-;WO<)I>CgFde%_z-CwOFL9zX;zhz!*>9YhAc<2`3F9~Q!* z0G!=*@4AZ<8sy9m{GbGC>YyA<-UHZKC78PB0D1qaQ`ghWMkVf`+sw>Nkc2wY4o&N-;W-z7;;Ed6EO)N~8@W6aM zzuste1a8~rS4g&*Ew929;8$|8AndNF=CZMzF0GEKR@r+2Q{0w78PjP-O$2C(GCPs? zj?$SurZbCHx;RFKE0GEeu^4qDmWieaMLAF9u9-hgC9ts>R+m=rv?z1J?1l+3vV%3) zrOmCm3#n&~qURu8$QM9hF~qT5aOTE^z(n{{_hoKWA=bdGLzDd*=@&_;c#k}EC&EzEyhQD+}{ZdkU1NU0uXH%bF) zIZ_u7efv~nZf;mvjTFeIxKqAj)|y=ZaO&RD``F$A37K_907G@)w&P`K;eDe+zLu6LY5}ZA2@e&YIWf6 z6axU9Fa*b6iu~oX`~`Wo6Oti4$PLPaiGjZgMZo78TX7wyzaNGzqt^AP!_b)eE_4(d znH8 z52sP2*bVEbOANbl3EjZj99pEBXNtLRU<;k``bSfplRegU53E${fBTQ&%RDr2b`XFQ zKmw8!=%jIYG|z|*9yqZlM6K{>-gQ?=z6#GB%kHnqI~1JHaZ(MY3MPa z@WZLh7&wY1hQ-gB%mgOO~ZJ}CPRSc?>_*r&5KnfE% zj7c;{`90yLa}@n~*qFMV7Jb5XHB{W6HLEZ!cbaguf-C^Vm8i4YToYG0O`AIpHpp_S z*Le>f3;php3$8z%dK}g3EYvDvH99J8q)Y;6n%YM5bDXmya830(Hc6w}mQ}ImIKPWE z1O~fb&bxlebEotEjKAM6@z;#Lg_V=~)|d9^TZ?&OUN~u%Be;{k32A$WgCqBxuP15m zs#AMnxNii>as;#A?H@p(8FasvEv-SG>(l ze}nfauDJ&`#ao>cON=Q@mzB09njP(q75}11-{vW@aJ`t} z)C>_g61~{ygRc5C zVf`xZS6oSDxBuv#JI*2pTQvwrSTzXQNQsA{e==|br|lu*PcmU!1(YrVr-^w;bSh!d zQyYu;OG`!&^Xc}gLb1?to2cL z3S}1n`s4)jL8FMs!C|-CShE;)0$E^z3sd1MG^2s6L%tUvY~bW?bgQ)buD*EX6PL~l zek+qoUhZ|*FE-oZRq1ThFxXo|^vghiWNbzuoiPFB^lUeLHsXB{MjLxRUB%S^CLV`Y z;_j+>Y6-I%3NL^?Sy@R|NOYX~95yf?Oe72=XXwQ`!N4jN!#PYEXkB&$Y&xV$vL1vc zyq0mC8j0FO59$o=GkwB=eJ}z$_N{thh2F>kt29uP6(Nx#&?dz z#praL2l;`7=A=%OK2fc{AGi?4jg@t1GBzaMe+k85d33DeYfQ`k!dFaz(6JI(qe|5~%@4g;2HjPmCJ| zdychD2EhRh8GpNf-j*PM`*6oYviq+4c|f%Nn2D67%@-p4K}2dFW=`cu$OK> z7f%Iw;t8LGb%nA6#smsM@t(g6@)57wdfAC*cJ>FP>bUXJbrjtqPC6U-+7W@{4%~NI zAX%GcPA@v81QJv_i29F8=U~Ee4xUgSFx>*Yi0)V6i`k=X)60*A2Jn-+IHil6vEMLO zQq4`})^ZUB?&J?v4DRH~(e{;&PCWbuesb-{eiW}Se_P+wZLT*0fKh;audA`*o_*K5 z-nH4S9aMiBBLdzEsYqhdJ;w`I;v$%angL7r)W5~8qS79)_Sepx|H!3<4=2NFSYXAg zj4Aq;8Ko?c)tV-vHsJg$$^(8cMjP_)P+Bp~^88t5Isrcr z<~X@44~=gfJKP|05>KI%F$h93;;;CTb4U!`0@22B@=DQdi+C_M-@4_!h=Axi{)Cv5f z4%qqvf5TIMb>Ou251jgJXGZoPxWOd%NAMK)Q30sY3_)rhv+yj>^Npr#E zz-i6Z4`RD>$Z&WgY}%S@9j+gVkK$c``#A=GC_fhh+|fh){P4~rlJmz&FuNc%Z-2^j z9K@$qF?c9RPz6`ZiJK+yqCzPI=z$}0h=rhqW(NdFm+robcG5 zn;kvI-f%`UklZz_2Aq1@Pl(BZBB)l;WK$xeV9g!4bvSv8oWw80J|?kVN{1y9KaouV zqor0-DCTAF4=3@PF1b(07u3(Ai)j{-+3RoNFs(@ZB7VwtF-t!GZn7o{Ff_ce{o{L{ zKFh^`KEex<$s&pTJ!^x&k%dQAPY&SKNAezUN1axF;MNPB&v525YY5Tm)uT8VDvlr0DDhScgJ<|`Wk*v zqRazFL>cez7I0^*?_*jaINE>|L5Y(ro0SFi-I(c%QEjR+n2g) z8yCRnVnEGGM{u@ZN5^sUbJ~6sA7z@m~Bx^gGIfFrl#$l|8IT(@4GW zj(Y$L2Od0Ds){?4LrLHm(^N)K7#`Nc3GxR)*V%O&Uf{-FJ-6n>9*Q$th1jbX zB~%)F^@-STm4f_Hye|iZ*lU%MAA`MG<$5vp_`7@7!DqfNo^|Ay=VjhZEKn1$N(|;E z{?(zaVS-DE0hziow~Jf8ik-0FatgW{lgb^J4oMy7-&h^zLl2lQhLG?q3(&hSSY2oD zH=;*)DPMoK2S={>uCMosYKifp+s|kV^cngJx3>EgW0K;FT1ABzJ-~z!o$62HU4RWo z*#^=m^04(Jbb&@3=5eU{UWDex>*I#)NVmto!r)`BkB4eg3@*SQV2i&G6hHd#Ovl-A z2FZ)0hB~IT2ZDX>QhVHN|oJd7?w(HKKbf@`F}eT(e&;=;w@ghnV4j0u_|7$3C> zHp#Hm?CNEab9^{iuj>Qq_0%C)U*D!Z*4oi>quu>|vResL|cqU75pd zcVQe^?dCqTnVDSQK|O+mIhZr4+)fwS9WgYIYlOysf?hhu^dc z;l&B-alDNsl9Z!lxc)L4nKaiwWtyA(fK-JS%ZbyQ??Xjbwfgf9o-`G`+^`>}(GQFy zPoNi|vrW6j5=4kE>gK}_kdQn)(;tZU)8l-P_7$vL*Llm zg`)?{$u5B6bYBb{NdXl%P>|!VuNsO-u*gy^WdH0CZ@?Jq1E)1z*1=sbo~s{>4@G}HKD={;Sp!q?(bh3I5aL78Z}Hn=ynpART_@hZ^DwuMaC@BF zN4c%;I`LyWkMBCWK9Bn&9ycLZppN#AohS73m3L0?E)KovMpf?TLoi%-Iv>nC@sk_n zH(hl>2gGqtCH(p+r}KE;iJ#)Hf&KMlP{yx@!Cm{F-?3BE*Lug!DZKlnti6*9i&LUo zXpw(yaZ*0B_ZAE1u6+0t@9IBsK1u~{bWEXoqWggi3g$bvn!%Y9s=B5B_W1D`fE=xv zx7GEi8=pC$`VU_TR{$~_2G#C%cV0@q8q4hvf#|&+W-Np!Yt>GqHXCh{9QB|1Sh&3$ zHB@k^6C-P73#4t-Lcg488u0M_4 zC)UC^?17kOEsXJ(U##gb)|!D(x=yZzG1|A&*wk(oCix*GJlwyhwbUh?|NC)c@Pxiq z=HssCIHw^9u=YEyPleUzf3Q4Zd-`741v+He!xpwHIEg`E1zlr~4jF-KLS_A^$Usjl z)Q*e|deZ^L$T!VvdZ`z1!{3c2rmLEUJ1lxJ!@q!c>`V4w(xFKYQfRUp@oXs-kjfyv zYoGBP=L{4E*v9_DN6E@Q<ser6GX^npo@92slbNXM$M>^u@ocq@}f*H<_;|HMrh}J`=AYFkJ(3r*J zruWc4yBP(&cK8CApu4f_IC)P*+SjnnO}l>1*XW;R{+(g+7qI7$1U3VDcF!YJEPe+S zSUF!LhA1%W%DFOte+d4g)1s5VQ}7A!2ntUaZuk}ETaial0DHJT_L|P&FC2w4ZuybC z=jY0~az3A%nVBihlxOzMJUVmOFU}Na3Nr=2tdA-BW#DudUEkw>`EuDW`DMT07yM}- z?H4zhQWCN literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/debug.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/debug.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21b0eec02aef3a8f7f684c4a8f7b8ca55cd4ba9d GIT binary patch literal 6343 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/fork.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/fork.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a118d910d34ef9fde38d8ae52cec53261e2b076d GIT binary patch literal 4850 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 Ie&6r?7y4@3eE2Mm}!?BQWBKH$UQc`yUS z0>;2Ob_0gR7%vQu;rzaPBQmp8J-rJ)hSE_!A|r0x$9KQy@B8A-`T06yjQwWsuRW;! z8e{)8z7&5RW9(auvE=&}V}iAq;KJ(h7N>WsWzoCcvgzGvIrLs>Rp{Mqx%6IbRq5Sp zdGuau)#$z6s?+;iYmVLf%x8~`6p|v1S$*Qmqt>h02E}Y(CzuKyD-HX$$({$F7 zc|T*`m+8IITk4-}ouzZ#-ctWu>l}Tq_LlljwVtBSUT>*?zIC2H*Lq9+3#|+Ex!zmq zUu<3EOg=3gF(;onv|C@G&kgzPq22o8z9)Uo-e=MC4wKLA)<5CutDNDV;9|bF)c0FH zXTOI<)(33glg}SJccVOap|{k3zV&=@uB?o|Bu*Xi)(c`$oPNw&D`H8UdCXd0mMe#> z^`cl7XCJfHC2>wX^_aDq;=H)t*qb_`+k>dPUZ|uZm}P*(Y56m*R`Uf6Q91MbFaF=jiC)5zm{Wp7@gfBwi3Jk6G)h zH<|ddc=0h4FUp@fv|C@h$;2hme9T0XzJL8D6PLwT9y9S3`u>fZOstA4kD0ha-+%fh z6Kmq7$4tB=Pj|0&f9B9`{me}!UKX!BX5tm}zx(>3-Fp2d6R(Qb9y9S8UH464-DAzK zj{bW6{!W?<@JyFjP|boBMu0NK&c%fr>ja zNo2C>C&SK;A0+i84OALN+kPY;`JFgQW z)*oV9T+~G6v6pe2b;$3rW_|RXyD}ZB$PfHPrhdHT4}x?j@wZgm_aE(qogM$({d6ae zu+Xte6F=RNev$@}2$b-9;id}I{#xDleLr^xN^XUF`J9qLuU&lE3=(O+bhsHxF_Hu$ zd7d$ru@5gE@RX%IaC_n7$DPgWyV;}K}FSvQYB#=(LAW97-Q-@V=O3#!ke~Q!s!ijW;=|w;@nPT+O%4yFrDpA z5Q$I(sZ4TDFQZJT!6sKennb(#{LGZRk2@@2eI1Ki+_e_?5^r$z!t^A7a$UyQTR8b` z##qY6JYz>(@LgLify1O$*B(1KcEB?(>|IY?7S27ER=V!EntB-*72zJ*NBqD_YnerK zTpisvbbM6=X>fJpz8cC6Kiu*+`mutWK~9EQC#jP!g~HAhTxdt0XJX-v~Ic=-Ze&S1h8!en~N zK+dLjxvMXx16WzTe4u4L*Hf2bG;%W83*509+~bbD%+*WNlMNay*#(+P88HDatjAtD zVFLF043Sm}B7Id8>E(h*GyXB#vW|GhcdhsmNOiXwUlJVehF~Ef?9qG@rt-=l=rHzyIeDWK_xo|AiL4;xYA@WxBs966!Ezdilt(2g+oR`ZXY_<^*|bzc z6YIVE*Kgl?v;FS1``^Ad-E#4n_R3cnV|%y(sFZPw*KIoX#0(JygL8p`Cp5dwLANx; zO!HaHNbrX_V}?M9;)YvW;hs#uZL5@--%nCSto$k+#NWGUDvhZrea9CQKZ%E`BmGV+q#sAV2$S7a zKURK%9rEstcdxwm@~c;r?8MtqIFfpc;19z6VbGINy5_%^41->8-%p_ygjzzBH`v%H zw$8?e59ShX4pX`6Zw}M4XJO(e1KA0;LMeQ?H|T|(5Yqrr{xA-O59tdf0kfc6E(WVe zdQAp-J4{H5m9y$&yZFU~Ye7{mCfE_k{T^2QB4cbDf@tZ1eZUVakUiMTFIyiu_q1GT za#drDHEqLi+qr}0k8Rc;)nD(0N!p3~gE#)cf>>QPTG^Iqo3xC!S@9`)Ew5-+6uOl+ z!c?l_#Hk%q6`{gSn-*=3+zQiU_s>s1aid>EH^2*6W0|X;2A43Bn79OPNCXX0Q4sVI zKL9g!#uYGQ2;k9A(UQHRwK*d`w9cSF33S_7$+o7dOHDo9O*gNG5mq?OYuhr_zvfjV zh;s+mkTea+FSkt819W`t3W{uD9R1vwrV> zK6mS#ckkT2-(J7=&W#RaGyd>i4*ywjJQB&f6?HLVhYbG(e}lcr?lICrto2VJ#UYkO znB?={zV`Ds+Sl&heD9qb>-U>hUV-|yne)!^-d54uKyCk{WhZ%^t6S98r|1C*o02q( ze&Lq{GL&>9*0l~T$i=SxOH<5z!f8H(l|v9^?89YTnzD}>l&q~|%2m-BqHXnUbd0N; z=-4d({U^~QbkAXD0%wf;YwvVZn>Gi7$}U{=*_ zOq|zlgP_{&rlsD&TC5ifGCf^1BmdN7mg?N)e>W!uL`Zro_(R4%T*Q2S%nsQx8X`(D zQv4@S&~N;%rbGQ4hK$)zclE-5&P4qAnTfE7`+(F_VeeMdzqR+5(rU&tE3*&n>+Hjg z2ljz;Pj!OZ{qE!x@po*+(K{-Fh~6 z0aQGfRq4H+)h@8XzapG1o0Kl`K8x<|y{qqeXkBL?zWKm9K)Yz)qx=1Eavy5_hoV9) z)=Z0Y7uY9!uvE7B!}l4fa&GXh#TYUDo8dzVD^Y6-n_byS{f$n%vFazCAo9aB@i+HV zNk8w0vPbe7hWN(D6x-d{pvHno_(77yosjgLfeK^gZ){9>^~Q!DMoFrM0JSf#5p6>q$Z-%54J_m%7gY51@>ON&u+o5G&C>aRnyiMNbK1Z_#%=%m0I z170fWZ(tPY{|1dDnYvM+ihD>gbC>dxtJLl3SzRSH4DI3OIFW3s%}Gmi^QpU(eb%=i z#=YLO)CDkb=wHM)KK=2JfBfTSgLqk90}D*!xR>O1Dz(VS=kKNQ;8uzigM|w#Ry~K8 zIlMrDQE!52)=1DqNt)zdCnkd_X*P&$w48VlrmYHIU)67~wcL0RsO?wG`>QQG9=z6a z0=4}bKE6sH>F6stj=pkSw4cTnAko$~UXp(aEM^t@#iGZTEQ@>G;|;#RU8~NYp>G~P zPw(e#{qAw~3)6ZT{=j$1`MbdwldQ!BgTuFFOJ`g;Vl7u#t!l_xUdUQCSr@i&9!V6sJ2u3b#=Bv(O+>K~ zM{W2mE;To(0U3}luw(}_oiIE1R^-1`IHFQNk+ProgE&dT%^p$yFhTd=mXzsEtet#O zGE~wJ{3wpD^n)-mJ)}Os5g56(YpYc#+4$DBG6sLg5##u+qCaWJ&nUXJwL64yY%|B4t4?l@Lo>oBB|;bYqQMC zSeq9vLMwMo&#H#Zpim{Ux1|Ia1Ux6l_pjG(W7gVjj~8YVclqetvEG=IO49-T!GBuf zOVpn+!$R&|gw~ny3+y`kuyJ5z*4V=FkCxxpMg&}$3dV+li;?=&wACg)qmy}+7FVj{ z9d+CELj&E^`mo0rpPV*2sZ@I~Wte5^W=0BJm#B}k(MHB}*EAJ^0=L-_Sr}s%W}l|Y zjCHHKRkf3P-J0N}l(24Piwo-qHh#mm*U<)jy1>SB7%m2z;a&}V`#=xfULW~|Z`=4> z!3zq~36lM&)3nuB%eer@O`7w0jVP=acXsn?(4p9Zf+j~(nAa1T3b_>wd#Rp67w*1D zC^$aznkLLJ+Rk0Qs4B$q3WXo?s**h!BrvJBaMIRQ#4xp%;jJi-*wg3vT|9<$*oRJE?;kYY8R8z z!%I0m>T)@qu9E%up;St%(ipFT7ycNjsUc(Rd5o1@y5(3szF-~Srn<#)n-(rdqBoe1 z=zIpd)y{0aK{8wW^SMgV4QE37dVlUb*`d5bRJ}SO{W|yf=*;Y7(>cx4WHtqT_-pHJ z<3nk;^G3V<(J<(lU#ji4h&%1Jim~R-?YOh6=kE97?zAz9c;-62AW5`b(NnsO6F@-J zeVQuuaJI`6llcK->~Db6;gIJ!mRE6X$9Akn&2t*Ax9HXBoYEnKKkQi&{9r57f* zd=#tQHJ^foVWMMioA44s&LST8qSi#O()?cCxEl{ssR}pd{T-5d`mW_Urv0_s@%CNW ziIv#!wJnm6Dr@?ygM2twDt(V(BqS`1a@^_O+U1Q6zY{AZlR+G5_aZq*L#>Hlg|qVN zpe#q8*VGfh1K#>VP9ANc!-wDv|hM z)aea{)b3Zgm#SdmGfj-Sad|S1wz=`f##R5lDAcx@3b(^3=#`y9FXJI1p>nz_MlTpB z2|ih<|I>||P$(KN)~J}jfp++r`PlH4+>%Nn9INNhG+ug_DXy!v|LHr>JZ@a|?-YHW znySKN)9&oz#ztglTJaT;%*6X^4#N^M6puj{BppY(2c-bVE(5nBP6Z!ZMhZm;y^I`2 zkcz3(t#n<%e_-*#1Bh_rR+PKC;bt|jwslW(x7`UkJ2IaOglL=l=BL_m)HXY)-HW%? z>);jN1lO6H+IxlcadlhA88Vp8O>jTLig1^^+~E!%ot@tDYfmUCko@pj(-?CaW0$Eq z)Fmh>aC~$rmX=w9?RsjEF>?9pqjqM~(eI@0xC+Tn$89|m!h$}K+1=Wfb&ef4X?;AG zIY-=~mT~6m>;UaI#`D5H;^PHyEpoCr2bBXit#?mlR=1WeW|cz@PA0s}$y~H1YJ!RS z52|pPWYzZR>0V{k_EP3#)%Kap8ZT#6Xep%r&8?5VvXWf&g^C9QupuxiGK7cGZWKR? zN>sqD>;8%uRVY@QyD+w+SAN79S)`+dOU3Dk`8CNeJ95$Riiv_&Nk7ReWb)@8!Y?4@ z-0g*tjN;txC)=uv1@`1#hXSn4vw1a4+Cm0G!5dE^Id>D)X@^4new;gRHXQWlP$(#P zc||G}tK7MJ`_4^$)2iWtUj*GeXt+hLTpPMJMaFpbFp zbKh2@I0J)G--BrOw1)8aqc5)Bo$%w*ey0Aksx%4hB0Xtx= zj~1t!xwN91O-|F^fa zAG20XEQnK&S*vcqO>+j^)R3O6$-11|c3SfW;;a$g}Z>9Vda&H-wSso#YiRE+uVm!640G;a|3xK6LJ-z3xzS^Vet!X(=q-1WHKTe*vA4>@XdDZ(b?(wn(%ATFA*6Ct>D+VNc#fT zM+?)}U1|~$ea1~eZofE9w$qoc6@*=iLENP@5FwjV25?o=lP^@!UyJyKD*` zgZ^euCVo&Ll9G-J2kDysHhEhg!Bvs~j<%1`WVoeOO|WpHj3A6Ap3iId-nwp?xn>XD##!3!7pk~R9`>?%HS zO}U<6R;6D8@uG|;9tnH^6Br<2!rFD!^BEsAT2-F$F&EAeS1)J0gy8TauD+A8am9S% zCInhp|MdTWtJWGhYlHm`y~gBS9ocJx{id6{3Ue~ZJsF7v`sY;q+Le1@k|yfUOc|hr zlbP^G9RX|)_6c+Y7V^uO59~|^rN>(2bqjLi93Nenot3F;%K3mGE$JI}FXmy_Rt6o2a!WPz{eL(8p*v;%CuHMYpxQb87+jN$f*}^%r4xCXVbH=qJc2Ggw zBy+}fZIfqqRuPpg7`tw8p>U?+@2>0st7t;#gaa|&&DYlUU{onYBcwpABaDC~=*d+2 zQ4GhoA8(c0Li?MgA^=6A!wF&Jix}z;oO?<`Nd{8&g9!HwlKp;PrfT1qA!Son`?0vX zVc?C3zob%a1s$V5lT{bP%Pakjb{n43c6+1Xr!Zm^xq8dy9!bJw8tr@f8p3)2F*3FH zyrRi)zv?Smwy~|<CT% z$3l}Cg2@WP^fe#<1;4TmK&zWBUt)kR$SZpG7Y_EVXv)>J1%wOv!UB_yu-g zriA}`HvlghI9a}Mtw3c_tN5*O^iNx= z#=mfXe;{*LM({N>t6ISQGq{P;=dt#N!jW#0_Ad05t0JfFja9YCD zO)Hs|y4~ubm3)nUfwX`#z=jDu_5ebKVM~xyJF}B_iP-CzC0s~{{e^UH+{i4D`ui+; z)?w-VZe9IBy_qs6`F-DKqIyu#$qH#@YLkpUDxd8blrbMk~ixHQ7%J z02{W4q5QR}F8#ua`VcX32qx0vQa|vO+#dDf><)?pPhXK-%y)*ZdGN)L@fBv<{*6PZH9uCCxH zX)XUMKAj{^p8g55@z+7;7!j)=C=6PLQM^Rb1af47FW3kv)HQ8i`iZnXHO(b`!vZed zK7WUF9#HshRV~1vNUhY)_#qe8Auuo2fqmf64sbM`W);DAz2sYe$OF z-P#d9aQA!K;gh-B8aY!1;$-yYjEx%^Xx7WTfsX_CvWs`l+^tb1`#vm`vbiuyf=D)f z?F%i?xoiI2BJl<4Qrv^%c}mYKFApLLUavv93(~bD+#@W=pf~Jq4*P4JxEHIb-L;a~gdi><`u`L&eCf6}9RYdo5|6pV}NyfeF5uDay(glnOsZ`K=W3^ za`Y}ExzgaHmww`2d-6UV$Xop3y)?U!kO2r7ST+KD8Gqea^1;^%NEQBWOwi7S%Q`3( z9k`Nx8Kr7}6@Gd|TtJ;e;H`bwf5SmZPC7ni5_dMGJ`Y~~@{Bj-Bfx#213e6SLvqV# zVabi7wK*#M=7U$y~)+DK@?Rcn`)wLX;E}+ zqntq?LMwgkVg_Hr&LEHG>5-Wy?Y&z&;p9@Hz)OVxWSRhospHn#G|4QroLTqS-WmWd z*MT&)GaF_Byhxd)o_Uvj^bGj^f!3~!4h8In-W<5PX^;Nq=eiF4je=@6R!f&5Y=kfp zfFYK%HDxYLQ=iTlNM@JfK45*&{x>ceiUUOf9z*F7a1^a#QH;*~v&{_x*_jnwk0Uwp zRN*hw85>0xf+&hp7$D?8oGfV5USD4yH76!Vft)8}!dylJ(=9bCwEjAFurzg<+5HpU zr|>#sOVU&Mnxq% zuoTHc3_810X)bXD(h4F!=!HR|z1e9D#24H%@H_`m91-7yU|X9aPLK(@vX)<{X zjfIg(vBY^(-8GqN6FON}e-XmIR=SHRCPg~2`paMl)hRhojuMjYf1D(2Pez0_n&BEB zL#P6lo;#ai`UFDjl#)r@dnn=K#!d)86VS6+1yC)|YLLUDVL8_5nHhRGc45gr3a}L7 z8ib78`!YFjPZdD#Tp(CsMG4J z+w2|y$Y`J zkIupCG6vAxJF?7iD9rBI%j$&Na>pK}`vD>1Wp(8I;`m1&ysuezE#qlDof94qB$=yo zf)Du-7;!E5wOMzi$q_D5zj1U$=ffIWHlR=_7-bfl@l7H|Q*MB1<~`^QX^Vu&R6_z$ zGR3EXilDoNi7#X-RX>b$xJSuUh4Nvcrx!eyGT0{S_4JLPVu$3x3=QIXqUr~|o<9Xp z_v68IFN}&&NSv$w+hhj-MWnOUKvNXoiu`uaLzG~@9CzEyO*we{!6?vq)zcjZgnkn+ z5E|MP1G)<9b?W{}I>Cvr=Z@Ty9d#ePLC3O3&PuY9l))qrDp)Y(ZVt}e43)iyih`ynNgPIc#y)&?l8k!91>3DDW635Kv6HH@0N5D2 z*kC3p)dYZRNw~KL>|63AyF}jXlt}~2xkzInB;qVSUMS1p2-m~Xa%nRQ}m0XD$6G=!ajxqq#0{aCb5O#IM#yI;LF_OOZGXt z&K(#?4xAG%A3gWE3tKWx@LDiUQUic)&G}C~WXAAG+TNUfg8DcV&5N-v(@&A~Cm3*t@WK+o*2uzzS z-Oc?OF7r9;Y=TV>bqw3TI@rGorR?g)NsF?foo}dmQ8ExEJ42O(4-x-1)>T4DHd>*8 z9fpbx(^(y}y_{mO|G#E!q~A1aI@oFt;(_{WAmRTEFMkpnp)!oZILh7O0A&i06q|Gc zCDea@%u`1Y-_CAcAZtz5xAqHsUya)q3;H zX9Gb90|HO8cBlMv`1G&g<=@21zl)cD4=?{dUcf$xOAzl?-mJQtGtbv=H6VHk-K4z__gfE_Z%v$Hg^Wsa7S?hv$;T~(Q``HXcJ z(o@~V?rGtaAi>JI*3J<&0&=VaP zcPBr)8Juvo`absH0&rmT6>UOzB>Bt zYsY|C2GFJ9*jlPig_d^WNAe*d%}wG};d#-So`r`_BYun8Q9iZ?WS$SzcSvxE?O~wA zn*Sb&0)$Y>LML~rKa7B50KR#0!D^Y$MOPA_%wVg-Ex#C~|IKguFBSuQapErelD{7h zmCl0&=wYos!)i(ouvWLb3`}L$Qp>_3=28*_)D==qZNn-Mb81@ZZ(>m+{h6{e;KDh! ziuq#67ffZPzl9~$W-U~>!mn4d!?oiVoZM;3s-pBDtxuKUH}&8Dj=EKHF4L{wE-CGV zE*(F)^c>B&8Yd|-6q>R#TlpE3eH)oe05id+78d!3T=g>S@05yGbYTQooTaE=$G^{M z<2j?hFmW*&6Rg0s6sv70an%P(rf@A%#;+evs+MfZ?J!D!Eh|fiO=uF8rUcwaf&ctx z64mo13m`(&=pCI+V``Yey}Si@4<+vn;xJ0r)<^RbG<+CcYE_loLS8}B%d78PyS3iF zdn0$XdZ<3avSB{8Yw#1|+k{4;TebmIM%bqC7ZUUMtE6G|5$`=tc&8#cOckXU5~$@z zSYTcC3C_Yrattl-0<1Qnzhl?UkYDK~L_;WoMgRC#g^;_GE)=rlq#f~88;H_&B)2JZ z8mWVWI7*~vPuTa1$9dgnTr+z1LGqO^JxDG;XD0gUgXD4{M1!q~PaIGkY&voXZE$q{ zW7cK6u3E-BG63(fly|MMjhY)rmb#oef`b@8MzSYasYU!Xvs9E;QFTB?sW+|(i>|Rb z`d{uB1f=;YQ}}TYkk7i!pCfU2C|M^ zycqd%dUkv+vqkj?2ng+x0y^2F8F&hOhKknJ(({N&XVvsP?v#01ZL4xnnH_PnzTTQE z`GypuF9SwH^iTev*iWQ-hyrz})mBK96*X#7tuE1~{C(qIC!a4I^T=IFi=d8)uZ}Jr zyJ8YnRSG)tr*IOK$_Eq#PyzsIRC$Cd2~kNqBw6UyPaGX}pROq6OsP$0QBthX{iHBH zk|(?oGpJuuq<)Gq_H__Cb~+&RqnDo`b<`>|1$P~o07c1BxC^&=c~(y%Yp8#w)E@d5;e`k(NE;zR2Hq;uZv#hbY^ z>V=!g)9*v@C)&&*2TXlR{vA_l{sUZZr7!1m6gt)#LtZZ$kwX0!xD9llVz$Wr(U_iw zdCVBZ3tYi%8U*hL1#Ee~Xdn4&{*{bu!3g(;s!#Fg~ z@Co0WzF{fg!0<|+2<%zxr!L#As&R&bc-Sd6ii&leZY8rb-gW6)Rq!7GsX^Q!$GY?51^7@nl6Bbl6k3DFL^u~1dCdMuhF!iWDzv*7>D%d|9lhhD z1v6$ zG|%J>;C+nwk*FHztR%a<`i@Sf%d3O1v)hv*uR$cHvK@+ij!J}R%VLTS$w&8Bu;PCV zFO*Hw(7D7qGb^_PA=KZ(kN-1X{x%3hM^OpCErY(6Dj+P%c9}>WO*yCA7$?;MH9dbD zd`pW_Ysx4_R?g{N$aAda_FQU#<)NfqB9+rc3PlGX&_Fe)@hG{MF$ z)-~hZDp7`qRP1S$66lborRtbmvNvlqXr#hNVlv$dx#VgdFWAg#0iRBBs&PizC-R+7 z6u9U0K=i8XdC}0t4=^vVzZhR2kcEddH3MNfJ`Yb`UQP$5qJ(Y3vuZYps}?|x`wu2t zW$#;=4HWEk_Tg6#oYcx318k}sxCd@l$xw6Qc2+T)?Dj`5WDMmtGY9(NM=u~ELf`RtE9!oe z6FjErsX0t5Q9g#L&TwR@-`MNO0hKm^DA5?Uvsep*vz)92ktwJ?cXjCG#0c2z5(SYc zyziS>B0@0}cSFL}0woz()*@e|&!4kkQ;n6X9JpEuUj7wQ<$hwS;R~3wj3eFf2mofC zVrX@}%!bxO9H=HNqW)TDXI8hq>nf4D<2eCzfktOGP+s7`87*dPJg+s6qV#|R&kX&Z zIYyURFl7jw_0hQ%)D@ysp)$J&8GeMTTAlT6TxwqR#c)uZPZk5Px(F_>sOBm411y;4 zJ4p(5Vv_h!|4%a|N*s|evwZN?c-P6t$sKfIS52`m~LD6_iAp7=(Fy0yfO zQWv54F3)Vlk`6f?t>WBcTnT|Cwvse$Jtg2v#G#rVF&+qT$}EI{S;iPkv7q@B`jamd zIkU#WWU_2%c0hF}a;s&{t%@vM&31li!l_`qn0!M6B%Yca;@Gp7&TuKLDmi?BI-Qu$ zW%ld~sfAz%05JvFC9`3xVgqcm1848~%u+v}Ib#qt!Wa%LTm#Ll98^B~8pUkMVCYr> zzRX<8%l(5!6(GF*1(+B>{g@Yw?^W8=9D0>dKS)yUie-2BleCc^IM z2>a+YDAZ*Ftr;{ToGVo2DT@7G9BoU5+BiuH-0q6f-uiikgasY`LXs3YF?75h8$(^h zf!XOwKQ=V!n|oj*Xx-^yrT zj4QCWNbH;|!40FQkuxw$VSr4N&CzrjHLlLOJU~v=VnFvB=k(j^>T-riyo^$}7mMQ3~}% z3K$11_j4HCu|+*~Y+C*tdTji$CM9mNRgay!-n@7`AOnq5$70|K%`GL>Z{iTK7XFU< z-|*#s$IEZy<$uKstQD;uadeQxHY7F)XHB`_zC^xyg)lO;GCLX?7ny7SEo1C2!f^r! zxW~O`Jlk`ZEXVUIp6xAo_JZa8w72Bdy(RCgSNG0)6}sk$fDQf<7+JF=LRyZn5v1u> zyI!j%Y6Js8MX*|{PM=W|%xlg4A?C1F17+ZI_f1-v;FpV%A!}koTr2;`kEd62A@PGQ2h z_V#tQP8=0i`;jD1ZkS9ZZoX5rQgj2I)YbmCh6d1z+#o1nRI($HDW%*|+OU!l9W6Vi zQ#)~TFWi!_V-uZY3ed=)Cs%>c9d7DGO{C+&khsF_L} zX%~DyjzZKrOm`;TEXG(u(=^H(q8$2 zinmqJUj^DXOo)2Q_RBy30d`@lT!G7iQ1@^gAr-918ou`KEm|Esl>);^kYrZ2L=SW5 zAV&oi)>5gko)2Y_!a18$hApyB{oj;6vV;8-5^kt&5vG|iz>_7=@g8GEFMn(OeyMDO z1A=Ff>k+7rjH`=4SfN9xPI8)lH$PIXXuE+g8I=mzX!z zB^FF|iBqP!#1kQYk~e4WvF1~`t4s9+zu}k?RX0!tU{dI*h;UHZB~uwnGw<1rKTL?n zr?H6>COSz01!iZ8LI4^Q`#@4De^b))A9P?bS@Yl0wBFm-xevr%h+h3rqsfa$+33or zy1ZafZQ{km2ruxP;b2<@h&2YOuZ9uU-ym*&SK`!2!@mdO6!WC-Mv}GUaHQO?O(bj4 zqtBPDDA$xqKUH`p4TB1wfmSVIYMB!jawHCJhFxpaqRx41ob5p|1AdU#1 zzHYT_uMn6CM9DzKs774CHjVTw2*?zS(oDr@2$Nv0Ih@8Iq%{at2%1U83$id&_z)v1 zN?_?rOwJ4?*PA6aHx8rJ?+1HDl^udnOm27wrT0Ri1!Oaptaf6t(rEl1>XsIwTKfSEc)1@;lm!CgP99)IA^%{2CsUB>rpM7b{m!lXcW&NT zZ@+u@&fB+c-&()9K4Tx1;tJLw0-L*@f}e^VQM!Y=sB4!}HYNl#!NSQzuS| zPJphXj-qL)g^KDKYul+~K2?x4+VwIf_z&<)&6E$FOWkgxJCA~faH*17;vmBbNTK^p zdjgfps{=gKC`p?O`P?mWtw-^?SmljSw1eUsSxAT#P&q%TsESfP-;1{k6IQAzA>5$R zy`weae4c2dZ4_h;Z<>cwOvp6oKr_?z+G^Kt+`snLw{NZAEW%2&)~fm`piVKJx>yOK zNASOC?Pj!KrqnFYO!Tl+=HM*R4|_N?bz|Y2CW@iTC9k8#2p9GRwv6wN`Cc*K-Pdun zdVvY=X?&^`-%}^!qJDuL^1-H8M+IMLB^K7n{U81f+r;f!H52CR zC*+xI%}qr_FA(# z+3mWtpy3U24@KWFKPZTw7%wJs@#!THkBz^?)NDvHOcWS#SQ+hhaIi91v<-nSMC=zHH*K? z{>E_v+qmlAic$%eClTCq^&S0ZvH%N>gH+ZD0CJ-c?}#>#0G~<##NX^af!N%*>c5S( zgZe|5A59~EA^|p{hb2Xjns7T5RnjJZpVu7|^O#C6D|&I_<~U*eczPoq)7*4*$O)mPW6^K|XEdwo zr=coPT;jGq@*yK-UZ;radJ)hf=(z;; z=oib;y%m&|{!OfiG(IS^LM9yAcoO8= z>w{3dvDS;XH_Y=Y@OK|R>@pgaFUtt7oHjLxnyxTabisoYXD4q=U+@m53G$v*>U?y@ zOwaTgGcyFHN;8B@GPrNJNzFR>UbS{xqIg%k{T^;)eC5R|lt0cy`N~x&Jo&mJIT*Bd zuN0nqTPu^~NkOF1L?3cjwvnoqS7iHP(95lK^O$Oc;w=zS?e+)d@KN)BqGsw}pYU+e z(4l5gXNtPK3?YB(tr9khKgHDBk=vOIB8xi*Ch zjyed3)ZR0?i$?$=EFAD64V1Hw8Yb354>84qGbb_P+@*31iTW-SbX|h}#QrW!btUNa z+LLKqfhtyp$?&p=FSp3f9SQ!c7GV^Da7;Q(LqaJa4XX(R<%`JcA&PMjFXDu zrqwPUd|;K?9rk*GubJd?)X8wktMs_8P>>&0{e7^;96YqZq)fz7VX*+uG{sTouHC+U z=dEk^Z?tb+FNol?awa5JUL$P4Vv(EY@X_*=X>_vDR&Mf>vLjASU2ZZXaQ~Be3`NkQ zsoiqy<~l2atcRKHQsQ|4e<(H!XQT5d8-Weuu&yOE;K@S4Njk@dK=?pElFAO?LoIpu zHKl@Rn@Yv|0nq**4RyIsxtF5Ka~hM|T^os1PjTkSQxp!;PjSJY@eoM!v`PJ}%z#P& z7UG7g%^BCB_6Od&4o^#wIX)4@MV{a)q7#Lu#RMkGh&DQ?eg5=>Y&`gS(w&pEsVOok zwW*1KC$fa7x{HlS4}thOe42XNLvDw{_~B`z5-!p!W(YDylU7xydLlGFySl}x<-)?t zQ7$Z!k*5~+8`BF5=RBc#fF5RD`zdx{0jfV8Ge_iji&E&jZ0wqaHyON70h?fM4-M!S zR>a!cifA^g$Htr2-@JY2t?#z)-D*w41Zfsb*?{*rV=WhuzVLXx>U^OXVtW%?_xqTW z!k!1#6E)}-xo3?oPVJd;Ui3vJubIJm!dn7dOi_RxQucv``dgSMK+w9#es#zq=b0%X zgtCemA6J2H!UK4X;Ir08UwC3h)Y=-#Kcz8nNuI_feEKT88?=@p;pL24+5 ztRK9yWY+=xUHsPK_t^K?&n%_>S1rK!6>KLP@_l;qGDMjNEIgjR*a6qaD6gbZrU8B#0m z&aP%h5-H3E1>^=j6+RR_^&kPg6v(-k{uKdo+Ma@(^e^P%;?0nf-9wk)eC?Yz@8kD- zvkw**0|0>kB;W3R{RY5yIGH~_0DJ-f#h(Fy0V4_YVA^KDXhKrjqXc$Agf4Jr9LReF z@Mqpq`}PkY=;wB)+pBRn^q4aS*<--mq@MacpMdaIC7P4$m7`uvaD6=&D{Zt)fBHS;sV>#gacfwrqg3FUwjKU-qJP5-u6vyc^ zF0F!MDpWKHvth{7TusAgNj4Z4Yk?8GpGPuE!$Qf>D!gx&@!2`xv$arVVH%4#jnDXe zpXBOTiUN1OzGW`RW}DZH!p=ffPh*vhxTxGgCV5p?Szqyqk{$q5w?3}fv07(5PqOLVXHmh|XNKN=f9=DykLI&=bZj_X%cs??xsQFTdkqoLL|6n((k8z7 z$z=ez1`DzR9UY+PUlISLB#f4H@}-6ic>2X&t#nFvkCwI4+oNT@^vi~J5K;;b>5z^+ z`MJjN&HQ(amY#4mEj{fqX9Y@MESo*fn94-gm^*f5a@5jJNwi;5?Pyomv^S)>uKgj^ zjbZHx5IAb>g5g5#?5ho#<#ParHe?oeQMf;k>ON+jN9ha;59e^rgh5~tesK50Fp@kR z^IY9Q^vp2I!-J?ecovC~fi@R|jKyMPA+jT*I6)Mj9>j_lc{Jc*T!hkc!eBp25_^K( z(Ur)>D{Q2LG<7YQX!lW7?{4ls+Ujmr^@pE*`srqOrxVB@06@>rB|qt_Y2Is^5f`jK z$dbxY)4Xz2Hm(|lilpkx>~ucF_jMG+H8a2!7m;buWSck84upXza_=A>JeRoSUD76P z;!&3bWQkCMYew!G9atIhZ_x1<0x*J-FjCSNWby^#;{|!DN=G}4u0rW*%E*|$Ao4)F zuOZ@rb}*kW$kCVP4dMqSYC~u2Nv6q#r=3gkibUP$xp(tM6Fh@CtAM>G%v}Xmvoq$c z00l(oeXS8$Byd#hlznH99bt~ktJg_-tmcG1QLYy64+3dGutACqNsW)shx#pJiI%CexO zM{P}6Fs8C?qGe<8M~u7ObJX4i*4T#5!r8wc^E^RBURRtsIZk7B5OyEUr?L1yO!>8# zhTA)jx4OTv;-Sb?m`74!b&f>HCrFec&IBT#ah`G(MmJ|#*l(FK$6vb|kZq`J%2g=& zARCFe;LOVR7EYsB_3bTUC}Y@i_44J*msiLe`6d=y5?tYR*6lToxZtOV?@q&TO1_11 zZ=;;IvAMC?-Ko5-uI;Tyl^5kX7fi08P)$Z+#5=y>TvaPnG#Hys@ro$9iZX%anW^tS zvdfG}{J)f11b`=4H-B+WsTT1`K-wfA%cMn`Bp^R0ZF1YpKOs#TkN|MiBR**&-B8E( zBpl$if)CR4x*q=v4C5#xrNc<+PX1wP^56GrH`RELmW?tf7jzA=KBn@CrWe$*Njcg( zC#9oZMn6J+674QS>4|rYv^%CU*Yu)x30CGFl&L3Mw3p{rq#1rdO?=3e!@?9QdEuWajzlx zsa-btmRJ$x53zX{n;&6w8=D_vgSYZ4b+iQlQ%rh@>2k1UVkHeoU~boWzhn|@q&q~e z<0Y_IImNW7Xq?%Ph=r{*&!pOvGLuzvcDJwLH;BX!ysCM^B~q>*i(yu^ZUoXivR^0W zDmN9|;K-czioM2iEdG`M%QR!h3I7;9Dv&KcrKC+-WQqDTAT8=U{*wQe|C9d$BrVms literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/parent.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/parent.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ac0d87e1b9297d28bd13141c982f85219a61c63 GIT binary patch literal 69048 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/service.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/service.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6918dc8d61fde2fe7b71d7bccbba24c040bfb8b GIT binary patch literal 32895 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 Gv;H3$=&Qg0 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/unix.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/unix.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33d1cc2c64c6aced9308ca0e5a1dd05d65a4c334 GIT binary patch literal 4383 zcmZu!TW=f36+Sb2;c`V%lq}2gCF!E6QWJ3~H@&1V61A}{CvFrvwG_l9QY==Sp|s28 zE;~E2O=9&D3yAU%pg&>x5TFJ6i>6OSpWN31edtqhW=UFdLjq@JcV~y^_I>m5ZoTdT z0Q^1v$5($@2lyvm%s(ao{1yPRuK|ET7Z_nQCS9UO+NF9lx`rOjuBk_>Yw6MM+Ip;X z9X(d{XvCGF+jR*rlUb+GU1Bz?oIn-S^5-)1m3Wh6X}gaiVn2hmsr70izs9F z(Z@Y9tET|~4glQ4k{$togaRZ)L_$QT^n^@488>oL7&%M{fiEHXg^AD2fh+b2;8$9O zJq1Rt!U@f3RAKZgNGGaFCvy9AVDG^b_!1c8q*$5)fhWL>J!qMom*|8*^Pp8zc9~XH z`RIXnkM4cy@9jU{`RFrMnN?OUCBO7PR;m>=1nDkR+2I4T&&yrNtcJ4ju zbRO?K_+oG8zOr{Zk9Ioy%Id{w##@FmM}a(2BvXX{6~rX~`0uZ`j?y9DisE3rbr{Mm z#z%2Fe)k~A_|`C#=^;!F>o^IYw?|`D3o{@0^BE7=&oPOsm|4;!lu*(j zO;RH_s7u6U0O-v@gdYTw&U!;`J1efe?i{v-UT_xxICKdE zM0VF;lo_YcHCa<*lyt2Up

}61o*uWA#($I;_E#PoZ07<{q?Gl(QRVk|$j3FM1Cv zzsLeQ$%up;A`+pcePbQ=phY{A>Sos7%x-RqR?AS1?x93}&09)t-~R_|z-GG&B`RCcUOuPU>aR%S01T$y_z^|zlJ|RLZ6hSF**V_NLQ_M7e9@vh69UQR z^}ri`S&13g|A^t^i`ex|(jYb3B$F$zI{(7zo@N999XfibDe9)~`~u3}%Ap`gwg`VbCaV;ms^(p4yI?{zMUbVQ9o4JKox^Ql1e?qr+Un2M?ZzFN^ zgD~dIlc^_oz&yc+spMYBcp^hNzT<6Xx{D_beMt4^ajcly!TWf zI7_m^zC8z*A7h8rLgoi?oc02l3gyhET-?&_HwsW7smi@l8j5%I-90IIFjOXq)BXABTcP59AZ-hJ$`MnN^H$~ z=fQtrzXf)%0~Wl8*~%f(p#O@V&=X_Q$f2M)EqjweKBIvdw~G|pZDzJXebPA;7X{QOLy9pT~TuDF3)y1qY5*>Q{lq2~L4Iu7EQ zTyO`RkNhAGpK|dd^y!yqG&9{<__>cW)P&~@N)vG%0PdqlNWTs-sZ(*6ovLFyH}tbb z?vQsX0Gd3r|CEZFAVDI|BrT|we?JI5BgaE$7A|k#%36O3ae=sF535TGXSJXf-Fy{*HVLvL0P3*3JM~=v#6| zipx1I)~8sv^^y%(B{z|o&WL|S)}xK$YPtHvI)ma`Zk-YBJ??Y;45Ay%`3AlreafoL zJ#{q4(Bcg|3eF(6`lw--I+M#w7XNE_?85D3vreqJWM~PZtVK(#5 z&ou6J)mv1ck{7>Uw1HO8&IfTYJYd20_W@A zI=Mz{;!vBov_Z7cqNqD+q)8p3-@yW`sEY5iwCDS!u+cft%2Ve>Yo86obZsUa+o&5$ z_HN8?l_@BbjsLMq5v#d;>28pPy^_#1M$63q0J@d)K}j0h=tG8ohfL~_#TVzaW;k`n HT-*I0F-#qU literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/__pycache__/utils.cpython-37.pyc b/mitogen-0.2.7/mitogen/__pycache__/utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6042a5bce072bd5780dcac0fb3c20700a48ec903 GIT binary patch literal 6008 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/compat/__init__.py b/mitogen-0.2.7/mitogen/compat/__init__.py new file mode 100644 index 000000000..e69de29 diff --git a/mitogen-0.2.7/mitogen/compat/pkgutil.py b/mitogen-0.2.7/mitogen/compat/pkgutil.py new file mode 100644 index 000000000..28e2aea --- /dev/null +++ b/mitogen-0.2.7/mitogen/compat/pkgutil.py @@ -0,0 +1,593 @@ +"""Utilities to support packages.""" + +# !mitogen: minify_safe + +# NOTE: This module must remain compatible with Python 2.3, as it is shared +# by setuptools for distribution with Python 2.3 and up. + +import os +import sys +import imp +import os.path +from types import ModuleType + +__all__ = [ + 'get_importer', 'iter_importers', 'get_loader', 'find_loader', + 'walk_packages', 'iter_modules', 'get_data', + 'ImpImporter', 'ImpLoader', 'read_code', 'extend_path', +] + +def read_code(stream): + # This helper is needed in order for the PEP 302 emulation to + # correctly handle compiled files + import marshal + + magic = stream.read(4) + if magic != imp.get_magic(): + return None + + stream.read(4) # Skip timestamp + return marshal.load(stream) + + +def simplegeneric(func): + """Make a trivial single-dispatch generic function""" + registry = {} + def wrapper(*args, **kw): + ob = args[0] + try: + cls = ob.__class__ + except AttributeError: + cls = type(ob) + try: + mro = cls.__mro__ + except AttributeError: + try: + class cls(cls, object): + pass + mro = cls.__mro__[1:] + except TypeError: + mro = object, # must be an ExtensionClass or some such :( + for t in mro: + if t in registry: + return registry[t](*args, **kw) + else: + return func(*args, **kw) + try: + wrapper.__name__ = func.__name__ + except (TypeError, AttributeError): + pass # Python 2.3 doesn't allow functions to be renamed + + def register(typ, func=None): + if func is None: + return lambda f: register(typ, f) + registry[typ] = func + return func + + wrapper.__dict__ = func.__dict__ + wrapper.__doc__ = func.__doc__ + wrapper.register = register + return wrapper + + +def walk_packages(path=None, prefix='', onerror=None): + """Yields (module_loader, name, ispkg) for all modules recursively + on path, or, if path is None, all accessible modules. + + 'path' should be either None or a list of paths to look for + modules in. + + 'prefix' is a string to output on the front of every module name + on output. + + Note that this function must import all *packages* (NOT all + modules!) on the given path, in order to access the __path__ + attribute to find submodules. + + 'onerror' is a function which gets called with one argument (the + name of the package which was being imported) if any exception + occurs while trying to import a package. If no onerror function is + supplied, ImportErrors are caught and ignored, while all other + exceptions are propagated, terminating the search. + + Examples: + + # list all modules python can access + walk_packages() + + # list all submodules of ctypes + walk_packages(ctypes.__path__, ctypes.__name__+'.') + """ + + def seen(p, m={}): + if p in m: + return True + m[p] = True + + for importer, name, ispkg in iter_modules(path, prefix): + yield importer, name, ispkg + + if ispkg: + try: + __import__(name) + except ImportError: + if onerror is not None: + onerror(name) + except Exception: + if onerror is not None: + onerror(name) + else: + raise + else: + path = getattr(sys.modules[name], '__path__', None) or [] + + # don't traverse path items we've seen before + path = [p for p in path if not seen(p)] + + for item in walk_packages(path, name+'.', onerror): + yield item + + +def iter_modules(path=None, prefix=''): + """Yields (module_loader, name, ispkg) for all submodules on path, + or, if path is None, all top-level modules on sys.path. + + 'path' should be either None or a list of paths to look for + modules in. + + 'prefix' is a string to output on the front of every module name + on output. + """ + + if path is None: + importers = iter_importers() + else: + importers = map(get_importer, path) + + yielded = {} + for i in importers: + for name, ispkg in iter_importer_modules(i, prefix): + if name not in yielded: + yielded[name] = 1 + yield i, name, ispkg + + +#@simplegeneric +def iter_importer_modules(importer, prefix=''): + if not hasattr(importer, 'iter_modules'): + return [] + return importer.iter_modules(prefix) + +iter_importer_modules = simplegeneric(iter_importer_modules) + + +class ImpImporter: + """PEP 302 Importer that wraps Python's "classic" import algorithm + + ImpImporter(dirname) produces a PEP 302 importer that searches that + directory. ImpImporter(None) produces a PEP 302 importer that searches + the current sys.path, plus any modules that are frozen or built-in. + + Note that ImpImporter does not currently support being used by placement + on sys.meta_path. + """ + + def __init__(self, path=None): + self.path = path + + def find_module(self, fullname, path=None): + # Note: we ignore 'path' argument since it is only used via meta_path + subname = fullname.split(".")[-1] + if subname != fullname and self.path is None: + return None + if self.path is None: + path = None + else: + path = [os.path.realpath(self.path)] + try: + file, filename, etc = imp.find_module(subname, path) + except ImportError: + return None + return ImpLoader(fullname, file, filename, etc) + + def iter_modules(self, prefix=''): + if self.path is None or not os.path.isdir(self.path): + return + + yielded = {} + import inspect + try: + filenames = os.listdir(self.path) + except OSError: + # ignore unreadable directories like import does + filenames = [] + filenames.sort() # handle packages before same-named modules + + for fn in filenames: + modname = inspect.getmodulename(fn) + if modname=='__init__' or modname in yielded: + continue + + path = os.path.join(self.path, fn) + ispkg = False + + if not modname and os.path.isdir(path) and '.' not in fn: + modname = fn + try: + dircontents = os.listdir(path) + except OSError: + # ignore unreadable directories like import does + dircontents = [] + for fn in dircontents: + subname = inspect.getmodulename(fn) + if subname=='__init__': + ispkg = True + break + else: + continue # not a package + + if modname and '.' not in modname: + yielded[modname] = 1 + yield prefix + modname, ispkg + + +class ImpLoader: + """PEP 302 Loader that wraps Python's "classic" import algorithm + """ + code = source = None + + def __init__(self, fullname, file, filename, etc): + self.file = file + self.filename = filename + self.fullname = fullname + self.etc = etc + + def load_module(self, fullname): + self._reopen() + try: + mod = imp.load_module(fullname, self.file, self.filename, self.etc) + finally: + if self.file: + self.file.close() + # Note: we don't set __loader__ because we want the module to look + # normal; i.e. this is just a wrapper for standard import machinery + return mod + + def get_data(self, pathname): + return open(pathname, "rb").read() + + def _reopen(self): + if self.file and self.file.closed: + mod_type = self.etc[2] + if mod_type==imp.PY_SOURCE: + self.file = open(self.filename, 'rU') + elif mod_type in (imp.PY_COMPILED, imp.C_EXTENSION): + self.file = open(self.filename, 'rb') + + def _fix_name(self, fullname): + if fullname is None: + fullname = self.fullname + elif fullname != self.fullname: + raise ImportError("Loader for module %s cannot handle " + "module %s" % (self.fullname, fullname)) + return fullname + + def is_package(self, fullname): + fullname = self._fix_name(fullname) + return self.etc[2]==imp.PKG_DIRECTORY + + def get_code(self, fullname=None): + fullname = self._fix_name(fullname) + if self.code is None: + mod_type = self.etc[2] + if mod_type==imp.PY_SOURCE: + source = self.get_source(fullname) + self.code = compile(source, self.filename, 'exec') + elif mod_type==imp.PY_COMPILED: + self._reopen() + try: + self.code = read_code(self.file) + finally: + self.file.close() + elif mod_type==imp.PKG_DIRECTORY: + self.code = self._get_delegate().get_code() + return self.code + + def get_source(self, fullname=None): + fullname = self._fix_name(fullname) + if self.source is None: + mod_type = self.etc[2] + if mod_type==imp.PY_SOURCE: + self._reopen() + try: + self.source = self.file.read() + finally: + self.file.close() + elif mod_type==imp.PY_COMPILED: + if os.path.exists(self.filename[:-1]): + f = open(self.filename[:-1], 'rU') + self.source = f.read() + f.close() + elif mod_type==imp.PKG_DIRECTORY: + self.source = self._get_delegate().get_source() + return self.source + + + def _get_delegate(self): + return ImpImporter(self.filename).find_module('__init__') + + def get_filename(self, fullname=None): + fullname = self._fix_name(fullname) + mod_type = self.etc[2] + if self.etc[2]==imp.PKG_DIRECTORY: + return self._get_delegate().get_filename() + elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION): + return self.filename + return None + + +try: + import zipimport + from zipimport import zipimporter + + def iter_zipimport_modules(importer, prefix=''): + dirlist = zipimport._zip_directory_cache[importer.archive].keys() + dirlist.sort() + _prefix = importer.prefix + plen = len(_prefix) + yielded = {} + import inspect + for fn in dirlist: + if not fn.startswith(_prefix): + continue + + fn = fn[plen:].split(os.sep) + + if len(fn)==2 and fn[1].startswith('__init__.py'): + if fn[0] not in yielded: + yielded[fn[0]] = 1 + yield fn[0], True + + if len(fn)!=1: + continue + + modname = inspect.getmodulename(fn[0]) + if modname=='__init__': + continue + + if modname and '.' not in modname and modname not in yielded: + yielded[modname] = 1 + yield prefix + modname, False + + iter_importer_modules.register(zipimporter, iter_zipimport_modules) + +except ImportError: + pass + + +def get_importer(path_item): + """Retrieve a PEP 302 importer for the given path item + + The returned importer is cached in sys.path_importer_cache + if it was newly created by a path hook. + + If there is no importer, a wrapper around the basic import + machinery is returned. This wrapper is never inserted into + the importer cache (None is inserted instead). + + The cache (or part of it) can be cleared manually if a + rescan of sys.path_hooks is necessary. + """ + try: + importer = sys.path_importer_cache[path_item] + except KeyError: + for path_hook in sys.path_hooks: + try: + importer = path_hook(path_item) + break + except ImportError: + pass + else: + importer = None + sys.path_importer_cache.setdefault(path_item, importer) + + if importer is None: + try: + importer = ImpImporter(path_item) + except ImportError: + importer = None + return importer + + +def iter_importers(fullname=""): + """Yield PEP 302 importers for the given module name + + If fullname contains a '.', the importers will be for the package + containing fullname, otherwise they will be importers for sys.meta_path, + sys.path, and Python's "classic" import machinery, in that order. If + the named module is in a package, that package is imported as a side + effect of invoking this function. + + Non PEP 302 mechanisms (e.g. the Windows registry) used by the + standard import machinery to find files in alternative locations + are partially supported, but are searched AFTER sys.path. Normally, + these locations are searched BEFORE sys.path, preventing sys.path + entries from shadowing them. + + For this to cause a visible difference in behaviour, there must + be a module or package name that is accessible via both sys.path + and one of the non PEP 302 file system mechanisms. In this case, + the emulation will find the former version, while the builtin + import mechanism will find the latter. + + Items of the following types can be affected by this discrepancy: + imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY + """ + if fullname.startswith('.'): + raise ImportError("Relative module names not supported") + if '.' in fullname: + # Get the containing package's __path__ + pkg = '.'.join(fullname.split('.')[:-1]) + if pkg not in sys.modules: + __import__(pkg) + path = getattr(sys.modules[pkg], '__path__', None) or [] + else: + for importer in sys.meta_path: + yield importer + path = sys.path + for item in path: + yield get_importer(item) + if '.' not in fullname: + yield ImpImporter() + +def get_loader(module_or_name): + """Get a PEP 302 "loader" object for module_or_name + + If the module or package is accessible via the normal import + mechanism, a wrapper around the relevant part of that machinery + is returned. Returns None if the module cannot be found or imported. + If the named module is not already imported, its containing package + (if any) is imported, in order to establish the package __path__. + + This function uses iter_importers(), and is thus subject to the same + limitations regarding platform-specific special import locations such + as the Windows registry. + """ + if module_or_name in sys.modules: + module_or_name = sys.modules[module_or_name] + if isinstance(module_or_name, ModuleType): + module = module_or_name + loader = getattr(module, '__loader__', None) + if loader is not None: + return loader + fullname = module.__name__ + else: + fullname = module_or_name + return find_loader(fullname) + +def find_loader(fullname): + """Find a PEP 302 "loader" object for fullname + + If fullname contains dots, path must be the containing package's __path__. + Returns None if the module cannot be found or imported. This function uses + iter_importers(), and is thus subject to the same limitations regarding + platform-specific special import locations such as the Windows registry. + """ + for importer in iter_importers(fullname): + loader = importer.find_module(fullname) + if loader is not None: + return loader + + return None + + +def extend_path(path, name): + """Extend a package's path. + + Intended use is to place the following code in a package's __init__.py: + + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) + + This will add to the package's __path__ all subdirectories of + directories on sys.path named after the package. This is useful + if one wants to distribute different parts of a single logical + package as multiple directories. + + It also looks for *.pkg files beginning where * matches the name + argument. This feature is similar to *.pth files (see site.py), + except that it doesn't special-case lines starting with 'import'. + A *.pkg file is trusted at face value: apart from checking for + duplicates, all entries found in a *.pkg file are added to the + path, regardless of whether they are exist the filesystem. (This + is a feature.) + + If the input path is not a list (as is the case for frozen + packages) it is returned unchanged. The input path is not + modified; an extended copy is returned. Items are only appended + to the copy at the end. + + It is assumed that sys.path is a sequence. Items of sys.path that + are not (unicode or 8-bit) strings referring to existing + directories are ignored. Unicode items of sys.path that cause + errors when used as filenames may cause this function to raise an + exception (in line with os.path.isdir() behavior). + """ + + if not isinstance(path, list): + # This could happen e.g. when this is called from inside a + # frozen package. Return the path unchanged in that case. + return path + + pname = os.path.join(*name.split('.')) # Reconstitute as relative path + # Just in case os.extsep != '.' + sname = os.extsep.join(name.split('.')) + sname_pkg = sname + os.extsep + "pkg" + init_py = "__init__" + os.extsep + "py" + + path = path[:] # Start with a copy of the existing path + + for dir in sys.path: + if not isinstance(dir, basestring) or not os.path.isdir(dir): + continue + subdir = os.path.join(dir, pname) + # XXX This may still add duplicate entries to path on + # case-insensitive filesystems + initfile = os.path.join(subdir, init_py) + if subdir not in path and os.path.isfile(initfile): + path.append(subdir) + # XXX Is this the right thing for subpackages like zope.app? + # It looks for a file named "zope.app.pkg" + pkgfile = os.path.join(dir, sname_pkg) + if os.path.isfile(pkgfile): + try: + f = open(pkgfile) + except IOError, msg: + sys.stderr.write("Can't open %s: %s\n" % + (pkgfile, msg)) + else: + for line in f: + line = line.rstrip('\n') + if not line or line.startswith('#'): + continue + path.append(line) # Don't check for existence! + f.close() + + return path + +def get_data(package, resource): + """Get a resource from a package. + + This is a wrapper round the PEP 302 loader get_data API. The package + argument should be the name of a package, in standard module format + (foo.bar). The resource argument should be in the form of a relative + filename, using '/' as the path separator. The parent directory name '..' + is not allowed, and nor is a rooted name (starting with a '/'). + + The function returns a binary string, which is the contents of the + specified resource. + + For packages located in the filesystem, which have already been imported, + this is the rough equivalent of + + d = os.path.dirname(sys.modules[package].__file__) + data = open(os.path.join(d, resource), 'rb').read() + + If the package cannot be located or loaded, or it uses a PEP 302 loader + which does not support get_data(), then None is returned. + """ + + loader = get_loader(package) + if loader is None or not hasattr(loader, 'get_data'): + return None + mod = sys.modules.get(package) or loader.load_module(package) + if mod is None or not hasattr(mod, '__file__'): + return None + + # Modify the resource name to be compatible with the loader.get_data + # signature - an os.path format "filename" starting with the dirname of + # the package's __file__ + parts = resource.split('/') + parts.insert(0, os.path.dirname(mod.__file__)) + resource_name = os.path.join(*parts) + return loader.get_data(resource_name) diff --git a/mitogen-0.2.7/mitogen/compat/tokenize.py b/mitogen-0.2.7/mitogen/compat/tokenize.py new file mode 100644 index 000000000..0473c6a --- /dev/null +++ b/mitogen-0.2.7/mitogen/compat/tokenize.py @@ -0,0 +1,453 @@ +"""Tokenization help for Python programs. + +generate_tokens(readline) is a generator that breaks a stream of +text into Python tokens. It accepts a readline-like method which is called +repeatedly to get the next line of input (or "" for EOF). It generates +5-tuples with these members: + + the token type (see token.py) + the token (a string) + the starting (row, column) indices of the token (a 2-tuple of ints) + the ending (row, column) indices of the token (a 2-tuple of ints) + the original line (string) + +It is designed to match the working of the Python tokenizer exactly, except +that it produces COMMENT tokens for comments and gives type OP for all +operators + +Older entry points + tokenize_loop(readline, tokeneater) + tokenize(readline, tokeneater=printtoken) +are the same, except instead of generating tokens, tokeneater is a callback +function to which the 5 fields described above are passed as 5 arguments, +each time a new token is found.""" + +# !mitogen: minify_safe + +__author__ = 'Ka-Ping Yee ' +__credits__ = ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, ' + 'Skip Montanaro, Raymond Hettinger') + +from itertools import chain +import string, re +from token import * + +import token +__all__ = [x for x in dir(token) if not x.startswith("_")] +__all__ += ["COMMENT", "tokenize", "generate_tokens", "NL", "untokenize"] +del token + +COMMENT = N_TOKENS +tok_name[COMMENT] = 'COMMENT' +NL = N_TOKENS + 1 +tok_name[NL] = 'NL' +N_TOKENS += 2 + +def group(*choices): return '(' + '|'.join(choices) + ')' +def any(*choices): return group(*choices) + '*' +def maybe(*choices): return group(*choices) + '?' + +Whitespace = r'[ \f\t]*' +Comment = r'#[^\r\n]*' +Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment) +Name = r'[a-zA-Z_]\w*' + +Hexnumber = r'0[xX][\da-fA-F]+[lL]?' +Octnumber = r'(0[oO][0-7]+)|(0[0-7]*)[lL]?' +Binnumber = r'0[bB][01]+[lL]?' +Decnumber = r'[1-9]\d*[lL]?' +Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber) +Exponent = r'[eE][-+]?\d+' +Pointfloat = group(r'\d+\.\d*', r'\.\d+') + maybe(Exponent) +Expfloat = r'\d+' + Exponent +Floatnumber = group(Pointfloat, Expfloat) +Imagnumber = group(r'\d+[jJ]', Floatnumber + r'[jJ]') +Number = group(Imagnumber, Floatnumber, Intnumber) + +# Tail end of ' string. +Single = r"[^'\\]*(?:\\.[^'\\]*)*'" +# Tail end of " string. +Double = r'[^"\\]*(?:\\.[^"\\]*)*"' +# Tail end of ''' string. +Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" +# Tail end of """ string. +Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' +Triple = group("[uUbB]?[rR]?'''", '[uUbB]?[rR]?"""') +# Single-line ' or " string. +String = group(r"[uUbB]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*'", + r'[uUbB]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*"') + +# Because of leftmost-then-longest match semantics, be sure to put the +# longest operators first (e.g., if = came before ==, == would get +# recognized as two instances of =). +Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"<>", r"!=", + r"//=?", + r"[+\-*/%&|^=<>]=?", + r"~") + +Bracket = '[][(){}]' +Special = group(r'\r?\n', r'[:;.,`@]') +Funny = group(Operator, Bracket, Special) + +PlainToken = group(Number, Funny, String, Name) +Token = Ignore + PlainToken + +# First (or only) line of ' or " string. +ContStr = group(r"[uUbB]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*" + + group("'", r'\\\r?\n'), + r'[uUbB]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*' + + group('"', r'\\\r?\n')) +PseudoExtras = group(r'\\\r?\n|\Z', Comment, Triple) +PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name) + +tokenprog, pseudoprog, single3prog, double3prog = map( + re.compile, (Token, PseudoToken, Single3, Double3)) +endprogs = {"'": re.compile(Single), '"': re.compile(Double), + "'''": single3prog, '"""': double3prog, + "r'''": single3prog, 'r"""': double3prog, + "u'''": single3prog, 'u"""': double3prog, + "ur'''": single3prog, 'ur"""': double3prog, + "R'''": single3prog, 'R"""': double3prog, + "U'''": single3prog, 'U"""': double3prog, + "uR'''": single3prog, 'uR"""': double3prog, + "Ur'''": single3prog, 'Ur"""': double3prog, + "UR'''": single3prog, 'UR"""': double3prog, + "b'''": single3prog, 'b"""': double3prog, + "br'''": single3prog, 'br"""': double3prog, + "B'''": single3prog, 'B"""': double3prog, + "bR'''": single3prog, 'bR"""': double3prog, + "Br'''": single3prog, 'Br"""': double3prog, + "BR'''": single3prog, 'BR"""': double3prog, + 'r': None, 'R': None, 'u': None, 'U': None, + 'b': None, 'B': None} + +triple_quoted = {} +for t in ("'''", '"""', + "r'''", 'r"""', "R'''", 'R"""', + "u'''", 'u"""', "U'''", 'U"""', + "ur'''", 'ur"""', "Ur'''", 'Ur"""', + "uR'''", 'uR"""', "UR'''", 'UR"""', + "b'''", 'b"""', "B'''", 'B"""', + "br'''", 'br"""', "Br'''", 'Br"""', + "bR'''", 'bR"""', "BR'''", 'BR"""'): + triple_quoted[t] = t +single_quoted = {} +for t in ("'", '"', + "r'", 'r"', "R'", 'R"', + "u'", 'u"', "U'", 'U"', + "ur'", 'ur"', "Ur'", 'Ur"', + "uR'", 'uR"', "UR'", 'UR"', + "b'", 'b"', "B'", 'B"', + "br'", 'br"', "Br'", 'Br"', + "bR'", 'bR"', "BR'", 'BR"' ): + single_quoted[t] = t + +tabsize = 8 + +class TokenError(Exception): pass + +class StopTokenizing(Exception): pass + +def printtoken(type, token, srow_scol, erow_ecol, line): # for testing + srow, scol = srow_scol + erow, ecol = erow_ecol + print("%d,%d-%d,%d:\t%s\t%s" % \ + (srow, scol, erow, ecol, tok_name[type], repr(token))) + +def tokenize(readline, tokeneater=printtoken): + """ + The tokenize() function accepts two parameters: one representing the + input stream, and one providing an output mechanism for tokenize(). + + The first parameter, readline, must be a callable object which provides + the same interface as the readline() method of built-in file objects. + Each call to the function should return one line of input as a string. + + The second parameter, tokeneater, must also be a callable object. It is + called once for each token, with five arguments, corresponding to the + tuples generated by generate_tokens(). + """ + try: + tokenize_loop(readline, tokeneater) + except StopTokenizing: + pass + +# backwards compatible interface +def tokenize_loop(readline, tokeneater): + for token_info in generate_tokens(readline): + tokeneater(*token_info) + +class Untokenizer: + + def __init__(self): + self.tokens = [] + self.prev_row = 1 + self.prev_col = 0 + + def add_whitespace(self, start): + row, col = start + if row < self.prev_row or row == self.prev_row and col < self.prev_col: + raise ValueError("start ({},{}) precedes previous end ({},{})" + .format(row, col, self.prev_row, self.prev_col)) + row_offset = row - self.prev_row + if row_offset: + self.tokens.append("\\\n" * row_offset) + self.prev_col = 0 + col_offset = col - self.prev_col + if col_offset: + self.tokens.append(" " * col_offset) + + def untokenize(self, iterable): + it = iter(iterable) + indents = [] + startline = False + for t in it: + if len(t) == 2: + self.compat(t, it) + break + tok_type, token, start, end, line = t + if tok_type == ENDMARKER: + break + if tok_type == INDENT: + indents.append(token) + continue + elif tok_type == DEDENT: + indents.pop() + self.prev_row, self.prev_col = end + continue + elif tok_type in (NEWLINE, NL): + startline = True + elif startline and indents: + indent = indents[-1] + if start[1] >= len(indent): + self.tokens.append(indent) + self.prev_col = len(indent) + startline = False + self.add_whitespace(start) + self.tokens.append(token) + self.prev_row, self.prev_col = end + if tok_type in (NEWLINE, NL): + self.prev_row += 1 + self.prev_col = 0 + return "".join(self.tokens) + + def compat(self, token, iterable): + indents = [] + toks_append = self.tokens.append + startline = token[0] in (NEWLINE, NL) + prevstring = False + + for tok in chain([token], iterable): + toknum, tokval = tok[:2] + + if toknum in (NAME, NUMBER): + tokval += ' ' + + # Insert a space between two consecutive strings + if toknum == STRING: + if prevstring: + tokval = ' ' + tokval + prevstring = True + else: + prevstring = False + + if toknum == INDENT: + indents.append(tokval) + continue + elif toknum == DEDENT: + indents.pop() + continue + elif toknum in (NEWLINE, NL): + startline = True + elif startline and indents: + toks_append(indents[-1]) + startline = False + toks_append(tokval) + +def untokenize(iterable): + """Transform tokens back into Python source code. + + Each element returned by the iterable must be a token sequence + with at least two elements, a token number and token value. If + only two tokens are passed, the resulting output is poor. + + Round-trip invariant for full input: + Untokenized source will match input source exactly + + Round-trip invariant for limited intput: + # Output text will tokenize the back to the input + t1 = [tok[:2] for tok in generate_tokens(f.readline)] + newcode = untokenize(t1) + readline = iter(newcode.splitlines(1)).next + t2 = [tok[:2] for tok in generate_tokens(readline)] + assert t1 == t2 + """ + ut = Untokenizer() + return ut.untokenize(iterable) + +def generate_tokens(readline): + """ + The generate_tokens() generator requires one argument, readline, which + must be a callable object which provides the same interface as the + readline() method of built-in file objects. Each call to the function + should return one line of input as a string. Alternately, readline + can be a callable function terminating with StopIteration: + readline = open(myfile).next # Example of alternate readline + + The generator produces 5-tuples with these members: the token type; the + token string; a 2-tuple (srow, scol) of ints specifying the row and + column where the token begins in the source; a 2-tuple (erow, ecol) of + ints specifying the row and column where the token ends in the source; + and the line on which the token was found. The line passed is the + logical line; continuation lines are included. + """ + lnum = parenlev = continued = 0 + namechars, numchars = string.ascii_letters + '_', '0123456789' + contstr, needcont = '', 0 + contline = None + indents = [0] + + while 1: # loop over lines in stream + try: + line = readline() + except StopIteration: + line = '' + lnum += 1 + pos, max = 0, len(line) + + if contstr: # continued string + if not line: + raise TokenError("EOF in multi-line string", strstart) + endmatch = endprog.match(line) + if endmatch: + pos = end = endmatch.end(0) + yield (STRING, contstr + line[:end], + strstart, (lnum, end), contline + line) + contstr, needcont = '', 0 + contline = None + elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n': + yield (ERRORTOKEN, contstr + line, + strstart, (lnum, len(line)), contline) + contstr = '' + contline = None + continue + else: + contstr = contstr + line + contline = contline + line + continue + + elif parenlev == 0 and not continued: # new statement + if not line: break + column = 0 + while pos < max: # measure leading whitespace + if line[pos] == ' ': + column += 1 + elif line[pos] == '\t': + column = (column//tabsize + 1)*tabsize + elif line[pos] == '\f': + column = 0 + else: + break + pos += 1 + if pos == max: + break + + if line[pos] in '#\r\n': # skip comments or blank lines + if line[pos] == '#': + comment_token = line[pos:].rstrip('\r\n') + nl_pos = pos + len(comment_token) + yield (COMMENT, comment_token, + (lnum, pos), (lnum, pos + len(comment_token)), line) + yield (NL, line[nl_pos:], + (lnum, nl_pos), (lnum, len(line)), line) + else: + yield ((NL, COMMENT)[line[pos] == '#'], line[pos:], + (lnum, pos), (lnum, len(line)), line) + continue + + if column > indents[-1]: # count indents or dedents + indents.append(column) + yield (INDENT, line[:pos], (lnum, 0), (lnum, pos), line) + while column < indents[-1]: + if column not in indents: + raise IndentationError( + "unindent does not match any outer indentation level", + ("", lnum, pos, line)) + indents = indents[:-1] + yield (DEDENT, '', (lnum, pos), (lnum, pos), line) + + else: # continued statement + if not line: + raise TokenError("EOF in multi-line statement", (lnum, 0)) + continued = 0 + + while pos < max: + pseudomatch = pseudoprog.match(line, pos) + if pseudomatch: # scan for tokens + start, end = pseudomatch.span(1) + spos, epos, pos = (lnum, start), (lnum, end), end + if start == end: + continue + token, initial = line[start:end], line[start] + + if initial in numchars or \ + (initial == '.' and token != '.'): # ordinary number + yield (NUMBER, token, spos, epos, line) + elif initial in '\r\n': + if parenlev > 0: + n = NL + else: + n = NEWLINE + yield (n, token, spos, epos, line) + elif initial == '#': + assert not token.endswith("\n") + yield (COMMENT, token, spos, epos, line) + elif token in triple_quoted: + endprog = endprogs[token] + endmatch = endprog.match(line, pos) + if endmatch: # all on one line + pos = endmatch.end(0) + token = line[start:pos] + yield (STRING, token, spos, (lnum, pos), line) + else: + strstart = (lnum, start) # multiple lines + contstr = line[start:] + contline = line + break + elif initial in single_quoted or \ + token[:2] in single_quoted or \ + token[:3] in single_quoted: + if token[-1] == '\n': # continued string + strstart = (lnum, start) + endprog = (endprogs[initial] or endprogs[token[1]] or + endprogs[token[2]]) + contstr, needcont = line[start:], 1 + contline = line + break + else: # ordinary string + yield (STRING, token, spos, epos, line) + elif initial in namechars: # ordinary name + yield (NAME, token, spos, epos, line) + elif initial == '\\': # continued stmt + continued = 1 + else: + if initial in '([{': + parenlev += 1 + elif initial in ')]}': + parenlev -= 1 + yield (OP, token, spos, epos, line) + else: + yield (ERRORTOKEN, line[pos], + (lnum, pos), (lnum, pos+1), line) + pos += 1 + + for indent in indents[1:]: # pop remaining indent levels + yield (DEDENT, '', (lnum, 0), (lnum, 0), '') + yield (ENDMARKER, '', (lnum, 0), (lnum, 0), '') + +if __name__ == '__main__': # testing + import sys + if len(sys.argv) > 1: + tokenize(open(sys.argv[1]).readline) + else: + tokenize(sys.stdin.readline) diff --git a/mitogen-0.2.7/mitogen/core.py b/mitogen-0.2.7/mitogen/core.py new file mode 100644 index 000000000..ff77bba --- /dev/null +++ b/mitogen-0.2.7/mitogen/core.py @@ -0,0 +1,3409 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +This module implements most package functionality, but remains separate from +non-essential code in order to reduce its size, since it is also serves as the +bootstrap implementation sent to every new slave context. +""" + +import binascii +import collections +import encodings.latin_1 +import errno +import fcntl +import itertools +import linecache +import logging +import os +import pickle as py_pickle +import pstats +import signal +import socket +import struct +import sys +import threading +import time +import traceback +import warnings +import weakref +import zlib + +# Python >3.7 deprecated the imp module. +warnings.filterwarnings('ignore', message='the imp module is deprecated') +import imp + +# Absolute imports for <2.5. +select = __import__('select') + +try: + import cProfile +except ImportError: + cProfile = None + +try: + import thread +except ImportError: + import threading as thread + +try: + import cPickle as pickle +except ImportError: + import pickle + +try: + from cStringIO import StringIO as BytesIO +except ImportError: + from io import BytesIO + +try: + BaseException +except NameError: + BaseException = Exception + +try: + ModuleNotFoundError +except NameError: + ModuleNotFoundError = ImportError + +# TODO: usage of 'import' after setting __name__, but before fixing up +# sys.modules generates a warning. This happens when profiling = True. +warnings.filterwarnings('ignore', + "Parent module 'mitogen' not found while handling absolute import") + +LOG = logging.getLogger('mitogen') +IOLOG = logging.getLogger('mitogen.io') +IOLOG.setLevel(logging.INFO) + +LATIN1_CODEC = encodings.latin_1.Codec() +# str.encode() may take import lock. Deadlock possible if broker calls +# .encode() on behalf of thread currently waiting for module. +UTF8_CODEC = encodings.latin_1.Codec() + +_v = False +_vv = False + +GET_MODULE = 100 +CALL_FUNCTION = 101 +FORWARD_LOG = 102 +ADD_ROUTE = 103 +DEL_ROUTE = 104 +ALLOCATE_ID = 105 +SHUTDOWN = 106 +LOAD_MODULE = 107 +FORWARD_MODULE = 108 +DETACHING = 109 +CALL_SERVICE = 110 + +#: Special value used to signal disconnection or the inability to route a +#: message, when it appears in the `reply_to` field. Usually causes +#: :class:`mitogen.core.ChannelError` to be raised when it is received. +#: +#: It indicates the sender did not know how to process the message, or wishes +#: no further messages to be delivered to it. It is used when: +#: +#: * a remote receiver is disconnected or explicitly closed. +#: * a related message could not be delivered due to no route existing for it. +#: * a router is being torn down, as a sentinel value to notify +#: :meth:`mitogen.core.Router.add_handler` callbacks to clean up. +IS_DEAD = 999 + +try: + BaseException +except NameError: + BaseException = Exception + +PY24 = sys.version_info < (2, 5) +PY3 = sys.version_info > (3,) +if PY3: + b = str.encode + BytesType = bytes + UnicodeType = str + FsPathTypes = (str,) + BufferType = lambda buf, start: memoryview(buf)[start:] + long = int +else: + b = str + BytesType = str + FsPathTypes = (str, unicode) + BufferType = buffer + UnicodeType = unicode + +AnyTextType = (BytesType, UnicodeType) + +try: + next +except NameError: + next = lambda it: it.next() + +# #550: prehistoric WSL did not advertise itself in uname output. +try: + fp = open('/proc/sys/kernel/osrelease') + IS_WSL = 'Microsoft' in fp.read() + fp.close() +except IOError: + IS_WSL = False + + +#: Default size for calls to :meth:`Side.read` or :meth:`Side.write`, and the +#: size of buffers configured by :func:`mitogen.parent.create_socketpair`. This +#: value has many performance implications, 128KiB seems to be a sweet spot. +#: +#: * When set low, large messages cause many :class:`Broker` IO loop +#: iterations, burning CPU and reducing throughput. +#: * When set high, excessive RAM is reserved by the OS for socket buffers (2x +#: per child), and an identically sized temporary userspace buffer is +#: allocated on each read that requires zeroing, and over a particular size +#: may require two system calls to allocate/deallocate. +#: +#: Care must be taken to ensure the underlying kernel object and receiving +#: program support the desired size. For example, +#: +#: * Most UNIXes have TTYs with fixed 2KiB-4KiB buffers, making them unsuitable +#: for efficient IO. +#: * Different UNIXes have varying presets for pipes, which may not be +#: configurable. On recent Linux the default pipe buffer size is 64KiB, but +#: under memory pressure may be as low as 4KiB for unprivileged processes. +#: * When communication is via an intermediary process, its internal buffers +#: effect the speed OS buffers will drain. For example OpenSSH uses 64KiB +#: reads. +#: +#: An ideal :class:`Message` has a size that is a multiple of +#: :data:`CHUNK_SIZE` inclusive of headers, to avoid wasting IO loop iterations +#: writing small trailer chunks. +CHUNK_SIZE = 131072 + +_tls = threading.local() + + +if __name__ == 'mitogen.core': + # When loaded using import mechanism, ExternalContext.main() will not have + # a chance to set the synthetic mitogen global, so just import it here. + import mitogen +else: + # When loaded as __main__, ensure classes and functions gain a __module__ + # attribute consistent with the host process, so that pickling succeeds. + __name__ = 'mitogen.core' + + +class Error(Exception): + """Base for all exceptions raised by Mitogen. + + :param str fmt: + Exception text, or format string if `args` is non-empty. + :param tuple args: + Format string arguments. + """ + def __init__(self, fmt=None, *args): + if args: + fmt %= args + if fmt and not isinstance(fmt, UnicodeType): + fmt = fmt.decode('utf-8') + Exception.__init__(self, fmt) + + +class LatchError(Error): + """Raised when an attempt is made to use a :class:`mitogen.core.Latch` + that has been marked closed.""" + pass + + +class Blob(BytesType): + """A serializable bytes subclass whose content is summarized in repr() + output, making it suitable for logging binary data.""" + def __repr__(self): + return '[blob: %d bytes]' % len(self) + + def __reduce__(self): + return (Blob, (BytesType(self),)) + + +class Secret(UnicodeType): + """A serializable unicode subclass whose content is masked in repr() + output, making it suitable for logging passwords.""" + def __repr__(self): + return '[secret]' + + if not PY3: + # TODO: what is this needed for in 2.x? + def __str__(self): + return UnicodeType(self) + + def __reduce__(self): + return (Secret, (UnicodeType(self),)) + + +class Kwargs(dict): + """ + A serializable dict subclass that indicates its keys should be coerced to + Unicode on Python 3 and bytes on Python<2.6. + + Python 2 produces keyword argument dicts whose keys are bytes, requiring a + helper to ensure compatibility with Python 3 where Unicode is required, + whereas Python 3 produces keyword argument dicts whose keys are Unicode, + requiring a helper for Python 2.4/2.5, where bytes are required. + """ + if PY3: + def __init__(self, dct): + for k, v in dct.items(): + if type(k) is bytes: + self[k.decode()] = v + else: + self[k] = v + elif sys.version_info < (2, 6, 5): + def __init__(self, dct): + for k, v in dct.iteritems(): + if type(k) is unicode: + k, _ = UTF8_CODEC.encode(k) + self[k] = v + + def __repr__(self): + return 'Kwargs(%s)' % (dict.__repr__(self),) + + def __reduce__(self): + return (Kwargs, (dict(self),)) + + +class CallError(Error): + """ + Serializable :class:`Error` subclass raised when :meth:`Context.call() + ` fails. A copy of the traceback from the + external context is appended to the exception message. + """ + def __init__(self, fmt=None, *args): + if not isinstance(fmt, BaseException): + Error.__init__(self, fmt, *args) + else: + e = fmt + cls = e.__class__ + fmt = '%s.%s: %s' % (cls.__module__, cls.__name__, e) + tb = sys.exc_info()[2] + if tb: + fmt += '\n' + fmt += ''.join(traceback.format_tb(tb)) + Error.__init__(self, fmt) + + def __reduce__(self): + return (_unpickle_call_error, (self.args[0],)) + + +def _unpickle_call_error(s): + if not (type(s) is UnicodeType and len(s) < 10000): + raise TypeError('cannot unpickle CallError: bad input') + return CallError(s) + + +class ChannelError(Error): + """Raised when a channel dies or has been closed.""" + remote_msg = 'Channel closed by remote end.' + local_msg = 'Channel closed by local end.' + + +class StreamError(Error): + """Raised when a stream cannot be established.""" + pass + + +class TimeoutError(Error): + """Raised when a timeout occurs on a stream.""" + pass + + +def to_text(o): + """Coerce `o` to Unicode by decoding it from UTF-8 if it is an instance of + :class:`bytes`, otherwise pass it to the :class:`str` constructor. The + returned object is always a plain :class:`str`, any subclass is removed.""" + if isinstance(o, BytesType): + return o.decode('utf-8') + return UnicodeType(o) + + +# Python 2.4 +try: + any +except NameError: + def any(it): + for elem in it: + if elem: + return True + + +def _partition(s, sep, find): + """ + (str|unicode).(partition|rpartition) for Python 2.4/2.5. + """ + idx = find(sep) + if idx != -1: + left = s[0:idx] + return left, sep, s[len(left)+len(sep):] + + +if hasattr(UnicodeType, 'rpartition'): + str_partition = UnicodeType.partition + str_rpartition = UnicodeType.rpartition + bytes_partition = BytesType.partition +else: + def str_partition(s, sep): + return _partition(s, sep, s.find) or (s, u'', u'') + def str_rpartition(s, sep): + return _partition(s, sep, s.rfind) or (u'', u'', s) + def bytes_partition(s, sep): + return _partition(s, sep, s.find) or (s, '', '') + + +def has_parent_authority(msg, _stream=None): + """Policy function for use with :class:`Receiver` and + :meth:`Router.add_handler` that requires incoming messages to originate + from a parent context, or on a :class:`Stream` whose :attr:`auth_id + ` has been set to that of a parent context or the current + context.""" + return (msg.auth_id == mitogen.context_id or + msg.auth_id in mitogen.parent_ids) + + +def listen(obj, name, func): + """ + Arrange for `func(*args, **kwargs)` to be invoked when the named signal is + fired by `obj`. + """ + signals = vars(obj).setdefault('_signals', {}) + signals.setdefault(name, []).append(func) + + +def fire(obj, name, *args, **kwargs): + """ + Arrange for `func(*args, **kwargs)` to be invoked for every function + registered for the named signal on `obj`. + """ + signals = vars(obj).get('_signals', {}) + for func in signals.get(name, ()): + func(*args, **kwargs) + + +def takes_econtext(func): + func.mitogen_takes_econtext = True + return func + + +def takes_router(func): + func.mitogen_takes_router = True + return func + + +def is_blacklisted_import(importer, fullname): + """ + Return :data:`True` if `fullname` is part of a blacklisted package, or if + any packages have been whitelisted and `fullname` is not part of one. + + NB: + - If a package is on both lists, then it is treated as blacklisted. + - If any package is whitelisted, then all non-whitelisted packages are + treated as blacklisted. + """ + return ((not any(fullname.startswith(s) for s in importer.whitelist)) or + (any(fullname.startswith(s) for s in importer.blacklist))) + + +def set_cloexec(fd): + """Set the file descriptor `fd` to automatically close on + :func:`os.execve`. This has no effect on file descriptors inherited across + :func:`os.fork`, they must be explicitly closed through some other means, + such as :func:`mitogen.fork.on_fork`.""" + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + assert fd > 2 + fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) + + +def set_nonblock(fd): + """Set the file descriptor `fd` to non-blocking mode. For most underlying + file types, this causes :func:`os.read` or :func:`os.write` to raise + :class:`OSError` with :data:`errno.EAGAIN` rather than block the thread + when the underlying kernel buffer is exhausted.""" + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + +def set_block(fd): + """Inverse of :func:`set_nonblock`, i.e. cause `fd` to block the thread + when the underlying kernel buffer is exhausted.""" + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) + + +def io_op(func, *args): + """Wrap `func(*args)` that may raise :class:`select.error`, + :class:`IOError`, or :class:`OSError`, trapping UNIX error codes relating + to disconnection and retry events in various subsystems: + + * When a signal is delivered to the process on Python 2, system call retry + is signalled through :data:`errno.EINTR`. The invocation is automatically + restarted. + * When performing IO against a TTY, disconnection of the remote end is + signalled by :data:`errno.EIO`. + * When performing IO against a socket, disconnection of the remote end is + signalled by :data:`errno.ECONNRESET`. + * When performing IO against a pipe, disconnection of the remote end is + signalled by :data:`errno.EPIPE`. + + :returns: + Tuple of `(return_value, disconnect_reason)`, where `return_value` is + the return value of `func(*args)`, and `disconnected` is an exception + instance when disconnection was detected, otherwise :data:`None`. + """ + while True: + try: + return func(*args), None + except (select.error, OSError, IOError): + e = sys.exc_info()[1] + _vv and IOLOG.debug('io_op(%r) -> OSError: %s', func, e) + if e.args[0] == errno.EINTR: + continue + if e.args[0] in (errno.EIO, errno.ECONNRESET, errno.EPIPE): + return None, e + raise + + +class PidfulStreamHandler(logging.StreamHandler): + """A :class:`logging.StreamHandler` subclass used when + :meth:`Router.enable_debug() ` has been + called, or the `debug` parameter was specified during context construction. + Verifies the process ID has not changed on each call to :meth:`emit`, + reopening the associated log file when a change is detected. + + This ensures logging to the per-process output files happens correctly even + when uncooperative third party components call :func:`os.fork`. + """ + #: PID that last opened the log file. + open_pid = None + + #: Output path template. + template = '/tmp/mitogen.%s.%s.log' + + def _reopen(self): + self.acquire() + try: + if self.open_pid == os.getpid(): + return + ts = time.strftime('%Y%m%d_%H%M%S') + path = self.template % (os.getpid(), ts) + self.stream = open(path, 'w', 1) + set_cloexec(self.stream.fileno()) + self.stream.write('Parent PID: %s\n' % (os.getppid(),)) + self.stream.write('Created by:\n\n%s\n' % ( + ''.join(traceback.format_stack()), + )) + self.open_pid = os.getpid() + finally: + self.release() + + def emit(self, record): + if self.open_pid != os.getpid(): + self._reopen() + logging.StreamHandler.emit(self, record) + + +def enable_debug_logging(): + global _v, _vv + _v = True + _vv = True + root = logging.getLogger() + root.setLevel(logging.DEBUG) + IOLOG.setLevel(logging.DEBUG) + handler = PidfulStreamHandler() + handler.formatter = logging.Formatter( + '%(asctime)s %(levelname).1s %(name)s: %(message)s', + '%H:%M:%S' + ) + root.handlers.insert(0, handler) + + +_profile_hook = lambda name, func, *args: func(*args) +_profile_fmt = os.environ.get( + 'MITOGEN_PROFILE_FMT', + '/tmp/mitogen.stats.%(pid)s.%(identity)s.%(now)s.%(ext)s', +) + + +def _profile_hook(name, func, *args): + """ + Call `func(*args)` and return its result. This function is replaced by + :func:`_real_profile_hook` when :func:`enable_profiling` is called. This + interface is obsolete and will be replaced by a signals-based integration + later on. + """ + return func(*args) + + +def _real_profile_hook(name, func, *args): + profiler = cProfile.Profile() + profiler.enable() + try: + return func(*args) + finally: + path = _profile_fmt % { + 'now': int(1e6 * time.time()), + 'identity': name, + 'pid': os.getpid(), + 'ext': '%s' + } + profiler.dump_stats(path % ('pstats',)) + profiler.create_stats() + fp = open(path % ('log',), 'w') + try: + stats = pstats.Stats(profiler, stream=fp) + stats.sort_stats('cumulative') + stats.print_stats() + finally: + fp.close() + + +def enable_profiling(econtext=None): + global _profile_hook + _profile_hook = _real_profile_hook + + +def import_module(modname): + """ + Import `module` and return the attribute named `attr`. + """ + return __import__(modname, None, None, ['']) + + +class Py24Pickler(py_pickle.Pickler): + """ + Exceptions were classic classes until Python 2.5. Sadly for 2.4, cPickle + offers little control over how a classic instance is pickled. Therefore 2.4 + uses a pure-Python pickler, so CallError can be made to look as it does on + newer Pythons. + + This mess will go away once proper serialization exists. + """ + @classmethod + def dumps(cls, obj, protocol): + bio = BytesIO() + self = cls(bio, protocol=protocol) + self.dump(obj) + return bio.getvalue() + + def save_exc_inst(self, obj): + if isinstance(obj, CallError): + func, args = obj.__reduce__() + self.save(func) + self.save(args) + self.write(py_pickle.REDUCE) + else: + py_pickle.Pickler.save_inst(self, obj) + + if PY24: + dispatch = py_pickle.Pickler.dispatch.copy() + dispatch[py_pickle.InstanceType] = save_exc_inst + + +if PY3: + # In 3.x Unpickler is a class exposing find_class as an overridable, but it + # cannot be overridden without subclassing. + class _Unpickler(pickle.Unpickler): + def find_class(self, module, func): + return self.find_global(module, func) + pickle__dumps = pickle.dumps +elif PY24: + # On Python 2.4, we must use a pure-Python pickler. + pickle__dumps = Py24Pickler.dumps + _Unpickler = pickle.Unpickler +else: + pickle__dumps = pickle.dumps + # In 2.x Unpickler is a function exposing a writeable find_global + # attribute. + _Unpickler = pickle.Unpickler + + +class Message(object): + """ + Messages are the fundamental unit of communication, comprising fields from + the :ref:`stream-protocol` header, an optional reference to the receiving + :class:`mitogen.core.Router` for ingress messages, and helper methods for + deserialization and generating replies. + """ + #: Integer target context ID. :class:`Router` delivers messages locally + #: when their :attr:`dst_id` matches :data:`mitogen.context_id`, otherwise + #: they are routed up or downstream. + dst_id = None + + #: Integer source context ID. Used as the target of replies if any are + #: generated. + src_id = None + + #: Context ID under whose authority the message is acting. See + #: :ref:`source-verification`. + auth_id = None + + #: Integer target handle in the destination context. This is one of the + #: :ref:`standard-handles`, or a dynamically generated handle used to + #: receive a one-time reply, such as the return value of a function call. + handle = None + + #: Integer target handle to direct any reply to this message. Used to + #: receive a one-time reply, such as the return value of a function call. + #: :data:`IS_DEAD` has a special meaning when it appears in this field. + reply_to = None + + #: Raw message data bytes. + data = b('') + + _unpickled = object() + + #: The :class:`Router` responsible for routing the message. This is + #: :data:`None` for locally originated messages. + router = None + + #: The :class:`Receiver` over which the message was last received. Part of + #: the :class:`mitogen.select.Select` interface. Defaults to :data:`None`. + receiver = None + + def __init__(self, **kwargs): + """ + Construct a message from from the supplied `kwargs`. :attr:`src_id` and + :attr:`auth_id` are always set to :data:`mitogen.context_id`. + """ + self.src_id = mitogen.context_id + self.auth_id = mitogen.context_id + vars(self).update(kwargs) + assert isinstance(self.data, BytesType) + + def _unpickle_context(self, context_id, name): + return _unpickle_context(context_id, name, router=self.router) + + def _unpickle_sender(self, context_id, dst_handle): + return _unpickle_sender(self.router, context_id, dst_handle) + + def _unpickle_bytes(self, s, encoding): + s, n = LATIN1_CODEC.encode(s) + return s + + def _find_global(self, module, func): + """Return the class implementing `module_name.class_name` or raise + `StreamError` if the module is not whitelisted.""" + if module == __name__: + if func == '_unpickle_call_error' or func == 'CallError': + return _unpickle_call_error + elif func == '_unpickle_sender': + return self._unpickle_sender + elif func == '_unpickle_context': + return self._unpickle_context + elif func == 'Blob': + return Blob + elif func == 'Secret': + return Secret + elif func == 'Kwargs': + return Kwargs + elif module == '_codecs' and func == 'encode': + return self._unpickle_bytes + elif module == '__builtin__' and func == 'bytes': + return BytesType + raise StreamError('cannot unpickle %r/%r', module, func) + + @property + def is_dead(self): + """ + :data:`True` if :attr:`reply_to` is set to the magic value + :data:`IS_DEAD`, indicating the sender considers the channel dead. Dead + messages can be raised in a variety of circumstances, see + :data:`IS_DEAD` for more information. + """ + return self.reply_to == IS_DEAD + + @classmethod + def dead(cls, reason=None, **kwargs): + """ + Syntax helper to construct a dead message. + """ + kwargs['data'], _ = UTF8_CODEC.encode(reason or u'') + return cls(reply_to=IS_DEAD, **kwargs) + + @classmethod + def pickled(cls, obj, **kwargs): + """ + Construct a pickled message, setting :attr:`data` to the serialization + of `obj`, and setting remaining fields using `kwargs`. + + :returns: + The new message. + """ + self = cls(**kwargs) + try: + self.data = pickle__dumps(obj, protocol=2) + except pickle.PicklingError: + e = sys.exc_info()[1] + self.data = pickle__dumps(CallError(e), protocol=2) + return self + + def reply(self, msg, router=None, **kwargs): + """ + Compose a reply to this message and send it using :attr:`router`, or + `router` is :attr:`router` is :data:`None`. + + :param obj: + Either a :class:`Message`, or an object to be serialized in order + to construct a new message. + :param router: + Optional router to use if :attr:`router` is :data:`None`. + :param kwargs: + Optional keyword parameters overriding message fields in the reply. + """ + if not isinstance(msg, Message): + msg = Message.pickled(msg) + msg.dst_id = self.src_id + msg.handle = self.reply_to + vars(msg).update(kwargs) + if msg.handle: + (self.router or router).route(msg) + else: + LOG.debug('Message.reply(): discarding due to zero handle: %r', msg) + + if PY3: + UNPICKLER_KWARGS = {'encoding': 'bytes'} + else: + UNPICKLER_KWARGS = {} + + def _throw_dead(self): + if len(self.data): + raise ChannelError(self.data.decode('utf-8', 'replace')) + elif self.src_id == mitogen.context_id: + raise ChannelError(ChannelError.local_msg) + else: + raise ChannelError(ChannelError.remote_msg) + + def unpickle(self, throw=True, throw_dead=True): + """ + Unpickle :attr:`data`, optionally raising any exceptions present. + + :param bool throw_dead: + If :data:`True`, raise exceptions, otherwise it is the caller's + responsibility. + + :raises CallError: + The serialized data contained CallError exception. + :raises ChannelError: + The `is_dead` field was set. + """ + _vv and IOLOG.debug('%r.unpickle()', self) + if throw_dead and self.is_dead: + self._throw_dead() + + obj = self._unpickled + if obj is Message._unpickled: + fp = BytesIO(self.data) + unpickler = _Unpickler(fp, **self.UNPICKLER_KWARGS) + unpickler.find_global = self._find_global + try: + # Must occur off the broker thread. + obj = unpickler.load() + self._unpickled = obj + except (TypeError, ValueError): + e = sys.exc_info()[1] + raise StreamError('invalid message: %s', e) + + if throw: + if isinstance(obj, CallError): + raise obj + + return obj + + def __repr__(self): + return 'Message(%r, %r, %r, %r, %r, %r..%d)' % ( + self.dst_id, self.src_id, self.auth_id, self.handle, + self.reply_to, (self.data or '')[:50], len(self.data) + ) + + +class Sender(object): + """ + Senders are used to send pickled messages to a handle in another context, + it is the inverse of :class:`mitogen.core.Receiver`. + + Senders may be serialized, making them convenient to wire up data flows. + See :meth:`mitogen.core.Receiver.to_sender` for more information. + + :param Context context: + Context to send messages to. + :param int dst_handle: + Destination handle to send messages to. + """ + def __init__(self, context, dst_handle): + self.context = context + self.dst_handle = dst_handle + + def send(self, data): + """ + Send `data` to the remote end. + """ + _vv and IOLOG.debug('%r.send(%r..)', self, repr(data)[:100]) + self.context.send(Message.pickled(data, handle=self.dst_handle)) + + explicit_close_msg = 'Sender was explicitly closed' + + def close(self): + """ + Send a dead message to the remote, causing :meth:`ChannelError` to be + raised in any waiting thread. + """ + _vv and IOLOG.debug('%r.close()', self) + self.context.send( + Message.dead( + reason=self.explicit_close_msg, + handle=self.dst_handle + ) + ) + + def __repr__(self): + return 'Sender(%r, %r)' % (self.context, self.dst_handle) + + def __reduce__(self): + return _unpickle_sender, (self.context.context_id, self.dst_handle) + + +def _unpickle_sender(router, context_id, dst_handle): + if not (isinstance(router, Router) and + isinstance(context_id, (int, long)) and context_id >= 0 and + isinstance(dst_handle, (int, long)) and dst_handle > 0): + raise TypeError('cannot unpickle Sender: bad input') + return Sender(Context(router, context_id), dst_handle) + + +class Receiver(object): + """ + Receivers maintain a thread-safe queue of messages sent to a handle of this + context from another context. + + :param mitogen.core.Router router: + Router to register the handler on. + + :param int handle: + If not :data:`None`, an explicit handle to register, otherwise an + unused handle is chosen. + + :param bool persist: + If :data:`False`, unregister the handler after one message is received. + Single-message receivers are intended for RPC-like transactions, such + as in the case of :meth:`mitogen.parent.Context.call_async`. + + :param mitogen.core.Context respondent: + Context this receiver is receiving from. If not :data:`None`, arranges + for the receiver to receive a dead message if messages can no longer be + routed to the context due to disconnection, and ignores messages that + did not originate from the respondent context. + """ + #: If not :data:`None`, a reference to a function invoked as + #: `notify(receiver)` when a new message is delivered to this receiver. The + #: function is invoked on the broker thread, therefore it must not block. + #: Used by :class:`mitogen.select.Select` to implement waiting on multiple + #: receivers. + notify = None + + raise_channelerror = True + + def __init__(self, router, handle=None, persist=True, + respondent=None, policy=None, overwrite=False): + self.router = router + #: The handle. + self.handle = handle # Avoid __repr__ crash in add_handler() + self._latch = Latch() # Must exist prior to .add_handler() + self.handle = router.add_handler( + fn=self._on_receive, + handle=handle, + policy=policy, + persist=persist, + respondent=respondent, + overwrite=overwrite, + ) + + def __repr__(self): + return 'Receiver(%r, %r)' % (self.router, self.handle) + + def __enter__(self): + return self + + def __exit__(self, _1, _2, _3): + self.close() + + def to_sender(self): + """ + Return a :class:`Sender` configured to deliver messages to this + receiver. As senders are serializable, this makes it convenient to pass + `(context_id, handle)` pairs around:: + + def deliver_monthly_report(sender): + for line in open('monthly_report.txt'): + sender.send(line) + sender.close() + + @mitogen.main() + def main(router): + remote = router.ssh(hostname='mainframe') + recv = mitogen.core.Receiver(router) + remote.call(deliver_monthly_report, recv.to_sender()) + for msg in recv: + print(msg) + """ + return Sender(self.router.myself(), self.handle) + + def _on_receive(self, msg): + """ + Callback registered for the handle with :class:`Router`; appends data + to the internal queue. + """ + _vv and IOLOG.debug('%r._on_receive(%r)', self, msg) + self._latch.put(msg) + if self.notify: + self.notify(self) + + closed_msg = 'the Receiver has been closed' + + def close(self): + """ + Unregister the receiver's handle from its associated router, and cause + :class:`ChannelError` to be raised in any thread waiting in :meth:`get` + on this receiver. + """ + if self.handle: + self.router.del_handler(self.handle) + self.handle = None + self._latch.close() + + def empty(self): + """ + Return :data:`True` if calling :meth:`get` would block. + + As with :class:`Queue.Queue`, :data:`True` may be returned even though + a subsequent call to :meth:`get` will succeed, since a message may be + posted at any moment between :meth:`empty` and :meth:`get`. + """ + return self._latch.empty() + + def get(self, timeout=None, block=True, throw_dead=True): + """ + Sleep waiting for a message to arrive on this receiver. + + :param float timeout: + If not :data:`None`, specifies a timeout in seconds. + + :raises mitogen.core.ChannelError: + The remote end indicated the channel should be closed, + communication with it was lost, or :meth:`close` was called in the + local process. + + :raises mitogen.core.TimeoutError: + Timeout was reached. + + :returns: + :class:`Message` that was received. + """ + _vv and IOLOG.debug('%r.get(timeout=%r, block=%r)', self, timeout, block) + try: + msg = self._latch.get(timeout=timeout, block=block) + except LatchError: + raise ChannelError(self.closed_msg) + if msg.is_dead and throw_dead: + msg._throw_dead() + return msg + + def __iter__(self): + """ + Yield consecutive :class:`Message` instances delivered to this receiver + until :class:`ChannelError` is raised. + """ + while True: + try: + msg = self.get() + except ChannelError: + return + yield msg + + +class Channel(Sender, Receiver): + """ + A channel inherits from :class:`mitogen.core.Sender` and + `mitogen.core.Receiver` to provide bidirectional functionality. + + This class is incomplete and obsolete, it will be removed in Mitogen 0.3. + Channels were an early attempt at syntax sugar. It is always easier to pass + around unidirectional pairs of senders/receivers, even though the syntax is + baroque: + + .. literalinclude:: ../examples/ping_pong.py + + Since all handles aren't known until after both ends are constructed, for + both ends to communicate through a channel, it is necessary for one end to + retrieve the handle allocated to the other and reconfigure its own channel + to match. Currently this is a manual task. + """ + def __init__(self, router, context, dst_handle, handle=None): + Sender.__init__(self, context, dst_handle) + Receiver.__init__(self, router, handle) + + def close(self): + Receiver.close(self) + Sender.close(self) + + def __repr__(self): + return 'Channel(%s, %s)' % ( + Sender.__repr__(self), + Receiver.__repr__(self) + ) + + +class Importer(object): + """ + Import protocol implementation that fetches modules from the parent + process. + + :param context: Context to communicate via. + """ + # The Mitogen package is handled specially, since the child context must + # construct it manually during startup. + MITOGEN_PKG_CONTENT = [ + 'compat', + 'debug', + 'doas', + 'docker', + 'kubectl', + 'fakessh', + 'fork', + 'jail', + 'lxc', + 'lxd', + 'master', + 'minify', + 'os_fork', + 'parent', + 'select', + 'service', + 'setns', + 'ssh', + 'su', + 'sudo', + 'utils', + ] + + ALWAYS_BLACKLIST = [ + # 2.x generates needless imports for 'builtins', while 3.x does the + # same for '__builtin__'. The correct one is built-in, the other always + # a negative round-trip. + 'builtins', + '__builtin__', + 'thread', + + # org.python.core imported by copy, pickle, xml.sax; breaks Jython, but + # very unlikely to trigger a bug report. + 'org', + ] + + if PY3: + ALWAYS_BLACKLIST += ['cStringIO'] + + def __init__(self, router, context, core_src, whitelist=(), blacklist=()): + self._context = context + self._present = {'mitogen': self.MITOGEN_PKG_CONTENT} + self._lock = threading.Lock() + self.whitelist = list(whitelist) or [''] + self.blacklist = list(blacklist) + self.ALWAYS_BLACKLIST + + # Preserve copies of the original server-supplied whitelist/blacklist + # for later use by children. + self.master_whitelist = self.whitelist[:] + self.master_blacklist = self.blacklist[:] + + # Presence of an entry in this map indicates in-flight GET_MODULE. + self._callbacks = {} + self._cache = {} + if core_src: + self._update_linecache('x/mitogen/core.py', core_src) + self._cache['mitogen.core'] = ( + 'mitogen.core', + None, + 'x/mitogen/core.py', + zlib.compress(core_src, 9), + [], + ) + self._install_handler(router) + + def _update_linecache(self, path, data): + """ + The Python 2.4 linecache module, used to fetch source code for + tracebacks and :func:`inspect.getsource`, does not support PEP-302, + meaning it needs extra help to for Mitogen-loaded modules. Directly + populate its cache if a loaded module belongs to the Mitogen package. + """ + if PY24 and 'mitogen' in path: + linecache.cache[path] = ( + len(data), + 0.0, + [line+'\n' for line in data.splitlines()], + path, + ) + + def _install_handler(self, router): + router.add_handler( + fn=self._on_load_module, + handle=LOAD_MODULE, + policy=has_parent_authority, + ) + + def __repr__(self): + return 'Importer()' + + def builtin_find_module(self, fullname): + # imp.find_module() will always succeed for __main__, because it is a + # built-in module. That means it exists on a special linked list deep + # within the bowels of the interpreter. We must special case it. + if fullname == '__main__': + raise ModuleNotFoundError() + + parent, _, modname = str_rpartition(fullname, '.') + if parent: + path = sys.modules[parent].__path__ + else: + path = None + + fp, pathname, description = imp.find_module(modname, path) + if fp: + fp.close() + + def find_module(self, fullname, path=None): + if hasattr(_tls, 'running'): + return None + + _tls.running = True + try: + _v and LOG.debug('%r.find_module(%r)', self, fullname) + fullname = to_text(fullname) + pkgname, dot, _ = str_rpartition(fullname, '.') + pkg = sys.modules.get(pkgname) + if pkgname and getattr(pkg, '__loader__', None) is not self: + LOG.debug('%r: %r is submodule of a package we did not load', + self, fullname) + return None + + suffix = fullname[len(pkgname+dot):] + if pkgname and suffix not in self._present.get(pkgname, ()): + LOG.debug('%r: master doesn\'t know %r', self, fullname) + return None + + # #114: explicitly whitelisted prefixes override any + # system-installed package. + if self.whitelist != ['']: + if any(fullname.startswith(s) for s in self.whitelist): + return self + + try: + self.builtin_find_module(fullname) + _vv and IOLOG.debug('%r: %r is available locally', + self, fullname) + except ImportError: + _vv and IOLOG.debug('find_module(%r) returning self', fullname) + return self + finally: + del _tls.running + + blacklisted_msg = ( + '%r is present in the Mitogen importer blacklist, therefore this ' + 'context will not attempt to request it from the master, as the ' + 'request will always be refused.' + ) + pkg_resources_msg = ( + 'pkg_resources is prohibited from importing __main__, as it causes ' + 'problems in applications whose main module is not designed to be ' + 're-imported by children.' + ) + absent_msg = ( + 'The Mitogen master process was unable to serve %r. It may be a ' + 'native Python extension, or it may be missing entirely. Check the ' + 'importer debug logs on the master for more information.' + ) + + def _refuse_imports(self, fullname): + if is_blacklisted_import(self, fullname): + raise ModuleNotFoundError(self.blacklisted_msg % (fullname,)) + + f = sys._getframe(2) + requestee = f.f_globals['__name__'] + + if fullname == '__main__' and requestee == 'pkg_resources': + # Anything that imports pkg_resources will eventually cause + # pkg_resources to try and scan __main__ for its __requires__ + # attribute (pkg_resources/__init__.py::_build_master()). This + # breaks any app that is not expecting its __main__ to suddenly be + # sucked over a network and injected into a remote process, like + # py.test. + raise ModuleNotFoundError(self.pkg_resources_msg) + + if fullname == 'pbr': + # It claims to use pkg_resources to read version information, which + # would result in PEP-302 being used, but it actually does direct + # filesystem access. So instead smodge the environment to override + # any version that was defined. This will probably break something + # later. + os.environ['PBR_VERSION'] = '0.0.0' + + def _on_load_module(self, msg): + if msg.is_dead: + return + + tup = msg.unpickle() + fullname = tup[0] + _v and LOG.debug('Importer._on_load_module(%r)', fullname) + + self._lock.acquire() + try: + self._cache[fullname] = tup + if tup[2] is not None and PY24: + self._update_linecache( + path='master:' + tup[2], + data=zlib.decompress(tup[3]) + ) + callbacks = self._callbacks.pop(fullname, []) + finally: + self._lock.release() + + for callback in callbacks: + callback() + + def _request_module(self, fullname, callback): + self._lock.acquire() + try: + present = fullname in self._cache + if not present: + funcs = self._callbacks.get(fullname) + if funcs is not None: + _v and LOG.debug('_request_module(%r): in flight', fullname) + funcs.append(callback) + else: + _v and LOG.debug('_request_module(%r): new request', fullname) + self._callbacks[fullname] = [callback] + self._context.send( + Message(data=b(fullname), handle=GET_MODULE) + ) + finally: + self._lock.release() + + if present: + callback() + + def load_module(self, fullname): + fullname = to_text(fullname) + _v and LOG.debug('Importer.load_module(%r)', fullname) + self._refuse_imports(fullname) + + event = threading.Event() + self._request_module(fullname, event.set) + event.wait() + + ret = self._cache[fullname] + if ret[2] is None: + raise ModuleNotFoundError(self.absent_msg % (fullname,)) + + pkg_present = ret[1] + mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) + mod.__file__ = self.get_filename(fullname) + mod.__loader__ = self + if pkg_present is not None: # it's a package. + mod.__path__ = [] + mod.__package__ = fullname + self._present[fullname] = pkg_present + else: + mod.__package__ = str_rpartition(fullname, '.')[0] or None + + if mod.__package__ and not PY3: + # 2.x requires __package__ to be exactly a string. + mod.__package__, _ = UTF8_CODEC.encode(mod.__package__) + + source = self.get_source(fullname) + try: + code = compile(source, mod.__file__, 'exec', 0, 1) + except SyntaxError: + LOG.exception('while importing %r', fullname) + raise + + if PY3: + exec(code, vars(mod)) + else: + exec('exec code in vars(mod)') + return mod + + def get_filename(self, fullname): + if fullname in self._cache: + path = self._cache[fullname][2] + if path is None: + # If find_loader() returns self but a subsequent master RPC + # reveals the module can't be loaded, and so load_module() + # throws ImportError, on Python 3.x it is still possible for + # the loader to be called to fetch metadata. + raise ModuleNotFoundError(self.absent_msg % (fullname,)) + return u'master:' + self._cache[fullname][2] + + def get_source(self, fullname): + if fullname in self._cache: + compressed = self._cache[fullname][3] + if compressed is None: + raise ModuleNotFoundError(self.absent_msg % (fullname,)) + + source = zlib.decompress(self._cache[fullname][3]) + if PY3: + return to_text(source) + return source + + +class LogHandler(logging.Handler): + def __init__(self, context): + logging.Handler.__init__(self) + self.context = context + self.local = threading.local() + self._buffer = [] + + def uncork(self): + """ + #305: during startup :class:`LogHandler` may be installed before it is + possible to route messages, therefore messages are buffered until + :meth:`uncork` is called by :class:`ExternalContext`. + """ + self._send = self.context.send + for msg in self._buffer: + self._send(msg) + self._buffer = None + + def _send(self, msg): + self._buffer.append(msg) + + def emit(self, rec): + if rec.name == 'mitogen.io' or \ + getattr(self.local, 'in_emit', False): + return + + self.local.in_emit = True + try: + msg = self.format(rec) + encoded = '%s\x00%s\x00%s' % (rec.name, rec.levelno, msg) + if isinstance(encoded, UnicodeType): + # Logging package emits both :( + encoded = encoded.encode('utf-8') + self._send(Message(data=encoded, handle=FORWARD_LOG)) + finally: + self.local.in_emit = False + + +class Side(object): + """ + Represent a single side of a :class:`BasicStream`. This exists to allow + streams implemented using unidirectional (e.g. UNIX pipe) and bidirectional + (e.g. UNIX socket) file descriptors to operate identically. + + :param mitogen.core.Stream stream: + The stream this side is associated with. + + :param int fd: + Underlying file descriptor. + + :param bool keep_alive: + Value for :attr:`keep_alive` + + During construction, the file descriptor has its :data:`os.O_NONBLOCK` flag + enabled using :func:`fcntl.fcntl`. + """ + _fork_refs = weakref.WeakValueDictionary() + + def __init__(self, stream, fd, cloexec=True, keep_alive=True, blocking=False): + #: The :class:`Stream` for which this is a read or write side. + self.stream = stream + #: Integer file descriptor to perform IO on, or :data:`None` if + #: :meth:`close` has been called. + self.fd = fd + self.closed = False + #: If :data:`True`, causes presence of this side in + #: :class:`Broker`'s active reader set to defer shutdown until the + #: side is disconnected. + self.keep_alive = keep_alive + self._fork_refs[id(self)] = self + if cloexec: + set_cloexec(fd) + if not blocking: + set_nonblock(fd) + + def __repr__(self): + return '' % (self.stream, self.fd) + + @classmethod + def _on_fork(cls): + while cls._fork_refs: + _, side = cls._fork_refs.popitem() + _vv and IOLOG.debug('Side._on_fork() closing %r', side) + side.close() + + def close(self): + """ + Call :func:`os.close` on :attr:`fd` if it is not :data:`None`, + then set it to :data:`None`. + """ + if not self.closed: + _vv and IOLOG.debug('%r.close()', self) + self.closed = True + os.close(self.fd) + + def read(self, n=CHUNK_SIZE): + """ + Read up to `n` bytes from the file descriptor, wrapping the underlying + :func:`os.read` call with :func:`io_op` to trap common disconnection + conditions. + + :meth:`read` always behaves as if it is reading from a regular UNIX + file; socket, pipe, and TTY disconnection errors are masked and result + in a 0-sized read like a regular file. + + :returns: + Bytes read, or the empty to string to indicate disconnection was + detected. + """ + if self.closed: + # Refuse to touch the handle after closed, it may have been reused + # by another thread. TODO: synchronize read()/write()/close(). + return b('') + s, disconnected = io_op(os.read, self.fd, n) + if disconnected: + LOG.debug('%r.read(): disconnected: %s', self, disconnected) + return b('') + return s + + def write(self, s): + """ + Write as much of the bytes from `s` as possible to the file descriptor, + wrapping the underlying :func:`os.write` call with :func:`io_op` to + trap common disconnection conditions. + + :returns: + Number of bytes written, or :data:`None` if disconnection was + detected. + """ + if self.closed or self.fd is None: + # Refuse to touch the handle after closed, it may have been reused + # by another thread. + return None + + written, disconnected = io_op(os.write, self.fd, s) + if disconnected: + LOG.debug('%r.write(): disconnected: %s', self, disconnected) + return None + return written + + +class BasicStream(object): + #: A :class:`Side` representing the stream's receive file descriptor. + receive_side = None + + #: A :class:`Side` representing the stream's transmit file descriptor. + transmit_side = None + + def on_receive(self, broker): + """ + Called by :class:`Broker` when the stream's :attr:`receive_side` has + been marked readable using :meth:`Broker.start_receive` and the broker + has detected the associated file descriptor is ready for reading. + + Subclasses must implement this if :meth:`Broker.start_receive` is ever + called on them, and the method must call :meth:`on_disconect` if + reading produces an empty string. + """ + pass + + def on_transmit(self, broker): + """ + Called by :class:`Broker` when the stream's :attr:`transmit_side` + has been marked writeable using :meth:`Broker._start_transmit` and + the broker has detected the associated file descriptor is ready for + writing. + + Subclasses must implement this if :meth:`Broker._start_transmit` is + ever called on them. + """ + pass + + def on_shutdown(self, broker): + """ + Called by :meth:`Broker.shutdown` to allow the stream time to + gracefully shutdown. The base implementation simply called + :meth:`on_disconnect`. + """ + _v and LOG.debug('%r.on_shutdown()', self) + fire(self, 'shutdown') + self.on_disconnect(broker) + + def on_disconnect(self, broker): + """ + Called by :class:`Broker` to force disconnect the stream. The base + implementation simply closes :attr:`receive_side` and + :attr:`transmit_side` and unregisters the stream from the broker. + """ + LOG.debug('%r.on_disconnect()', self) + if self.receive_side: + broker.stop_receive(self) + self.receive_side.close() + if self.transmit_side: + broker._stop_transmit(self) + self.transmit_side.close() + fire(self, 'disconnect') + + +class Stream(BasicStream): + """ + :class:`BasicStream` subclass implementing mitogen's :ref:`stream + protocol `. + """ + #: If not :data:`None`, :class:`Router` stamps this into + #: :attr:`Message.auth_id` of every message received on this stream. + auth_id = None + + #: If not :data:`False`, indicates the stream has :attr:`auth_id` set and + #: its value is the same as :data:`mitogen.context_id` or appears in + #: :data:`mitogen.parent_ids`. + is_privileged = False + + def __init__(self, router, remote_id, **kwargs): + self._router = router + self.remote_id = remote_id + self.name = u'default' + self.sent_modules = set(['mitogen', 'mitogen.core']) + self.construct(**kwargs) + self._input_buf = collections.deque() + self._output_buf = collections.deque() + self._input_buf_len = 0 + self._output_buf_len = 0 + #: Routing records the dst_id of every message arriving from this + #: stream. Any arriving DEL_ROUTE is rebroadcast for any such ID. + self.egress_ids = set() + + def construct(self): + pass + + def _internal_receive(self, broker, buf): + if self._input_buf and self._input_buf_len < 128: + self._input_buf[0] += buf + else: + self._input_buf.append(buf) + + self._input_buf_len += len(buf) + while self._receive_one(broker): + pass + + def on_receive(self, broker): + """Handle the next complete message on the stream. Raise + :class:`StreamError` on failure.""" + _vv and IOLOG.debug('%r.on_receive()', self) + + buf = self.receive_side.read() + if not buf: + return self.on_disconnect(broker) + + self._internal_receive(broker, buf) + + HEADER_FMT = '>hLLLLLL' + HEADER_LEN = struct.calcsize(HEADER_FMT) + HEADER_MAGIC = 0x4d49 # 'MI' + + corrupt_msg = ( + 'Corruption detected: frame signature incorrect. This likely means ' + 'some external process is interfering with the connection. Received:' + '\n\n' + '%r' + ) + + def _receive_one(self, broker): + if self._input_buf_len < self.HEADER_LEN: + return False + + msg = Message() + msg.router = self._router + (magic, msg.dst_id, msg.src_id, msg.auth_id, + msg.handle, msg.reply_to, msg_len) = struct.unpack( + self.HEADER_FMT, + self._input_buf[0][:self.HEADER_LEN], + ) + + if magic != self.HEADER_MAGIC: + LOG.error(self.corrupt_msg, self._input_buf[0][:2048]) + self.on_disconnect(broker) + return False + + if msg_len > self._router.max_message_size: + LOG.error('Maximum message size exceeded (got %d, max %d)', + msg_len, self._router.max_message_size) + self.on_disconnect(broker) + return False + + total_len = msg_len + self.HEADER_LEN + if self._input_buf_len < total_len: + _vv and IOLOG.debug( + '%r: Input too short (want %d, got %d)', + self, msg_len, self._input_buf_len - self.HEADER_LEN + ) + return False + + start = self.HEADER_LEN + prev_start = start + remain = total_len + bits = [] + while remain: + buf = self._input_buf.popleft() + bit = buf[start:remain] + bits.append(bit) + remain -= len(bit) + start + prev_start = start + start = 0 + + msg.data = b('').join(bits) + self._input_buf.appendleft(buf[prev_start+len(bit):]) + self._input_buf_len -= total_len + self._router._async_route(msg, self) + return True + + def pending_bytes(self): + """ + Return the number of bytes queued for transmission on this stream. This + can be used to limit the amount of data buffered in RAM by an otherwise + unlimited consumer. + + For an accurate result, this method should be called from the Broker + thread, for example by using :meth:`Broker.defer_sync`. + """ + return self._output_buf_len + + def on_transmit(self, broker): + """Transmit buffered messages.""" + _vv and IOLOG.debug('%r.on_transmit()', self) + + if self._output_buf: + buf = self._output_buf.popleft() + written = self.transmit_side.write(buf) + if not written: + _v and LOG.debug('%r.on_transmit(): disconnection detected', self) + self.on_disconnect(broker) + return + elif written != len(buf): + self._output_buf.appendleft(BufferType(buf, written)) + + _vv and IOLOG.debug('%r.on_transmit() -> len %d', self, written) + self._output_buf_len -= written + + if not self._output_buf: + broker._stop_transmit(self) + + def _send(self, msg): + _vv and IOLOG.debug('%r._send(%r)', self, msg) + pkt = struct.pack(self.HEADER_FMT, self.HEADER_MAGIC, msg.dst_id, + msg.src_id, msg.auth_id, msg.handle, + msg.reply_to or 0, len(msg.data)) + msg.data + + if not self._output_buf_len: + # Modifying epoll/Kqueue state is expensive, as are needless broker + # loops. Rather than wait for writeability, just write immediately, + # and fall back to the broker loop on error or full buffer. + try: + n = self.transmit_side.write(pkt) + if n: + if n == len(pkt): + return + pkt = pkt[n:] + except OSError: + pass + + self._router.broker._start_transmit(self) + self._output_buf.append(pkt) + self._output_buf_len += len(pkt) + + def send(self, msg): + """Send `data` to `handle`, and tell the broker we have output. May + be called from any thread.""" + self._router.broker.defer(self._send, msg) + + def on_shutdown(self, broker): + """Override BasicStream behaviour of immediately disconnecting.""" + _v and LOG.debug('%r.on_shutdown(%r)', self, broker) + + def accept(self, rfd, wfd): + # TODO: what is this os.dup for? + self.receive_side = Side(self, os.dup(rfd)) + self.transmit_side = Side(self, os.dup(wfd)) + + def __repr__(self): + cls = type(self) + return "%s.%s('%s')" % (cls.__module__, cls.__name__, self.name) + + +class Context(object): + """ + Represent a remote context regardless of the underlying connection method. + Context objects are simple facades that emit messages through an + associated router, and have :ref:`signals` raised against them in response + to various events relating to the context. + + **Note:** This is the somewhat limited core version, used by child + contexts. The master subclass is documented below this one. + + Contexts maintain no internal state and are thread-safe. + + Prefer :meth:`Router.context_by_id` over constructing context objects + explicitly, as that method is deduplicating, and returns the only context + instance :ref:`signals` will be raised on. + + :param Router router: + Router to emit messages through. + :param int context_id: + Context ID. + :param str name: + Context name. + """ + remote_name = None + + def __init__(self, router, context_id, name=None): + self.router = router + self.context_id = context_id + self.name = name + + def __reduce__(self): + name = self.name + if name and not isinstance(name, UnicodeType): + name = UnicodeType(name, 'utf-8') + return _unpickle_context, (self.context_id, name) + + def on_disconnect(self): + _v and LOG.debug('%r.on_disconnect()', self) + fire(self, 'disconnect') + + def send_async(self, msg, persist=False): + """ + Arrange for `msg` to be delivered to this context, with replies + directed to a newly constructed receiver. :attr:`dst_id + ` is set to the target context ID, and :attr:`reply_to + ` is set to the newly constructed receiver's handle. + + :param bool persist: + If :data:`False`, the handler will be unregistered after a single + message has been received. + + :param mitogen.core.Message msg: + The message. + + :returns: + :class:`Receiver` configured to receive any replies sent to the + message's `reply_to` handle. + """ + if self.router.broker._thread == threading.currentThread(): # TODO + raise SystemError('Cannot making blocking call on broker thread') + + receiver = Receiver(self.router, persist=persist, respondent=self) + msg.dst_id = self.context_id + msg.reply_to = receiver.handle + + _v and LOG.debug('%r.send_async(%r)', self, msg) + self.send(msg) + return receiver + + def call_service_async(self, service_name, method_name, **kwargs): + _v and LOG.debug('%r.call_service_async(%r, %r, %r)', + self, service_name, method_name, kwargs) + if isinstance(service_name, BytesType): + service_name = service_name.encode('utf-8') + elif not isinstance(service_name, UnicodeType): + service_name = service_name.name() # Service.name() + tup = (service_name, to_text(method_name), Kwargs(kwargs)) + msg = Message.pickled(tup, handle=CALL_SERVICE) + return self.send_async(msg) + + def send(self, msg): + """ + Arrange for `msg` to be delivered to this context. :attr:`dst_id + ` is set to the target context ID. + + :param Message msg: + Message. + """ + msg.dst_id = self.context_id + self.router.route(msg) + + def call_service(self, service_name, method_name, **kwargs): + recv = self.call_service_async(service_name, method_name, **kwargs) + return recv.get().unpickle() + + def send_await(self, msg, deadline=None): + """ + Like :meth:`send_async`, but expect a single reply (`persist=False`) + delivered within `deadline` seconds. + + :param mitogen.core.Message msg: + The message. + :param float deadline: + If not :data:`None`, seconds before timing out waiting for a reply. + :returns: + Deserialized reply. + :raises TimeoutError: + No message was received and `deadline` passed. + """ + receiver = self.send_async(msg) + response = receiver.get(deadline) + data = response.unpickle() + _vv and IOLOG.debug('%r._send_await() -> %r', self, data) + return data + + def __repr__(self): + return 'Context(%s, %r)' % (self.context_id, self.name) + + +def _unpickle_context(context_id, name, router=None): + if not (isinstance(context_id, (int, long)) and context_id >= 0 and ( + (name is None) or + (isinstance(name, UnicodeType) and len(name) < 100)) + ): + raise TypeError('cannot unpickle Context: bad input') + + if isinstance(router, Router): + return router.context_by_id(context_id, name=name) + return Context(None, context_id, name) # For plain Jane pickle. + + +class Poller(object): + """ + A poller manages OS file descriptors the user is waiting to become + available for IO. The :meth:`poll` method blocks the calling thread + until one or more become ready. The default implementation is based on + :func:`select.poll`. + + Each descriptor has an associated `data` element, which is unique for each + readiness type, and defaults to being the same as the file descriptor. The + :meth:`poll` method yields the data associated with a descriptor, rather + than the descriptor itself, allowing concise loops like:: + + p = Poller() + p.start_receive(conn.fd, data=conn.on_read) + p.start_transmit(conn.fd, data=conn.on_write) + + for callback in p.poll(): + callback() # invoke appropriate bound instance method + + Pollers may be modified while :meth:`poll` is yielding results. Removals + are processed immediately, causing pending events for the descriptor to be + discarded. + + The :meth:`close` method must be called when a poller is discarded to avoid + a resource leak. + + Pollers may only be used by one thread at a time. + """ + SUPPORTED = True + + # This changed from select() to poll() in Mitogen 0.2.4. Since poll() has + # no upper FD limit, it is suitable for use with Latch, which must handle + # FDs larger than select's limit during many-host runs. We want this + # because poll() requires no setup and teardown: just a single system call, + # which is important because Latch.get() creates a Poller on each + # invocation. In a microbenchmark, poll() vs. epoll_ctl() is 30% faster in + # this scenario. If select() must return in future, it is important + # Latch.poller_class is set from parent.py to point to the industrial + # strength poller for the OS, otherwise Latch will fail randomly. + + #: Increments on every poll(). Used to version _rfds and _wfds. + _generation = 1 + + def __init__(self): + self._rfds = {} + self._wfds = {} + + def __repr__(self): + return '%s(%#x)' % (type(self).__name__, id(self)) + + def _update(self, fd): + """ + Required by PollPoller subclass. + """ + pass + + @property + def readers(self): + """ + Return a list of `(fd, data)` tuples for every FD registered for + receive readiness. + """ + return list((fd, data) for fd, (data, gen) in self._rfds.items()) + + @property + def writers(self): + """ + Return a list of `(fd, data)` tuples for every FD registered for + transmit readiness. + """ + return list((fd, data) for fd, (data, gen) in self._wfds.items()) + + def close(self): + """ + Close any underlying OS resource used by the poller. + """ + pass + + def start_receive(self, fd, data=None): + """ + Cause :meth:`poll` to yield `data` when `fd` is readable. + """ + self._rfds[fd] = (data or fd, self._generation) + self._update(fd) + + def stop_receive(self, fd): + """ + Stop yielding readability events for `fd`. + + Redundant calls to :meth:`stop_receive` are silently ignored, this may + change in future. + """ + self._rfds.pop(fd, None) + self._update(fd) + + def start_transmit(self, fd, data=None): + """ + Cause :meth:`poll` to yield `data` when `fd` is writeable. + """ + self._wfds[fd] = (data or fd, self._generation) + self._update(fd) + + def stop_transmit(self, fd): + """ + Stop yielding writeability events for `fd`. + + Redundant calls to :meth:`stop_transmit` are silently ignored, this may + change in future. + """ + self._wfds.pop(fd, None) + self._update(fd) + + def _poll(self, timeout): + (rfds, wfds, _), _ = io_op(select.select, + self._rfds, + self._wfds, + (), timeout + ) + + for fd in rfds: + _vv and IOLOG.debug('%r: POLLIN for %r', self, fd) + data, gen = self._rfds.get(fd, (None, None)) + if gen and gen < self._generation: + yield data + + for fd in wfds: + _vv and IOLOG.debug('%r: POLLOUT for %r', self, fd) + data, gen = self._wfds.get(fd, (None, None)) + if gen and gen < self._generation: + yield data + + if timeout: + timeout *= 1000 + + def poll(self, timeout=None): + """ + Block the calling thread until one or more FDs are ready for IO. + + :param float timeout: + If not :data:`None`, seconds to wait without an event before + returning an empty iterable. + :returns: + Iterable of `data` elements associated with ready FDs. + """ + _vv and IOLOG.debug('%r.poll(%r)', self, timeout) + self._generation += 1 + return self._poll(timeout) + + +class Latch(object): + """ + A latch is a :class:`Queue.Queue`-like object that supports mutation and + waiting from multiple threads, however unlike :class:`Queue.Queue`, + waiting threads always remain interruptible, so CTRL+C always succeeds, and + waits where a timeout is set experience no wake up latency. These + properties are not possible in combination using the built-in threading + primitives available in Python 2.x. + + Latches implement queues using the UNIX self-pipe trick, and a per-thread + :func:`socket.socketpair` that is lazily created the first time any + latch attempts to sleep on a thread, and dynamically associated with the + waiting Latch only for duration of the wait. + + See :ref:`waking-sleeping-threads` for further discussion. + """ + poller_class = Poller + + notify = None + + # The _cls_ prefixes here are to make it crystal clear in the code which + # state mutation isn't covered by :attr:`_lock`. + + #: List of reusable :func:`socket.socketpair` tuples. The list is mutated + #: from multiple threads, the only safe operations are `append()` and + #: `pop()`. + _cls_idle_socketpairs = [] + + #: List of every socket object that must be closed by :meth:`_on_fork`. + #: Inherited descriptors cannot be reused, as the duplicated handles + #: reference the same underlying kernel object in use by the parent. + _cls_all_sockets = [] + + def __init__(self): + self.closed = False + self._lock = threading.Lock() + #: List of unconsumed enqueued items. + self._queue = [] + #: List of `(wsock, cookie)` awaiting an element, where `wsock` is the + #: socketpair's write side, and `cookie` is the string to write. + self._sleeping = [] + #: Number of elements of :attr:`_sleeping` that have already been + #: woken, and have a corresponding element index from :attr:`_queue` + #: assigned to them. + self._waking = 0 + + @classmethod + def _on_fork(cls): + """ + Clean up any files belonging to the parent process after a fork. + """ + cls._cls_idle_socketpairs = [] + while cls._cls_all_sockets: + cls._cls_all_sockets.pop().close() + + def close(self): + """ + Mark the latch as closed, and cause every sleeping thread to be woken, + with :class:`mitogen.core.LatchError` raised in each thread. + """ + self._lock.acquire() + try: + self.closed = True + while self._waking < len(self._sleeping): + wsock, cookie = self._sleeping[self._waking] + self._wake(wsock, cookie) + self._waking += 1 + finally: + self._lock.release() + + def empty(self): + """ + Return :data:`True` if calling :meth:`get` would block. + + As with :class:`Queue.Queue`, :data:`True` may be returned even + though a subsequent call to :meth:`get` will succeed, since a + message may be posted at any moment between :meth:`empty` and + :meth:`get`. + + As with :class:`Queue.Queue`, :data:`False` may be returned even + though a subsequent call to :meth:`get` will block, since another + waiting thread may be woken at any moment between :meth:`empty` and + :meth:`get`. + + :raises LatchError: + The latch has already been marked closed. + """ + self._lock.acquire() + try: + if self.closed: + raise LatchError() + return len(self._queue) == 0 + finally: + self._lock.release() + + def _get_socketpair(self): + """ + Return an unused socketpair, creating one if none exist. + """ + try: + return self._cls_idle_socketpairs.pop() # pop() must be atomic + except IndexError: + rsock, wsock = socket.socketpair() + set_cloexec(rsock.fileno()) + set_cloexec(wsock.fileno()) + self._cls_all_sockets.extend((rsock, wsock)) + return rsock, wsock + + COOKIE_MAGIC, = struct.unpack('L', b('LTCH') * (struct.calcsize('L')//4)) + COOKIE_FMT = '>Qqqq' # #545: id() and get_ident() may exceed long on armhfp. + COOKIE_SIZE = struct.calcsize(COOKIE_FMT) + + def _make_cookie(self): + """ + Return a string encoding the ID of the process, instance and thread. + This disambiguates legitimate wake-ups, accidental writes to the FD, + and buggy internal FD sharing. + """ + return struct.pack(self.COOKIE_FMT, self.COOKIE_MAGIC, + os.getpid(), id(self), thread.get_ident()) + + def get(self, timeout=None, block=True): + """ + Return the next enqueued object, or sleep waiting for one. + + :param float timeout: + If not :data:`None`, specifies a timeout in seconds. + + :param bool block: + If :data:`False`, immediately raise + :class:`mitogen.core.TimeoutError` if the latch is empty. + + :raises mitogen.core.LatchError: + :meth:`close` has been called, and the object is no longer valid. + + :raises mitogen.core.TimeoutError: + Timeout was reached. + + :returns: + The de-queued object. + """ + _vv and IOLOG.debug('%r.get(timeout=%r, block=%r)', + self, timeout, block) + self._lock.acquire() + try: + if self.closed: + raise LatchError() + i = len(self._sleeping) + if len(self._queue) > i: + _vv and IOLOG.debug('%r.get() -> %r', self, self._queue[i]) + return self._queue.pop(i) + if not block: + raise TimeoutError() + rsock, wsock = self._get_socketpair() + cookie = self._make_cookie() + self._sleeping.append((wsock, cookie)) + finally: + self._lock.release() + + poller = self.poller_class() + poller.start_receive(rsock.fileno()) + try: + return self._get_sleep(poller, timeout, block, rsock, wsock, cookie) + finally: + poller.close() + + def _get_sleep(self, poller, timeout, block, rsock, wsock, cookie): + """ + When a result is not immediately available, sleep waiting for + :meth:`put` to write a byte to our socket pair. + """ + _vv and IOLOG.debug( + '%r._get_sleep(timeout=%r, block=%r, rfd=%d, wfd=%d)', + self, timeout, block, rsock.fileno(), wsock.fileno() + ) + + e = None + woken = None + try: + woken = list(poller.poll(timeout)) + except Exception: + e = sys.exc_info()[1] + + self._lock.acquire() + try: + i = self._sleeping.index((wsock, cookie)) + del self._sleeping[i] + if not woken: + raise e or TimeoutError() + + got_cookie = rsock.recv(self.COOKIE_SIZE) + self._cls_idle_socketpairs.append((rsock, wsock)) + + assert cookie == got_cookie, ( + "Cookie incorrect; got %r, expected %r" \ + % (binascii.hexlify(got_cookie), + binascii.hexlify(cookie)) + ) + assert i < self._waking, ( + "Cookie correct, but no queue element assigned." + ) + self._waking -= 1 + if self.closed: + raise LatchError() + _vv and IOLOG.debug('%r.get() wake -> %r', self, self._queue[i]) + return self._queue.pop(i) + finally: + self._lock.release() + + def put(self, obj=None): + """ + Enqueue an object, waking the first thread waiting for a result, if one + exists. + + :param obj: + Object to enqueue. Defaults to :data:`None` as a convenience when + using :class:`Latch` only for synchronization. + :raises mitogen.core.LatchError: + :meth:`close` has been called, and the object is no longer valid. + """ + _vv and IOLOG.debug('%r.put(%r)', self, obj) + self._lock.acquire() + try: + if self.closed: + raise LatchError() + self._queue.append(obj) + + if self._waking < len(self._sleeping): + wsock, cookie = self._sleeping[self._waking] + self._waking += 1 + _vv and IOLOG.debug('%r.put() -> waking wfd=%r', + self, wsock.fileno()) + self._wake(wsock, cookie) + elif self.notify: + self.notify(self) + finally: + self._lock.release() + + def _wake(self, wsock, cookie): + written, disconnected = io_op(os.write, wsock.fileno(), cookie) + assert written == len(cookie) and not disconnected + + def __repr__(self): + return 'Latch(%#x, size=%d, t=%r)' % ( + id(self), + len(self._queue), + threading.currentThread().getName(), + ) + + +class Waker(BasicStream): + """ + :class:`BasicStream` subclass implementing the `UNIX self-pipe trick`_. + Used to wake the multiplexer when another thread needs to modify its state + (via a cross-thread function call). + + .. _UNIX self-pipe trick: https://cr.yp.to/docs/selfpipe.html + """ + broker_ident = None + + def __init__(self, broker): + self._broker = broker + self._lock = threading.Lock() + self._deferred = [] + + rfd, wfd = os.pipe() + self.receive_side = Side(self, rfd) + self.transmit_side = Side(self, wfd) + + def __repr__(self): + return 'Waker(%r rfd=%r, wfd=%r)' % ( + self._broker, + self.receive_side and self.receive_side.fd, + self.transmit_side and self.transmit_side.fd, + ) + + @property + def keep_alive(self): + """ + Prevent immediate Broker shutdown while deferred functions remain. + """ + self._lock.acquire() + try: + return len(self._deferred) + finally: + self._lock.release() + + def on_receive(self, broker): + """ + Drain the pipe and fire callbacks. Since :attr:`_deferred` is + synchronized, :meth:`defer` and :meth:`on_receive` can conspire to + ensure only one byte needs to be pending regardless of queue length. + """ + _vv and IOLOG.debug('%r.on_receive()', self) + self._lock.acquire() + try: + self.receive_side.read(1) + deferred = self._deferred + self._deferred = [] + finally: + self._lock.release() + + for func, args, kwargs in deferred: + try: + func(*args, **kwargs) + except Exception: + LOG.exception('defer() crashed: %r(*%r, **%r)', + func, args, kwargs) + self._broker.shutdown() + + def _wake(self): + """ + Wake the multiplexer by writing a byte. If Broker is midway through + teardown, the FD may already be closed, so ignore EBADF. + """ + try: + self.transmit_side.write(b(' ')) + except OSError: + e = sys.exc_info()[1] + if e.args[0] != errno.EBADF: + raise + + broker_shutdown_msg = ( + "An attempt was made to enqueue a message with a Broker that has " + "already exitted. It is likely your program called Broker.shutdown() " + "too early." + ) + + def defer(self, func, *args, **kwargs): + """ + Arrange for `func()` to execute on the broker thread. This function + returns immediately without waiting the result of `func()`. Use + :meth:`defer_sync` to block until a result is available. + + :raises mitogen.core.Error: + :meth:`defer` was called after :class:`Broker` has begun shutdown. + """ + if thread.get_ident() == self.broker_ident: + _vv and IOLOG.debug('%r.defer() [immediate]', self) + return func(*args, **kwargs) + if self._broker._exitted: + raise Error(self.broker_shutdown_msg) + + _vv and IOLOG.debug('%r.defer() [fd=%r]', self, self.transmit_side.fd) + self._lock.acquire() + try: + if not self._deferred: + self._wake() + self._deferred.append((func, args, kwargs)) + finally: + self._lock.release() + + +class IoLogger(BasicStream): + """ + :class:`BasicStream` subclass that sets up redirection of a standard + UNIX file descriptor back into the Python :mod:`logging` package. + """ + _buf = '' + + def __init__(self, broker, name, dest_fd): + self._broker = broker + self._name = name + self._rsock, self._wsock = socket.socketpair() + os.dup2(self._wsock.fileno(), dest_fd) + set_cloexec(self._wsock.fileno()) + + self._log = logging.getLogger(name) + # #453: prevent accidental log initialization in a child creating a + # feedback loop. + self._log.propagate = False + self._log.handlers = logging.getLogger().handlers[:] + + self.receive_side = Side(self, self._rsock.fileno()) + self.transmit_side = Side(self, dest_fd, cloexec=False, blocking=True) + self._broker.start_receive(self) + + def __repr__(self): + return '' % (self._name,) + + def _log_lines(self): + while self._buf.find('\n') != -1: + line, _, self._buf = str_partition(self._buf, '\n') + self._log.info('%s', line.rstrip('\n')) + + def on_shutdown(self, broker): + """Shut down the write end of the logging socket.""" + _v and LOG.debug('%r.on_shutdown()', self) + if not IS_WSL: + # #333: WSL generates invalid readiness indication on shutdown() + self._wsock.shutdown(socket.SHUT_WR) + self._wsock.close() + self.transmit_side.close() + + def on_receive(self, broker): + _vv and IOLOG.debug('%r.on_receive()', self) + buf = self.receive_side.read() + if not buf: + return self.on_disconnect(broker) + + self._buf += buf.decode('latin1') + self._log_lines() + + +class Router(object): + """ + Route messages between contexts, and invoke local handlers for messages + addressed to this context. :meth:`Router.route() ` straddles the + :class:`Broker` thread and user threads, it is safe to call anywhere. + + **Note:** This is the somewhat limited core version of the Router class + used by child contexts. The master subclass is documented below this one. + """ + context_class = Context + max_message_size = 128 * 1048576 + + #: When :data:`True`, permit children to only communicate with the current + #: context or a parent of the current context. Routing between siblings or + #: children of parents is prohibited, ensuring no communication is possible + #: between intentionally partitioned networks, such as when a program + #: simultaneously manipulates hosts spread across a corporate and a + #: production network, or production networks that are otherwise + #: air-gapped. + #: + #: Sending a prohibited message causes an error to be logged and a dead + #: message to be sent in reply to the errant message, if that message has + #: ``reply_to`` set. + #: + #: The value of :data:`unidirectional` becomes the default for the + #: :meth:`local() ` `unidirectional` + #: parameter. + unidirectional = False + + def __init__(self, broker): + self.broker = broker + listen(broker, 'exit', self._on_broker_exit) + self._setup_logging() + + self._write_lock = threading.Lock() + #: context ID -> Stream; must hold _write_lock to edit or iterate + self._stream_by_id = {} + #: List of contexts to notify of shutdown; must hold _write_lock + self._context_by_id = {} + self._last_handle = itertools.count(1000) + #: handle -> (persistent?, func(msg)) + self._handle_map = {} + #: Context -> set { handle, .. } + self._handles_by_respondent = {} + self.add_handler(self._on_del_route, DEL_ROUTE) + + def __repr__(self): + return 'Router(%r)' % (self.broker,) + + def _setup_logging(self): + """ + This is done in the :class:`Router` constructor for historical reasons. + It must be called before ExternalContext logs its first messages, but + after logging has been setup. It must also be called when any router is + constructed for a consumer app. + """ + # Here seems as good a place as any. + global _v, _vv + _v = logging.getLogger().level <= logging.DEBUG + _vv = IOLOG.level <= logging.DEBUG + + def _on_del_route(self, msg): + """ + Stub :data:`DEL_ROUTE` handler; fires 'disconnect' events on the + corresponding :attr:`_context_by_id` member. This is replaced by + :class:`mitogen.parent.RouteMonitor` in an upgraded context. + """ + LOG.error('%r._on_del_route() %r', self, msg) + if msg.is_dead: + return + + target_id_s, _, name = bytes_partition(msg.data, b(':')) + target_id = int(target_id_s, 10) + context = self._context_by_id.get(target_id) + if context: + fire(context, 'disconnect') + else: + LOG.debug('DEL_ROUTE for unknown ID %r: %r', target_id, msg) + + def _on_stream_disconnect(self, stream): + notify = [] + self._write_lock.acquire() + try: + for context in list(self._context_by_id.values()): + stream_ = self._stream_by_id.get(context.context_id) + if stream_ is stream: + del self._stream_by_id[context.context_id] + notify.append(context) + finally: + self._write_lock.release() + + # Happens outside lock as e.g. RouteMonitor wants the same lock. + for context in notify: + context.on_disconnect() + + broker_exit_msg = 'Broker has exitted' + + def _on_broker_exit(self): + while self._handle_map: + _, (_, func, _, _) = self._handle_map.popitem() + func(Message.dead(self.broker_exit_msg)) + + def myself(self): + """ + Return a :class:`Context` referring to the current process. + """ + return self.context_class( + router=self, + context_id=mitogen.context_id, + name='self', + ) + + def context_by_id(self, context_id, via_id=None, create=True, name=None): + """ + Messy factory/lookup function to find a context by its ID, or construct + it. This will eventually be replaced by a more sensible interface. + """ + context = self._context_by_id.get(context_id) + if context: + return context + + if create and via_id is not None: + via = self.context_by_id(via_id) + else: + via = None + + self._write_lock.acquire() + try: + context = self._context_by_id.get(context_id) + if create and not context: + context = self.context_class(self, context_id, name=name) + context.via = via + self._context_by_id[context_id] = context + finally: + self._write_lock.release() + + return context + + def register(self, context, stream): + """ + Register a newly constructed context and its associated stream, and add + the stream's receive side to the I/O multiplexer. This method remains + public while the design has not yet settled. + """ + _v and LOG.debug('register(%r, %r)', context, stream) + self._write_lock.acquire() + try: + self._stream_by_id[context.context_id] = stream + self._context_by_id[context.context_id] = context + finally: + self._write_lock.release() + + self.broker.start_receive(stream) + listen(stream, 'disconnect', lambda: self._on_stream_disconnect(stream)) + + def stream_by_id(self, dst_id): + """ + Return the :class:`Stream` that should be used to communicate with + `dst_id`. If a specific route for `dst_id` is not known, a reference to + the parent context's stream is returned. + """ + return ( + self._stream_by_id.get(dst_id) or + self._stream_by_id.get(mitogen.parent_id) + ) + + def del_handler(self, handle): + """ + Remove the handle registered for `handle` + + :raises KeyError: + The handle wasn't registered. + """ + _, _, _, respondent = self._handle_map.pop(handle) + if respondent: + self._handles_by_respondent[respondent].discard(handle) + + def add_handler(self, fn, handle=None, persist=True, + policy=None, respondent=None, + overwrite=False): + """ + Invoke `fn(msg)` on the :class:`Broker` thread for each Message sent to + `handle` from this context. Unregister after one invocation if + `persist` is :data:`False`. If `handle` is :data:`None`, a new handle + is allocated and returned. + + :param int handle: + If not :data:`None`, an explicit handle to register, usually one of + the ``mitogen.core.*`` constants. If unspecified, a new unused + handle will be allocated. + + :param bool persist: + If :data:`False`, the handler will be unregistered after a single + message has been received. + + :param Context respondent: + Context that messages to this handle are expected to be sent from. + If specified, arranges for a dead message to be delivered to `fn` + when disconnection of the context is detected. + + In future `respondent` will likely also be used to prevent other + contexts from sending messages to the handle. + + :param function policy: + Function invoked as `policy(msg, stream)` where `msg` is a + :class:`mitogen.core.Message` about to be delivered, and `stream` + is the :class:`mitogen.core.Stream` on which it was received. The + function must return :data:`True`, otherwise an error is logged and + delivery is refused. + + Two built-in policy functions exist: + + * :func:`has_parent_authority`: requires the message arrived from a + parent context, or a context acting with a parent context's + authority (``auth_id``). + + * :func:`mitogen.parent.is_immediate_child`: requires the + message arrived from an immediately connected child, for use in + messaging patterns where either something becomes buggy or + insecure by permitting indirect upstream communication. + + In case of refusal, and the message's ``reply_to`` field is + nonzero, a :class:`mitogen.core.CallError` is delivered to the + sender indicating refusal occurred. + + :param bool overwrite: + If :data:`True`, allow existing handles to be silently overwritten. + + :return: + `handle`, or if `handle` was :data:`None`, the newly allocated + handle. + :raises Error: + Attemp to register handle that was already registered. + """ + handle = handle or next(self._last_handle) + _vv and IOLOG.debug('%r.add_handler(%r, %r, %r)', self, fn, handle, persist) + if handle in self._handle_map and not overwrite: + raise Error(self.duplicate_handle_msg) + + self._handle_map[handle] = persist, fn, policy, respondent + if respondent: + if respondent not in self._handles_by_respondent: + self._handles_by_respondent[respondent] = set() + listen(respondent, 'disconnect', + lambda: self._on_respondent_disconnect(respondent)) + self._handles_by_respondent[respondent].add(handle) + + return handle + + duplicate_handle_msg = 'cannot register a handle that is already exists' + refused_msg = 'refused by policy' + invalid_handle_msg = 'invalid handle' + too_large_msg = 'message too large (max %d bytes)' + respondent_disconnect_msg = 'the respondent Context has disconnected' + broker_shutdown_msg = 'Broker is shutting down' + no_route_msg = 'no route to %r, my ID is %r' + unidirectional_msg = ( + 'routing mode prevents forward of message from context %d via ' + 'context %d' + ) + + def _on_respondent_disconnect(self, context): + for handle in self._handles_by_respondent.pop(context, ()): + _, fn, _, _ = self._handle_map[handle] + fn(Message.dead(self.respondent_disconnect_msg)) + del self._handle_map[handle] + + def on_shutdown(self, broker): + """Called during :meth:`Broker.shutdown`, informs callbacks registered + with :meth:`add_handle_cb` the connection is dead.""" + _v and LOG.debug('%r.on_shutdown(%r)', self, broker) + fire(self, 'shutdown') + for handle, (persist, fn) in self._handle_map.iteritems(): + _v and LOG.debug('%r.on_shutdown(): killing %r: %r', self, handle, fn) + fn(Message.dead(self.broker_shutdown_msg)) + + def _maybe_send_dead(self, msg, reason, *args): + if args: + reason %= args + LOG.debug('%r: %r is dead: %r', self, msg, reason) + if msg.reply_to and not msg.is_dead: + msg.reply(Message.dead(reason=reason), router=self) + + def _invoke(self, msg, stream): + # IOLOG.debug('%r._invoke(%r)', self, msg) + try: + persist, fn, policy, respondent = self._handle_map[msg.handle] + except KeyError: + self._maybe_send_dead(msg, reason=self.invalid_handle_msg) + return + + if respondent and not (msg.is_dead or + msg.src_id == respondent.context_id): + self._maybe_send_dead(msg, 'reply from unexpected context') + return + + if policy and not policy(msg, stream): + self._maybe_send_dead(msg, self.refused_msg) + return + + if not persist: + self.del_handler(msg.handle) + + try: + fn(msg) + except Exception: + LOG.exception('%r._invoke(%r): %r crashed', self, msg, fn) + + def _async_route(self, msg, in_stream=None): + """ + Arrange for `msg` to be forwarded towards its destination. If its + destination is the local context, then arrange for it to be dispatched + using the local handlers. + + This is a lower overhead version of :meth:`route` that may only be + called from the :class:`Broker` thread. + + :param Stream in_stream: + If not :data:`None`, the stream the message arrived on. Used for + performing source route verification, to ensure sensitive messages + such as ``CALL_FUNCTION`` arrive only from trusted contexts. + """ + _vv and IOLOG.debug('%r._async_route(%r, %r)', self, msg, in_stream) + + if len(msg.data) > self.max_message_size: + self._maybe_send_dead(msg, self.too_large_msg % ( + self.max_message_size, + )) + return + + # Perform source verification. + if in_stream: + parent = self._stream_by_id.get(mitogen.parent_id) + expect = self._stream_by_id.get(msg.auth_id, parent) + if in_stream != expect: + LOG.error('%r: bad auth_id: got %r via %r, not %r: %r', + self, msg.auth_id, in_stream, expect, msg) + return + + if msg.src_id != msg.auth_id: + expect = self._stream_by_id.get(msg.src_id, parent) + if in_stream != expect: + LOG.error('%r: bad src_id: got %r via %r, not %r: %r', + self, msg.src_id, in_stream, expect, msg) + return + + if in_stream.auth_id is not None: + msg.auth_id = in_stream.auth_id + + # Maintain a set of IDs the source ever communicated with. + in_stream.egress_ids.add(msg.dst_id) + + if msg.dst_id == mitogen.context_id: + return self._invoke(msg, in_stream) + + out_stream = self._stream_by_id.get(msg.dst_id) + if out_stream is None: + out_stream = self._stream_by_id.get(mitogen.parent_id) + + if out_stream is None: + self._maybe_send_dead(msg, self.no_route_msg, + msg.dst_id, mitogen.context_id) + return + + if in_stream and self.unidirectional and not \ + (in_stream.is_privileged or out_stream.is_privileged): + self._maybe_send_dead(msg, self.unidirectional_msg, + in_stream.remote_id, out_stream.remote_id) + return + + out_stream._send(msg) + + def route(self, msg): + """ + Arrange for the :class:`Message` `msg` to be delivered to its + destination using any relevant downstream context, or if none is found, + by forwarding the message upstream towards the master context. If `msg` + is destined for the local context, it is dispatched using the handles + registered with :meth:`add_handler`. + + This may be called from any thread. + """ + self.broker.defer(self._async_route, msg) + + +class Broker(object): + """ + Responsible for handling I/O multiplexing in a private thread. + + **Note:** This is the somewhat limited core version of the Broker class + used by child contexts. The master subclass is documented below. + """ + poller_class = Poller + _waker = None + _thread = None + + #: Seconds grace to allow :class:`streams ` to shutdown gracefully + #: before force-disconnecting them during :meth:`shutdown`. + shutdown_timeout = 3.0 + + def __init__(self, poller_class=None, activate_compat=True): + self._alive = True + self._exitted = False + self._waker = Waker(self) + #: Arrange for `func(\*args, \**kwargs)` to be executed on the broker + #: thread, or immediately if the current thread is the broker thread. + #: Safe to call from any thread. + self.defer = self._waker.defer + self.poller = self.poller_class() + self.poller.start_receive( + self._waker.receive_side.fd, + (self._waker.receive_side, self._waker.on_receive) + ) + self._thread = threading.Thread( + target=self._broker_main, + name='mitogen.broker' + ) + self._thread.start() + if activate_compat: + self._py24_25_compat() + + def _py24_25_compat(self): + """ + Python 2.4/2.5 have grave difficulties with threads/fork. We + mandatorily quiesce all running threads during fork using a + monkey-patch there. + """ + if sys.version_info < (2, 6): + # import_module() is used to avoid dep scanner. + os_fork = import_module('mitogen.os_fork') + mitogen.os_fork._notice_broker_or_pool(self) + + def start_receive(self, stream): + """ + Mark the :attr:`receive_side ` on `stream` as + ready for reading. Safe to call from any thread. When the associated + file descriptor becomes ready for reading, + :meth:`BasicStream.on_receive` will be called. + """ + _vv and IOLOG.debug('%r.start_receive(%r)', self, stream) + side = stream.receive_side + assert side and side.fd is not None + self.defer(self.poller.start_receive, + side.fd, (side, stream.on_receive)) + + def stop_receive(self, stream): + """ + Mark the :attr:`receive_side ` on `stream` as not + ready for reading. Safe to call from any thread. + """ + _vv and IOLOG.debug('%r.stop_receive(%r)', self, stream) + self.defer(self.poller.stop_receive, stream.receive_side.fd) + + def _start_transmit(self, stream): + """ + Mark the :attr:`transmit_side ` on `stream` as + ready for writing. Must only be called from the Broker thread. When the + associated file descriptor becomes ready for writing, + :meth:`BasicStream.on_transmit` will be called. + """ + _vv and IOLOG.debug('%r._start_transmit(%r)', self, stream) + side = stream.transmit_side + assert side and side.fd is not None + self.poller.start_transmit(side.fd, (side, stream.on_transmit)) + + def _stop_transmit(self, stream): + """ + Mark the :attr:`transmit_side ` on `stream` as not + ready for writing. + """ + _vv and IOLOG.debug('%r._stop_transmit(%r)', self, stream) + self.poller.stop_transmit(stream.transmit_side.fd) + + def keep_alive(self): + """ + Return :data:`True` if any reader's :attr:`Side.keep_alive` attribute + is :data:`True`, or any :class:`Context` is still registered that is + not the master. Used to delay shutdown while some important work is in + progress (e.g. log draining). + """ + it = (side.keep_alive for (_, (side, _)) in self.poller.readers) + return sum(it, 0) + + def defer_sync(self, func): + """ + Arrange for `func()` to execute on :class:`Broker` thread, blocking the + current thread until a result or exception is available. + + :returns: + Return value of `func()`. + """ + latch = Latch() + def wrapper(): + try: + latch.put(func()) + except Exception: + latch.put(sys.exc_info()[1]) + self.defer(wrapper) + res = latch.get() + if isinstance(res, Exception): + raise res + return res + + def _call(self, stream, func): + """ + Call `func(self)`, catching any exception that might occur, logging it, + and force-disconnecting the related `stream`. + """ + try: + func(self) + except Exception: + LOG.exception('%r crashed', stream) + stream.on_disconnect(self) + + def _loop_once(self, timeout=None): + """ + Execute a single :class:`Poller` wait, dispatching any IO events that + caused the wait to complete. + + :param float timeout: + If not :data:`None`, maximum time in seconds to wait for events. + """ + _vv and IOLOG.debug('%r._loop_once(%r, %r)', + self, timeout, self.poller) + #IOLOG.debug('readers =\n%s', pformat(self.poller.readers)) + #IOLOG.debug('writers =\n%s', pformat(self.poller.writers)) + for side, func in self.poller.poll(timeout): + self._call(side.stream, func) + + def _broker_exit(self): + """ + Forcefully call :meth:`Stream.on_disconnect` on any streams that failed + to shut down gracefully, then discard the :class:`Poller`. + """ + for _, (side, _) in self.poller.readers + self.poller.writers: + LOG.debug('_broker_main() force disconnecting %r', side) + side.stream.on_disconnect(self) + + self.poller.close() + + def _broker_shutdown(self): + """ + Invoke :meth:`Stream.on_shutdown` for every active stream, then allow + up to :attr:`shutdown_timeout` seconds for the streams to unregister + themselves, logging an error if any did not unregister during the grace + period. + """ + for _, (side, _) in self.poller.readers + self.poller.writers: + self._call(side.stream, side.stream.on_shutdown) + + deadline = time.time() + self.shutdown_timeout + while self.keep_alive() and time.time() < deadline: + self._loop_once(max(0, deadline - time.time())) + + if self.keep_alive(): + LOG.error('%r: some streams did not close gracefully. ' + 'The most likely cause for this is one or ' + 'more child processes still connected to ' + 'our stdout/stderr pipes.', self) + + def _do_broker_main(self): + """ + Broker thread main function. Dispatches IO events until + :meth:`shutdown` is called. + """ + # For Python 2.4, no way to retrieve ident except on thread. + self._waker.broker_ident = thread.get_ident() + try: + while self._alive: + self._loop_once() + + fire(self, 'shutdown') + self._broker_shutdown() + except Exception: + LOG.exception('_broker_main() crashed') + + self._alive = False # Ensure _alive is consistent on crash. + self._exitted = True + self._broker_exit() + + def _broker_main(self): + _profile_hook('mitogen.broker', self._do_broker_main) + fire(self, 'exit') + + def shutdown(self): + """ + Request broker gracefully disconnect streams and stop. Safe to call + from any thread. + """ + _v and LOG.debug('%r.shutdown()', self) + def _shutdown(): + self._alive = False + if self._alive and not self._exitted: + self.defer(_shutdown) + + def join(self): + """ + Wait for the broker to stop, expected to be called after + :meth:`shutdown`. + """ + self._thread.join() + + def __repr__(self): + return 'Broker(%#x)' % (id(self),) + + +class Dispatcher(object): + """ + Implementation of the :data:`CALL_FUNCTION` handle for a child context. + Listens on the child's main thread for messages sent by + :class:`mitogen.parent.CallChain` and dispatches the function calls they + describe. + + If a :class:`mitogen.parent.CallChain` sending a message is in pipelined + mode, any exception that occurs is recorded, and causes all subsequent + calls with the same `chain_id` to fail with the same exception. + """ + def __init__(self, econtext): + self.econtext = econtext + #: Chain ID -> CallError if prior call failed. + self._error_by_chain_id = {} + self.recv = Receiver(router=econtext.router, + handle=CALL_FUNCTION, + policy=has_parent_authority) + listen(econtext.broker, 'shutdown', self.recv.close) + + @classmethod + @takes_econtext + def forget_chain(cls, chain_id, econtext): + econtext.dispatcher._error_by_chain_id.pop(chain_id, None) + + def _parse_request(self, msg): + data = msg.unpickle(throw=False) + _v and LOG.debug('_dispatch_one(%r)', data) + + chain_id, modname, klass, func, args, kwargs = data + obj = import_module(modname) + if klass: + obj = getattr(obj, klass) + fn = getattr(obj, func) + if getattr(fn, 'mitogen_takes_econtext', None): + kwargs.setdefault('econtext', self.econtext) + if getattr(fn, 'mitogen_takes_router', None): + kwargs.setdefault('router', self.econtext.router) + + return chain_id, fn, args, kwargs + + def _dispatch_one(self, msg): + try: + chain_id, fn, args, kwargs = self._parse_request(msg) + except Exception: + return None, CallError(sys.exc_info()[1]) + + if chain_id in self._error_by_chain_id: + return chain_id, self._error_by_chain_id[chain_id] + + try: + return chain_id, fn(*args, **kwargs) + except Exception: + e = CallError(sys.exc_info()[1]) + if chain_id is not None: + self._error_by_chain_id[chain_id] = e + return chain_id, e + + def _dispatch_calls(self): + for msg in self.recv: + chain_id, ret = self._dispatch_one(msg) + _v and LOG.debug('_dispatch_calls: %r -> %r', msg, ret) + if msg.reply_to: + msg.reply(ret) + elif isinstance(ret, CallError) and chain_id is None: + LOG.error('No-reply function call failed: %s', ret) + + def run(self): + if self.econtext.config.get('on_start'): + self.econtext.config['on_start'](self.econtext) + + _profile_hook('mitogen.child_main', self._dispatch_calls) + + +class ExternalContext(object): + """ + External context implementation. + + .. attribute:: broker + The :class:`mitogen.core.Broker` instance. + + .. attribute:: context + The :class:`mitogen.core.Context` instance. + + .. attribute:: channel + The :class:`mitogen.core.Channel` over which :data:`CALL_FUNCTION` + requests are received. + + .. attribute:: stdout_log + The :class:`mitogen.core.IoLogger` connected to ``stdout``. + + .. attribute:: importer + The :class:`mitogen.core.Importer` instance. + + .. attribute:: stdout_log + The :class:`IoLogger` connected to ``stdout``. + + .. attribute:: stderr_log + The :class:`IoLogger` connected to ``stderr``. + + .. method:: _dispatch_calls + Implementation for the main thread in every child context. + """ + detached = False + + def __init__(self, config): + self.config = config + + def _on_broker_exit(self): + if not self.config['profiling']: + os.kill(os.getpid(), signal.SIGTERM) + + #: On Python >3.4, the global importer lock has been sharded into a + #: per-module lock, meaning there is no guarantee the import statement in + #: service_stub_main will be truly complete before a second thread + #: attempting the same import will see a partially initialized module. + #: Sigh. Therefore serialize execution of the stub itself. + service_stub_lock = threading.Lock() + + def _service_stub_main(self, msg): + self.service_stub_lock.acquire() + try: + import mitogen.service + pool = mitogen.service.get_or_create_pool(router=self.router) + pool._receiver._on_receive(msg) + finally: + self.service_stub_lock.release() + + def _on_call_service_msg(self, msg): + """ + Stub service handler. Start a thread to import the mitogen.service + implementation from, and deliver the message to the newly constructed + pool. This must be done as CALL_SERVICE for e.g. PushFileService may + race with a CALL_FUNCTION blocking the main thread waiting for a result + from that service. + """ + if not msg.is_dead: + th = threading.Thread(target=self._service_stub_main, args=(msg,)) + th.start() + + def _on_shutdown_msg(self, msg): + _v and LOG.debug('_on_shutdown_msg(%r)', msg) + if not msg.is_dead: + self.broker.shutdown() + + def _on_parent_disconnect(self): + if self.detached: + mitogen.parent_ids = [] + mitogen.parent_id = None + LOG.info('Detachment complete') + else: + _v and LOG.debug('%r: parent stream is gone, dying.', self) + self.broker.shutdown() + + def detach(self): + self.detached = True + stream = self.router.stream_by_id(mitogen.parent_id) + if stream: # not double-detach()'d + os.setsid() + self.parent.send_await(Message(handle=DETACHING)) + LOG.info('Detaching from %r; parent is %s', stream, self.parent) + for x in range(20): + pending = self.broker.defer_sync(lambda: stream.pending_bytes()) + if not pending: + break + time.sleep(0.05) + if pending: + LOG.error('Stream had %d bytes after 2000ms', pending) + self.broker.defer(stream.on_disconnect, self.broker) + + def _setup_master(self): + Router.max_message_size = self.config['max_message_size'] + if self.config['profiling']: + enable_profiling() + self.broker = Broker(activate_compat=False) + self.router = Router(self.broker) + self.router.debug = self.config.get('debug', False) + self.router.undirectional = self.config['unidirectional'] + self.router.add_handler( + fn=self._on_shutdown_msg, + handle=SHUTDOWN, + policy=has_parent_authority, + ) + self.router.add_handler( + fn=self._on_call_service_msg, + handle=CALL_SERVICE, + policy=has_parent_authority, + ) + self.master = Context(self.router, 0, 'master') + parent_id = self.config['parent_ids'][0] + if parent_id == 0: + self.parent = self.master + else: + self.parent = Context(self.router, parent_id, 'parent') + + in_fd = self.config.get('in_fd', 100) + out_fd = self.config.get('out_fd', 1) + self.stream = Stream(self.router, parent_id) + self.stream.name = 'parent' + self.stream.accept(in_fd, out_fd) + self.stream.receive_side.keep_alive = False + + listen(self.stream, 'disconnect', self._on_parent_disconnect) + listen(self.broker, 'exit', self._on_broker_exit) + + os.close(in_fd) + + def _reap_first_stage(self): + try: + os.wait() # Reap first stage. + except OSError: + pass # No first stage exists (e.g. fakessh) + + def _setup_logging(self): + self.log_handler = LogHandler(self.master) + root = logging.getLogger() + root.setLevel(self.config['log_level']) + root.handlers = [self.log_handler] + if self.config['debug']: + enable_debug_logging() + + def _setup_importer(self): + importer = self.config.get('importer') + if importer: + importer._install_handler(self.router) + importer._context = self.parent + else: + core_src_fd = self.config.get('core_src_fd', 101) + if core_src_fd: + fp = os.fdopen(core_src_fd, 'rb', 1) + try: + core_src = fp.read() + # Strip "ExternalContext.main()" call from last line. + core_src = b('\n').join(core_src.splitlines()[:-1]) + finally: + fp.close() + else: + core_src = None + + importer = Importer( + self.router, + self.parent, + core_src, + self.config.get('whitelist', ()), + self.config.get('blacklist', ()), + ) + + self.importer = importer + self.router.importer = importer + sys.meta_path.insert(0, self.importer) + + def _setup_package(self): + global mitogen + mitogen = imp.new_module('mitogen') + mitogen.__package__ = 'mitogen' + mitogen.__path__ = [] + mitogen.__loader__ = self.importer + mitogen.main = lambda *args, **kwargs: (lambda func: None) + mitogen.core = sys.modules['__main__'] + mitogen.core.__file__ = 'x/mitogen/core.py' # For inspect.getsource() + mitogen.core.__loader__ = self.importer + sys.modules['mitogen'] = mitogen + sys.modules['mitogen.core'] = mitogen.core + del sys.modules['__main__'] + + def _setup_globals(self): + mitogen.is_master = False + mitogen.__version__ = self.config['version'] + mitogen.context_id = self.config['context_id'] + mitogen.parent_ids = self.config['parent_ids'][:] + mitogen.parent_id = mitogen.parent_ids[0] + + def _nullify_stdio(self): + """ + Open /dev/null to replace stdin, and stdout/stderr temporarily. In case + of odd startup, assume we may be allocated a standard handle. + """ + fd = os.open('/dev/null', os.O_RDWR) + try: + for stdfd in (0, 1, 2): + if fd != stdfd: + os.dup2(fd, stdfd) + finally: + if fd not in (0, 1, 2): + os.close(fd) + + def _setup_stdio(self): + # #481: when stderr is a TTY due to being started via + # tty_create_child()/hybrid_tty_create_child(), and some privilege + # escalation tool like prehistoric versions of sudo exec this process + # over the top of itself, there is nothing left to keep the slave PTY + # open after we replace our stdio. Therefore if stderr is a TTY, keep + # around a permanent dup() to avoid receiving SIGHUP. + try: + if os.isatty(2): + self.reserve_tty_fd = os.dup(2) + set_cloexec(self.reserve_tty_fd) + except OSError: + pass + # When sys.stdout was opened by the runtime, overwriting it will not + # close FD 1. However when forking from a child that previously used + # fdopen(), overwriting it /will/ close FD 1. So we must swallow the + # close before IoLogger overwrites FD 1, otherwise its new FD 1 will be + # clobbered. Additionally, stdout must be replaced with /dev/null prior + # to stdout.close(), since if block buffering was active in the parent, + # any pre-fork buffered data will be flushed on close(), corrupting the + # connection to the parent. + self._nullify_stdio() + sys.stdout.close() + self._nullify_stdio() + + self.stdout_log = IoLogger(self.broker, 'stdout', 1) + self.stderr_log = IoLogger(self.broker, 'stderr', 2) + # Reopen with line buffering. + sys.stdout = os.fdopen(1, 'w', 1) + + def main(self): + self._setup_master() + try: + try: + self._setup_logging() + self._setup_importer() + self._reap_first_stage() + if self.config.get('setup_package', True): + self._setup_package() + self._setup_globals() + if self.config.get('setup_stdio', True): + self._setup_stdio() + + self.dispatcher = Dispatcher(self) + self.router.register(self.parent, self.stream) + self.router._setup_logging() + self.log_handler.uncork() + + sys.executable = os.environ.pop('ARGV0', sys.executable) + _v and LOG.debug('Connected to context %s; my ID is %r', + self.parent, mitogen.context_id) + _v and LOG.debug('pid:%r ppid:%r uid:%r/%r, gid:%r/%r host:%r', + os.getpid(), os.getppid(), os.geteuid(), + os.getuid(), os.getegid(), os.getgid(), + socket.gethostname()) + _v and LOG.debug('Recovered sys.executable: %r', sys.executable) + + self.broker._py24_25_compat() + self.dispatcher.run() + _v and LOG.debug('ExternalContext.main() normal exit') + except KeyboardInterrupt: + LOG.debug('KeyboardInterrupt received, exiting gracefully.') + except BaseException: + LOG.exception('ExternalContext.main() crashed') + raise + finally: + self.broker.shutdown() + self.broker.join() diff --git a/mitogen-0.2.7/mitogen/core.pyc b/mitogen-0.2.7/mitogen/core.pyc new file mode 100644 index 0000000000000000000000000000000000000000..107eb2f51f1926762361a6fcc02768791f318fe5 GIT binary patch literal 122272 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

Fu@ZXK2AGAwl@En$!o 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 c%-x)rnET literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/debug.py b/mitogen-0.2.7/mitogen/debug.py new file mode 100644 index 000000000..3d13347 --- /dev/null +++ b/mitogen-0.2.7/mitogen/debug.py @@ -0,0 +1,236 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +Basic signal handler for dumping thread stacks. +""" + +import difflib +import logging +import os +import gc +import signal +import sys +import threading +import time +import traceback + +import mitogen.core +import mitogen.parent + + +LOG = logging.getLogger(__name__) +_last = None + + +def enable_evil_interrupts(): + signal.signal(signal.SIGALRM, (lambda a, b: None)) + signal.setitimer(signal.ITIMER_REAL, 0.01, 0.01) + + +def disable_evil_interrupts(): + signal.setitimer(signal.ITIMER_REAL, 0, 0) + + +def _hex(n): + return '%08x' % n + + +def get_subclasses(klass): + """ + Rather than statically import every interesting subclass, forcing it all to + be transferred and potentially disrupting the debugged environment, + enumerate only those loaded in memory. Also returns the original class. + """ + stack = [klass] + seen = set() + while stack: + klass = stack.pop() + seen.add(klass) + stack.extend(klass.__subclasses__()) + return seen + + +def get_routers(): + return dict( + (_hex(id(router)), router) + for klass in get_subclasses(mitogen.core.Router) + for router in gc.get_referrers(klass) + if isinstance(router, mitogen.core.Router) + ) + + +def get_router_info(): + return { + 'routers': dict( + (id_, { + 'id': id_, + 'streams': len(set(router._stream_by_id.values())), + 'contexts': len(set(router._context_by_id.values())), + 'handles': len(router._handle_map), + }) + for id_, router in get_routers().items() + ) + } + + +def get_stream_info(router_id): + router = get_routers().get(router_id) + return { + 'streams': dict( + (_hex(id(stream)), ({ + 'name': stream.name, + 'remote_id': stream.remote_id, + 'sent_module_count': len(getattr(stream, 'sent_modules', [])), + 'routes': sorted(getattr(stream, 'routes', [])), + 'type': type(stream).__module__, + })) + for via_id, stream in router._stream_by_id.items() + ) + } + + +def format_stacks(): + name_by_id = dict( + (t.ident, t.name) + for t in threading.enumerate() + ) + + l = ['', ''] + for threadId, stack in sys._current_frames().items(): + l += ["# PID %d ThreadID: (%s) %s; %r" % ( + os.getpid(), + name_by_id.get(threadId, ''), + threadId, + stack, + )] + #stack = stack.f_back.f_back + + for filename, lineno, name, line in traceback.extract_stack(stack): + l += [ + 'File: "%s", line %d, in %s' % ( + filename, + lineno, + name + ) + ] + if line: + l += [' ' + line.strip()] + l += [''] + + l += ['', ''] + return '\n'.join(l) + + +def get_snapshot(): + global _last + + s = format_stacks() + snap = s + if _last: + snap += '\n' + diff = list(difflib.unified_diff( + a=_last.splitlines(), + b=s.splitlines(), + fromfile='then', + tofile='now' + )) + + if diff: + snap += '\n'.join(diff) + '\n' + else: + snap += '(no change since last time)\n' + _last = s + return snap + + +def _handler(*_): + fp = open('/dev/tty', 'w', 1) + fp.write(get_snapshot()) + fp.close() + + +def install_handler(): + signal.signal(signal.SIGUSR2, _handler) + + +def _logging_main(secs): + while True: + time.sleep(secs) + LOG.info('PERIODIC THREAD DUMP\n\n%s', get_snapshot()) + + +def dump_to_logger(secs=5): + th = threading.Thread( + target=_logging_main, + kwargs={'secs': secs}, + name='mitogen.debug.dump_to_logger', + ) + th.setDaemon(True) + th.start() + + +class ContextDebugger(object): + @classmethod + @mitogen.core.takes_econtext + def _configure_context(cls, econtext): + mitogen.parent.upgrade_router(econtext) + econtext.debugger = cls(econtext.router) + + def __init__(self, router): + self.router = router + self.router.add_handler( + func=self._on_debug_msg, + handle=mitogen.core.DEBUG, + persist=True, + policy=mitogen.core.has_parent_authority, + ) + mitogen.core.listen(router, 'register', self._on_stream_register) + LOG.debug('Context debugging configured.') + + def _on_stream_register(self, context, stream): + LOG.debug('_on_stream_register: sending configure() to %r', stream) + context.call_async(ContextDebugger._configure_context) + + def _on_debug_msg(self, msg): + if msg != mitogen.core._DEAD: + threading.Thread( + target=self._handle_debug_msg, + name='ContextDebuggerHandler', + args=(msg,) + ).start() + + def _handle_debug_msg(self, msg): + try: + method, args, kwargs = msg.unpickle() + msg.reply(getattr(cls, method)(*args, **kwargs)) + except Exception: + e = sys.exc_info()[1] + msg.reply(mitogen.core.CallError(e)) diff --git a/mitogen-0.2.7/mitogen/debug.pyc b/mitogen-0.2.7/mitogen/debug.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc010d04e7604e06bc6b2695ba3778a8228518be GIT binary patch literal 8472 zcmcIp&yU;26@EiX+LgTPpYb}*Pd4cmO6u&oF1ksb#Ho|rI9u48lpYdy%p%6&{Ix5^icHNo_cSA0=X3ExqpC*?+q#KrbaJTRJ-Yv1Zu~v@@@|`o|6C&a9uetp2_i|HsZ}q>4Qibz$4eM1hkDDTPSQkB>lEY(&yaNK5mDtwkn zhy6jEMxN>BDwLi!VS8V%HR8vQA3xri+Mz;3A7HV|L}ZA1_Q)2%I~@|3b@-Z#mrI8x zE(uAr+k_Q&+71!nCx~blYhh8oj+q@k8qDovscPa*g9{-a_nJTiAM|&LXuX^D)p{=p zN9$2+)}}8D874yCI5x z^2`@EzWnUbdrb&nY7mcgWfmaq_KoW|{adDvX=P#)_f>9A5YhJS?OR)Z;BQ^OITLUm zQmzn@O2fTG1?oYZ1aWFqo(~5`f2UL(i52nR270bAkYH0-w-7T^K&?A5>k?M+t2khf zKJ+r*JzRi&WBsH}T*W3J3yC(2j2})JV~|7&8WXMTT7elVtC!z8uma+4MnyUk_$g5V zvj<&uaP2FCK}l1=J|==xk)T6X?}X^kp@R(?3(CKbV^8oT(b$1Ej0q7!;DU>jUImvi zb^kow8N)@f7yU1|sONc}ADXVpJ<|1pFU#X7 zM#y0)*LbZaFlV4xtqlJiWCQ*iO3ALL4vb2rISHEt-St^ZVE}k2qqn{j^f^ z0|Z_kDRboY??YltQ>`xWZ%HhR3r}!C&$^%#2#rRNabSMfUeM-5;7@^T#y&KGEouCG z3$c426%gwQzzPrEy8^Ho2hcLST(642o39{VnFY+-#m5eH9QvWSze4xjqY)O4Wq{_W zZI+Au<%+S9Ytta2SjVY0VcJ$4_=-@<+HGgKV(a+X(5T#BDi@2TH47GAyX>FEJOXIW z_sz>gHG=OukxJFUAiu`>123rwSDX_TQk+lXw(-v+o?XNN(a?W>?vV|lAKA6LWnQ(SKa;X~V+C1%ugTo%%1pn#d2@ZT#Z>un^mEmQ;h}{~F8XMKuGhFlN=|nN7jsYr&(@xfcK=4?A z$`!R5!vYcl=bR`D`T{6%x}0!g0?;_`KO#;^HuU^Ypy4$Pv`GQ9(F|x)Y}w!vR|}j0 zcK{Ew?F~~wTply;iwJxJ@N;PcvB_Sj9Jx@TjcLT{ErKY|uveqqCJ@K|oQ8eHyen7z z%qU<8fk&&<1pQ176Q#|W;}bzU8>VI!f;2B=Mgz6eL=fS~#lZI8!08$e@KCmUq)cc` z&X4OXH%j_fa1GHhC|+44A`4Bx{y`j;=(cd5q2uX*aU7!wHp&=X@V^F602(bW5iTNS zEK*l7{Y~6}o2~jd7zeSOs|!tWTR1sz+l7$O7KyVKxeLrvfiaksky{a5Kun`y&p8qb z52+|1A`W&3e=PDHa9-v6?){GO>CmCeA<#v}=`G+ISk@u~v4ZS4c4+KUr$Q@{pf?W% z9o&OLdJRl-TD?hkD|A?)!zvxt=x~7-tkSqb<0_47)N!ZJju$BZoUhj~?w!C3FBA*k z{~Z6{K}2>y5%IG7Z=VpWGR=M)DKm(r|5oWGCJ)QV+9kvN*MT3Q)&xiW~+}6V8%Wz-{TH3g?+loG1=xh#A;xnz6ZfMV!yePAq;W z%lly=y*_|_UY}BwbMBIOS=2;boD!}$S$Ty$6?x{QTN5vdGtOyCV}MBPkiGVo*a&wR zP~i|$R6+UA*d;9DO-W9r1L|x2Rodc*eOFcx?PU#qFrf-k7JF$vELQ2&;Ix*!Two`% z>o2nRr2OKLonn{6`Hn@055@cJ3-GByI~qB zPseFnc}b{^hYJ6-2B#ta7IqEm2e?$&N*m4=GVXMec#m_^FpWF0k^$xz6?%}w24>a% zhj>IjB^#)e z75Xrbjq*Ws__Up5`bgs^U{CD~=B66pqM(=g7uY1+5Lv3gIxDHfI+_jDJ2_s>5`^Klc{uMvOR z7BM__N|~p`1I=WZm1`r=WCwhCOw3hnY?_c;K;1=TE*m(kGHZX}X2%AUFprewn)?sK zJkmaTYaG52k+&2}HrYYj}sVym2&@!y3&SkUH6zlU?sRISWrsQOvzOHA4H6XwR| zcD#oD!*u5cH1sx(i})wVfrvr^!DCs}on^s*{J@rgo9L=WDxdZtfkJMSAc`nLlU_Nb z%6^3;ZWlB8u|0US%okt=3>IMq4C-YQy+OXMM5paz0F|@RH=qJGB@oU_ig8);OgKXX zm80zssF2$NtnWbxX26>s4x&7iswn4~A#<=O`eC`HSlC)Su@$f?3fH;m9$gbu!<{%9 z<|tvf!u|{N504}Fw3uoD=wJ;*V2O<0z!KjQ*dW1-?P;UPQe;^vXE^y50RenjAS>gs zI`@iA1MF7T82ua{;6Xd=*_~n9W)0d(oul7CU4T%^GS*p7&(eUc5%hI5YwfUCY;L{(=?&{7&k#{J)Pc2a5DrZ@ z%VRTQxe|zy8n7`eSClNJh(46b>+yT6$)S3!lFodKCeA}aoW>>y{)#u8E<9|Bn~F8D z>|7LWz^(kTwYfx{TG*x|B;b;9Ay6F&NssCLT^B6;&_x}LN#=OQWg^Z6^teL5wjAP4dX*}sLi-KM4GAGk6_kC# zpKLB3R?FuX5LSA16wx-^i&W5$X85$65Q4Hl4AVi}-cKx>$kiYj`QL_~9^eK9ysd+_ z8kjgs{rAde)Ipm&4o6JEp)pL7tvt_)1Ph${zrr(6L0_3}CjDPv673WwsB^2kd{jvP zgavHUbj5k`s#q4MMH5l2@O-Wo10NsqIN0&GkwUN;s*sIMxUY0jCL4A>|8s~A_h57L z8)pFG(bF+2_VYiNdq98v*=qeny;5InqAS5J9|SUM2Z4XPoG6Hx51;h$?b3c&gsH4k zD2#wTE>KEZgl`JGW!`N$TV!U~GE(Md27=-L1`BX(0=9h|eca&#Lbj#HZGUuW{YyCD z8zUEQOhcKN``{@|vHbgx1&GM@da7-B*K^e)8~YvGqr3*qal5wW*y`)Z5i{;7QFH6n bdZk{epQ_jEC+nx{UVWurt5@o^rN(~%$I` enqueued. + 2. Target program (`rsync/scp/sftp`) invoked, which internally executes + `ssh` from ``PATH``. + 3. :py:mod:`mitogen.core` bootstrap begins, recovers the stream FD + inherited via the target program, established itself as the fakessh + context. + 4. :py:func:`_fakessh_main` :py:data:`CALL_FUNCTION + ` is read by fakessh context, + + a. sets up :py:class:`IoPump` for stdio, registers + stdin_handle for local context. + b. Enqueues :py:data:`CALL_FUNCTION ` for + :py:func:`_start_slave` invoked in target context, + + i. the program from the `ssh` command line is started + ii. sets up :py:class:`IoPump` for `ssh` command line process's + stdio pipes + iii. returns `(control_handle, stdin_handle)` to + :py:func:`_fakessh_main` + + 5. :py:func:`_fakessh_main` receives control/stdin handles from from + :py:func:`_start_slave`, + + a. registers remote's stdin_handle with local :py:class:`IoPump`. + b. sends `("start", local_stdin_handle)` to remote's control_handle + c. registers local :py:class:`IoPump` with + :py:class:`mitogen.core.Broker`. + d. loops waiting for `local stdout closed && remote stdout closed` + + 6. :py:func:`_start_slave` control channel receives `("start", stdin_handle)`, + + a. registers remote's stdin_handle with local :py:class:`IoPump` + b. registers local :py:class:`IoPump` with + :py:class:`mitogen.core.Broker`. + c. loops waiting for `local stdout closed && remote stdout closed` +""" + +import getopt +import inspect +import os +import shutil +import socket +import subprocess +import sys +import tempfile +import threading + +import mitogen.core +import mitogen.master +import mitogen.parent + +from mitogen.core import LOG, IOLOG + + +SSH_GETOPTS = ( + "1246ab:c:e:fgi:kl:m:no:p:qstvx" + "ACD:E:F:I:KL:MNO:PQ:R:S:TVw:W:XYy" +) + +_mitogen = None + + +class IoPump(mitogen.core.BasicStream): + _output_buf = '' + _closed = False + + def __init__(self, broker, stdin_fd, stdout_fd): + self._broker = broker + self.receive_side = mitogen.core.Side(self, stdout_fd) + self.transmit_side = mitogen.core.Side(self, stdin_fd) + + def write(self, s): + self._output_buf += s + self._broker._start_transmit(self) + + def close(self): + self._closed = True + # If local process hasn't exitted yet, ensure its write buffer is + # drained before lazily triggering disconnect in on_transmit. + if self.transmit_side.fd is not None: + self._broker._start_transmit(self) + + def on_shutdown(self, broker): + self.close() + + def on_transmit(self, broker): + written = self.transmit_side.write(self._output_buf) + IOLOG.debug('%r.on_transmit() -> len %r', self, written) + if written is None: + self.on_disconnect(broker) + else: + self._output_buf = self._output_buf[written:] + + if not self._output_buf: + broker._stop_transmit(self) + if self._closed: + self.on_disconnect(broker) + + def on_receive(self, broker): + s = self.receive_side.read() + IOLOG.debug('%r.on_receive() -> len %r', self, len(s)) + if s: + mitogen.core.fire(self, 'receive', s) + else: + self.on_disconnect(broker) + + def __repr__(self): + return 'IoPump(%r, %r)' % ( + self.receive_side.fd, + self.transmit_side.fd, + ) + + +class Process(object): + """ + Manages the lifetime and pipe connections of the SSH command running in the + slave. + """ + def __init__(self, router, stdin_fd, stdout_fd, proc=None): + self.router = router + self.stdin_fd = stdin_fd + self.stdout_fd = stdout_fd + self.proc = proc + self.control_handle = router.add_handler(self._on_control) + self.stdin_handle = router.add_handler(self._on_stdin) + self.pump = IoPump(router.broker, stdin_fd, stdout_fd) + self.stdin = None + self.control = None + self.wake_event = threading.Event() + + mitogen.core.listen(self.pump, 'disconnect', self._on_pump_disconnect) + mitogen.core.listen(self.pump, 'receive', self._on_pump_receive) + + if proc: + pmon = mitogen.parent.ProcessMonitor.instance() + pmon.add(proc.pid, self._on_proc_exit) + + def __repr__(self): + return 'Process(%r, %r)' % (self.stdin_fd, self.stdout_fd) + + def _on_proc_exit(self, status): + LOG.debug('%r._on_proc_exit(%r)', self, status) + self.control.put(('exit', status)) + + def _on_stdin(self, msg): + if msg.is_dead: + IOLOG.debug('%r._on_stdin() -> %r', self, data) + self.pump.close() + return + + data = msg.unpickle() + IOLOG.debug('%r._on_stdin() -> len %d', self, len(data)) + self.pump.write(data) + + def _on_control(self, msg): + if not msg.is_dead: + command, arg = msg.unpickle(throw=False) + LOG.debug('%r._on_control(%r, %s)', self, command, arg) + + func = getattr(self, '_on_%s' % (command,), None) + if func: + return func(msg, arg) + + LOG.warning('%r: unknown command %r', self, command) + + def _on_start(self, msg, arg): + dest = mitogen.core.Context(self.router, msg.src_id) + self.control = mitogen.core.Sender(dest, arg[0]) + self.stdin = mitogen.core.Sender(dest, arg[1]) + self.router.broker.start_receive(self.pump) + + def _on_exit(self, msg, arg): + LOG.debug('on_exit: proc = %r', self.proc) + if self.proc: + self.proc.terminate() + else: + self.router.broker.shutdown() + + def _on_pump_receive(self, s): + IOLOG.info('%r._on_pump_receive(len %d)', self, len(s)) + self.stdin.put(s) + + def _on_pump_disconnect(self): + LOG.debug('%r._on_pump_disconnect()', self) + mitogen.core.fire(self, 'disconnect') + self.stdin.close() + self.wake_event.set() + + def start_master(self, stdin, control): + self.stdin = stdin + self.control = control + control.put(('start', (self.control_handle, self.stdin_handle))) + self.router.broker.start_receive(self.pump) + + def wait(self): + while not self.wake_event.isSet(): + # Timeout is used so that sleep is interruptible, as blocking + # variants of libc thread operations cannot be interrupted e.g. via + # KeyboardInterrupt. isSet() test and wait() are separate since in + # <2.7 wait() always returns None. + self.wake_event.wait(0.1) + + +@mitogen.core.takes_router +def _start_slave(src_id, cmdline, router): + """ + This runs in the target context, it is invoked by _fakessh_main running in + the fakessh context immediately after startup. It starts the slave process + (the the point where it has a stdin_handle to target but not stdout_chan to + write to), and waits for main to. + """ + LOG.debug('_start_slave(%r, %r)', router, cmdline) + + proc = subprocess.Popen( + cmdline, + # SSH server always uses user's shell. + shell=True, + # SSH server always executes new commands in the user's HOME. + cwd=os.path.expanduser('~'), + + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + + process = Process( + router, + proc.stdin.fileno(), + proc.stdout.fileno(), + proc, + ) + + return process.control_handle, process.stdin_handle + + +# +# SSH client interface. +# + + +def exit(): + _mitogen.broker.shutdown() + + +def die(msg, *args): + if args: + msg %= args + sys.stderr.write('%s\n' % (msg,)) + exit() + + +def parse_args(): + hostname = None + remain = sys.argv[1:] + allopts = [] + restarted = 0 + + while remain and restarted < 2: + opts, args = getopt.getopt(remain, SSH_GETOPTS) + remain = remain[:] # getopt bug! + allopts += opts + if not args: + break + + if not hostname: + hostname = args.pop(0) + remain = remain[remain.index(hostname) + 1:] + + restarted += 1 + + return hostname, allopts, args + + +@mitogen.core.takes_econtext +def _fakessh_main(dest_context_id, econtext): + hostname, opts, args = parse_args() + if not hostname: + die('Missing hostname') + + subsystem = False + for opt, optarg in opts: + if opt == '-s': + subsystem = True + else: + LOG.debug('Warning option %s %s is ignored.', opt, optarg) + + LOG.debug('hostname: %r', hostname) + LOG.debug('opts: %r', opts) + LOG.debug('args: %r', args) + + if subsystem: + die('-s is not yet supported') + + if not args: + die('fakessh: login mode not supported and no command specified') + + dest = mitogen.parent.Context(econtext.router, dest_context_id) + + # Even though SSH receives an argument vector, it still cats the vector + # together before sending to the server, the server just uses /bin/sh -c to + # run the command. We must remain puke-for-puke compatible. + control_handle, stdin_handle = dest.call(_start_slave, + mitogen.context_id, ' '.join(args)) + + LOG.debug('_fakessh_main: received control_handle=%r, stdin_handle=%r', + control_handle, stdin_handle) + + process = Process(econtext.router, 1, 0) + process.start_master( + stdin=mitogen.core.Sender(dest, stdin_handle), + control=mitogen.core.Sender(dest, control_handle), + ) + process.wait() + process.control.put(('exit', None)) + + +def _get_econtext_config(context, sock2): + parent_ids = mitogen.parent_ids[:] + parent_ids.insert(0, mitogen.context_id) + return { + 'context_id': context.context_id, + 'core_src_fd': None, + 'debug': getattr(context.router, 'debug', False), + 'in_fd': sock2.fileno(), + 'log_level': mitogen.parent.get_log_level(), + 'max_message_size': context.router.max_message_size, + 'out_fd': sock2.fileno(), + 'parent_ids': parent_ids, + 'profiling': getattr(context.router, 'profiling', False), + 'unidirectional': getattr(context.router, 'unidirectional', False), + 'setup_stdio': False, + 'version': mitogen.__version__, + } + + +# +# Public API. +# + +@mitogen.core.takes_econtext +@mitogen.core.takes_router +def run(dest, router, args, deadline=None, econtext=None): + """ + Run the command specified by `args` such that ``PATH`` searches for SSH by + the command will cause its attempt to use SSH to execute a remote program + to be redirected to use mitogen to execute that program using the context + `dest` instead. + + :param list args: + Argument vector. + :param mitogen.core.Context dest: + The destination context to execute the SSH command line in. + + :param mitogen.core.Router router: + + :param list[str] args: + Command line arguments for local program, e.g. + ``['rsync', '/tmp', 'remote:/tmp']`` + + :returns: + Exit status of the child process. + """ + if econtext is not None: + mitogen.parent.upgrade_router(econtext) + + context_id = router.allocate_id() + fakessh = mitogen.parent.Context(router, context_id) + fakessh.name = u'fakessh.%d' % (context_id,) + + sock1, sock2 = socket.socketpair() + + stream = mitogen.core.Stream(router, context_id) + stream.name = u'fakessh' + stream.accept(sock1.fileno(), sock1.fileno()) + router.register(fakessh, stream) + + # Held in socket buffer until process is booted. + fakessh.call_async(_fakessh_main, dest.context_id) + + tmp_path = tempfile.mkdtemp(prefix='mitogen_fakessh') + try: + ssh_path = os.path.join(tmp_path, 'ssh') + fp = open(ssh_path, 'w') + try: + fp.write('#!%s\n' % (mitogen.parent.get_sys_executable(),)) + fp.write(inspect.getsource(mitogen.core)) + fp.write('\n') + fp.write('ExternalContext(%r).main()\n' % ( + _get_econtext_config(context, sock2), + )) + finally: + fp.close() + + os.chmod(ssh_path, int('0755', 8)) + env = os.environ.copy() + env.update({ + 'PATH': '%s:%s' % (tmp_path, env.get('PATH', '')), + 'ARGV0': mitogen.parent.get_sys_executable(), + 'SSH_PATH': ssh_path, + }) + + proc = subprocess.Popen(args, env=env) + return proc.wait() + finally: + shutil.rmtree(tmp_path) diff --git a/mitogen-0.2.7/mitogen/fork.py b/mitogen-0.2.7/mitogen/fork.py new file mode 100644 index 000000000..d6685d7 --- /dev/null +++ b/mitogen-0.2.7/mitogen/fork.py @@ -0,0 +1,223 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +import logging +import os +import random +import sys +import threading +import traceback + +import mitogen.core +import mitogen.parent + + +LOG = logging.getLogger('mitogen') + +# Python 2.4/2.5 cannot support fork+threads whatsoever, it doesn't even fix up +# interpreter state. So 2.4/2.5 interpreters start .local() contexts for +# isolation instead. Since we don't have any crazy memory sharing problems to +# avoid, there is no virginal fork parent either. The child is started directly +# from the login/become process. In future this will be default everywhere, +# fork is brainwrong from the stone age. +FORK_SUPPORTED = sys.version_info >= (2, 6) + + +class Error(mitogen.core.StreamError): + pass + + +def fixup_prngs(): + """ + Add 256 bits of /dev/urandom to OpenSSL's PRNG in the child, and re-seed + the random package with the same data. + """ + s = os.urandom(256 // 8) + random.seed(s) + if 'ssl' in sys.modules: + sys.modules['ssl'].RAND_add(s, 75.0) + + +def reset_logging_framework(): + """ + After fork, ensure any logging.Handler locks are recreated, as a variety of + threads in the parent may have been using the logging package at the moment + of fork. + + It is not possible to solve this problem in general; see + https://github.com/dw/mitogen/issues/150 for a full discussion. + """ + logging._lock = threading.RLock() + + # The root logger does not appear in the loggerDict. + for name in [None] + list(logging.Logger.manager.loggerDict): + for handler in logging.getLogger(name).handlers: + handler.createLock() + + root = logging.getLogger() + root.handlers = [ + handler + for handler in root.handlers + if not isinstance(handler, mitogen.core.LogHandler) + ] + + +def on_fork(): + """ + Should be called by any program integrating Mitogen each time the process + is forked, in the context of the new child. + """ + reset_logging_framework() # Must be first! + fixup_prngs() + mitogen.core.Latch._on_fork() + mitogen.core.Side._on_fork() + mitogen.core.ExternalContext.service_stub_lock = threading.Lock() + + mitogen__service = sys.modules.get('mitogen.service') + if mitogen__service: + mitogen__service._pool_lock = threading.Lock() + + +def handle_child_crash(): + """ + Respond to _child_main() crashing by ensuring the relevant exception is + logged to /dev/tty. + """ + tty = open('/dev/tty', 'wb') + tty.write('\n\nFORKED CHILD PID %d CRASHED\n%s\n\n' % ( + os.getpid(), + traceback.format_exc(), + )) + tty.close() + os._exit(1) + + +class Stream(mitogen.parent.Stream): + child_is_immediate_subprocess = True + + #: Reference to the importer, if any, recovered from the parent. + importer = None + + #: User-supplied function for cleaning up child process state. + on_fork = None + + python_version_msg = ( + "The mitogen.fork method is not supported on Python versions " + "prior to 2.6, since those versions made no attempt to repair " + "critical interpreter state following a fork. Please use the " + "local() method instead." + ) + + def construct(self, old_router, max_message_size, on_fork=None, + debug=False, profiling=False, unidirectional=False, + on_start=None): + if not FORK_SUPPORTED: + raise Error(self.python_version_msg) + + # fork method only supports a tiny subset of options. + super(Stream, self).construct(max_message_size=max_message_size, + debug=debug, profiling=profiling, + unidirectional=False) + self.on_fork = on_fork + self.on_start = on_start + + responder = getattr(old_router, 'responder', None) + if isinstance(responder, mitogen.parent.ModuleForwarder): + self.importer = responder.importer + + name_prefix = u'fork' + + def start_child(self): + parentfp, childfp = mitogen.parent.create_socketpair() + self.pid = os.fork() + if self.pid: + childfp.close() + # Decouple the socket from the lifetime of the Python socket object. + fd = os.dup(parentfp.fileno()) + parentfp.close() + return self.pid, fd, None + else: + parentfp.close() + self._wrap_child_main(childfp) + + def _wrap_child_main(self, childfp): + try: + self._child_main(childfp) + except BaseException: + handle_child_crash() + + def _child_main(self, childfp): + on_fork() + if self.on_fork: + self.on_fork() + mitogen.core.set_block(childfp.fileno()) + + # Expected by the ExternalContext.main(). + os.dup2(childfp.fileno(), 1) + os.dup2(childfp.fileno(), 100) + + # Overwritten by ExternalContext.main(); we must replace the + # parent-inherited descriptors that were closed by Side._on_fork() to + # avoid ExternalContext.main() accidentally allocating new files over + # the standard handles. + os.dup2(childfp.fileno(), 0) + + # Avoid corrupting the stream on fork crash by dupping /dev/null over + # stderr. Instead, handle_child_crash() uses /dev/tty to log errors. + devnull = os.open('/dev/null', os.O_WRONLY) + if devnull != 2: + os.dup2(devnull, 2) + os.close(devnull) + + # If we're unlucky, childfp.fileno() may coincidentally be one of our + # desired FDs. In that case closing it breaks ExternalContext.main(). + if childfp.fileno() not in (0, 1, 100): + childfp.close() + + config = self.get_econtext_config() + config['core_src_fd'] = None + config['importer'] = self.importer + config['setup_package'] = False + if self.on_start: + config['on_start'] = self.on_start + + try: + try: + mitogen.core.ExternalContext(config).main() + except Exception: + # TODO: report exception somehow. + os._exit(72) + finally: + # Don't trigger atexit handlers, they were copied from the parent. + os._exit(0) + + def _connect_bootstrap(self): + # None required. + pass diff --git a/mitogen-0.2.7/mitogen/fork.pyc b/mitogen-0.2.7/mitogen/fork.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b1161743523b88ca71bc9b5a39bc33953014bcf GIT binary patch literal 5964 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 OEOah)u6I^Dm;VEy=2.5, but the modern pkgutil.py syntax has + # been kept intentionally 2.3 compatible so we can reuse it. + from mitogen.compat import pkgutil + +import mitogen +import mitogen.core +import mitogen.minify +import mitogen.parent + +from mitogen.core import b +from mitogen.core import IOLOG +from mitogen.core import LOG +from mitogen.core import str_partition +from mitogen.core import str_rpartition +from mitogen.core import to_text + +imap = getattr(itertools, 'imap', map) +izip = getattr(itertools, 'izip', zip) + +try: + any +except NameError: + from mitogen.core import any + +try: + next +except NameError: + from mitogen.core import next + + +RLOG = logging.getLogger('mitogen.ctx') + + +def _stdlib_paths(): + """Return a set of paths from which Python imports the standard library. + """ + attr_candidates = [ + 'prefix', + 'real_prefix', # virtualenv: only set inside a virtual environment. + 'base_prefix', # venv: always set, equal to prefix if outside. + ] + prefixes = (getattr(sys, a) for a in attr_candidates if hasattr(sys, a)) + version = 'python%s.%s' % sys.version_info[0:2] + return set(os.path.abspath(os.path.join(p, 'lib', version)) + for p in prefixes) + + +def is_stdlib_name(modname): + """Return :data:`True` if `modname` appears to come from the standard + library. + """ + if imp.is_builtin(modname) != 0: + return True + + module = sys.modules.get(modname) + if module is None: + return False + + # six installs crap with no __file__ + modpath = os.path.abspath(getattr(module, '__file__', '')) + return is_stdlib_path(modpath) + + +_STDLIB_PATHS = _stdlib_paths() + + +def is_stdlib_path(path): + return any( + os.path.commonprefix((libpath, path)) == libpath + and 'site-packages' not in path + and 'dist-packages' not in path + for libpath in _STDLIB_PATHS + ) + + +def get_child_modules(path): + """Return the suffixes of submodules directly neated beneath of the package + directory at `path`. + + :param str path: + Path to the module's source code on disk, or some PEP-302-recognized + equivalent. Usually this is the module's ``__file__`` attribute, but + is specified explicitly to avoid loading the module. + + :return: + List of submodule name suffixes. + """ + it = pkgutil.iter_modules([os.path.dirname(path)]) + return [to_text(name) for _, name, _ in it] + + +def _get_core_source(): + """ + Master version of parent.get_core_source(). + """ + source = inspect.getsource(mitogen.core) + return mitogen.minify.minimize_source(source) + + +if mitogen.is_master: + # TODO: find a less surprising way of installing this. + mitogen.parent._get_core_source = _get_core_source + + +LOAD_CONST = dis.opname.index('LOAD_CONST') +IMPORT_NAME = dis.opname.index('IMPORT_NAME') + + +def _getarg(nextb, c): + if c >= dis.HAVE_ARGUMENT: + return nextb() | (nextb() << 8) + + +if sys.version_info < (3, 0): + def iter_opcodes(co): + # Yield `(op, oparg)` tuples from the code object `co`. + ordit = imap(ord, co.co_code) + nextb = ordit.next + return ((c, _getarg(nextb, c)) for c in ordit) +elif sys.version_info < (3, 6): + def iter_opcodes(co): + # Yield `(op, oparg)` tuples from the code object `co`. + ordit = iter(co.co_code) + nextb = ordit.__next__ + return ((c, _getarg(nextb, c)) for c in ordit) +else: + def iter_opcodes(co): + # Yield `(op, oparg)` tuples from the code object `co`. + ordit = iter(co.co_code) + nextb = ordit.__next__ + # https://github.com/abarnert/cpython/blob/c095a32f/Python/wordcode.md + return ((c, nextb()) for c in ordit) + + +def scan_code_imports(co): + """ + Given a code object `co`, scan its bytecode yielding any ``IMPORT_NAME`` + and associated prior ``LOAD_CONST`` instructions representing an `Import` + statement or `ImportFrom` statement. + + :return: + Generator producing `(level, modname, namelist)` tuples, where: + + * `level`: -1 for normal import, 0, for absolute import, and >0 for + relative import. + * `modname`: Name of module to import, or from where `namelist` names + are imported. + * `namelist`: for `ImportFrom`, the list of names to be imported from + `modname`. + """ + opit = iter_opcodes(co) + opit, opit2, opit3 = itertools.tee(opit, 3) + + try: + next(opit2) + next(opit3) + next(opit3) + except StopIteration: + return + + if sys.version_info >= (2, 5): + for oparg1, oparg2, (op3, arg3) in izip(opit, opit2, opit3): + if op3 == IMPORT_NAME: + op2, arg2 = oparg2 + op1, arg1 = oparg1 + if op1 == op2 == LOAD_CONST: + yield (co.co_consts[arg1], + co.co_names[arg3], + co.co_consts[arg2] or ()) + else: + # Python 2.4 did not yet have 'level', so stack format differs. + for oparg1, (op2, arg2) in izip(opit, opit2): + if op2 == IMPORT_NAME: + op1, arg1 = oparg1 + if op1 == LOAD_CONST: + yield (-1, co.co_names[arg2], co.co_consts[arg1] or ()) + + +class ThreadWatcher(object): + """ + Manage threads that wait for another thread to shut down, before invoking + `on_join()` for each associated ThreadWatcher. + + In CPython it seems possible to use this method to ensure a non-main thread + is signalled when the main thread has exited, using a third thread as a + proxy. + """ + #: Protects remaining _cls_* members. + _cls_lock = threading.Lock() + + #: PID of the process that last modified the class data. If the PID + #: changes, it means the thread watch dict refers to threads that no longer + #: exist in the current process (since it forked), and so must be reset. + _cls_pid = None + + #: Map watched Thread -> list of ThreadWatcher instances. + _cls_instances_by_target = {} + + #: Map watched Thread -> watcher Thread for each watched thread. + _cls_thread_by_target = {} + + @classmethod + def _reset(cls): + """If we have forked since the watch dictionaries were initialized, all + that has is garbage, so clear it.""" + if os.getpid() != cls._cls_pid: + cls._cls_pid = os.getpid() + cls._cls_instances_by_target.clear() + cls._cls_thread_by_target.clear() + + def __init__(self, target, on_join): + self.target = target + self.on_join = on_join + + @classmethod + def _watch(cls, target): + target.join() + for watcher in cls._cls_instances_by_target[target]: + watcher.on_join() + + def install(self): + self._cls_lock.acquire() + try: + self._reset() + lst = self._cls_instances_by_target.setdefault(self.target, []) + lst.append(self) + if self.target not in self._cls_thread_by_target: + self._cls_thread_by_target[self.target] = threading.Thread( + name='mitogen.master.join_thread_async', + target=self._watch, + args=(self.target,) + ) + self._cls_thread_by_target[self.target].start() + finally: + self._cls_lock.release() + + def remove(self): + self._cls_lock.acquire() + try: + self._reset() + lst = self._cls_instances_by_target.get(self.target, []) + if self in lst: + lst.remove(self) + finally: + self._cls_lock.release() + + @classmethod + def watch(cls, target, on_join): + watcher = cls(target, on_join) + watcher.install() + return watcher + + +class LogForwarder(object): + """ + Install a :data:`mitogen.core.FORWARD_LOG` handler that delivers forwarded + log events into the local logging framework. This is used by the master's + :class:`Router`. + + The forwarded :class:`logging.LogRecord` objects are delivered to loggers + under ``mitogen.ctx.*`` corresponding to their + :attr:`mitogen.core.Context.name`, with the message prefixed with the + logger name used in the child. The records include some extra attributes: + + * ``mitogen_message``: Unicode original message without the logger name + prepended. + * ``mitogen_context``: :class:`mitogen.parent.Context` reference to the + source context. + * ``mitogen_name``: Original logger name. + + :param mitogen.master.Router router: + Router to install the handler on. + """ + def __init__(self, router): + self._router = router + self._cache = {} + router.add_handler( + fn=self._on_forward_log, + handle=mitogen.core.FORWARD_LOG, + ) + + def _on_forward_log(self, msg): + if msg.is_dead: + return + + logger = self._cache.get(msg.src_id) + if logger is None: + context = self._router.context_by_id(msg.src_id) + if context is None: + LOG.error('%s: dropping log from unknown context ID %d', + self, msg.src_id) + return + + name = '%s.%s' % (RLOG.name, context.name) + self._cache[msg.src_id] = logger = logging.getLogger(name) + + name, level_s, s = msg.data.decode('latin1').split('\x00', 2) + + # See logging.Handler.makeRecord() + record = logging.LogRecord( + name=logger.name, + level=int(level_s), + pathname='(unknown file)', + lineno=0, + msg=('%s: %s' % (name, s)), + args=(), + exc_info=None, + ) + record.mitogen_message = s + record.mitogen_context = self._router.context_by_id(msg.src_id) + record.mitogen_name = name + logger.handle(record) + + def __repr__(self): + return 'LogForwarder(%r)' % (self._router,) + + +class ModuleFinder(object): + """ + Given the name of a loaded module, make a best-effort attempt at finding + related modules likely needed by a child context requesting the original + module. + """ + def __init__(self): + #: Import machinery is expensive, keep :py:meth`:get_module_source` + #: results around. + self._found_cache = {} + + #: Avoid repeated dependency scanning, which is expensive. + self._related_cache = {} + + def __repr__(self): + return 'ModuleFinder()' + + def _looks_like_script(self, path): + """ + Return :data:`True` if the (possibly extensionless) file at `path` + resembles a Python script. For now we simply verify the file contains + ASCII text. + """ + fp = open(path, 'rb') + try: + sample = fp.read(512).decode('latin-1') + return not set(sample).difference(string.printable) + finally: + fp.close() + + def _py_filename(self, path): + if not path: + return None + + if path[-4:] in ('.pyc', '.pyo'): + path = path.rstrip('co') + + if path.endswith('.py'): + return path + + if os.path.exists(path) and self._looks_like_script(path): + return path + + def _get_main_module_defective_python_3x(self, fullname): + """ + Recent versions of Python 3.x introduced an incomplete notion of + importer specs, and in doing so created permanent asymmetry in the + :mod:`pkgutil` interface handling for the `__main__` module. Therefore + we must handle `__main__` specially. + """ + if fullname != '__main__': + return None + + mod = sys.modules.get(fullname) + if not mod: + return None + + path = getattr(mod, '__file__', None) + if not (os.path.exists(path) and self._looks_like_script(path)): + return None + + fp = open(path, 'rb') + try: + source = fp.read() + finally: + fp.close() + + return path, source, False + + def _get_module_via_pkgutil(self, fullname): + """ + Attempt to fetch source code via pkgutil. In an ideal world, this would + be the only required implementation of get_module(). + """ + try: + # Pre-'import spec' this returned None, in Python3.6 it raises + # ImportError. + loader = pkgutil.find_loader(fullname) + except ImportError: + e = sys.exc_info()[1] + LOG.debug('%r._get_module_via_pkgutil(%r): %s', + self, fullname, e) + return None + + IOLOG.debug('%r._get_module_via_pkgutil(%r) -> %r', + self, fullname, loader) + if not loader: + return + + try: + path = self._py_filename(loader.get_filename(fullname)) + source = loader.get_source(fullname) + is_pkg = loader.is_package(fullname) + except (AttributeError, ImportError): + # - Per PEP-302, get_source() and is_package() are optional, + # calling them may throw AttributeError. + # - get_filename() may throw ImportError if pkgutil.find_loader() + # picks a "parent" package's loader for some crap that's been + # stuffed in sys.modules, for example in the case of urllib3: + # "loader for urllib3.contrib.pyopenssl cannot handle + # requests.packages.urllib3.contrib.pyopenssl" + e = sys.exc_info()[1] + LOG.debug('%r: loading %r using %r failed: %s', + self, fullname, loader, e) + return + + if path is None or source is None: + return + + if isinstance(source, mitogen.core.UnicodeType): + # get_source() returns "string" according to PEP-302, which was + # reinterpreted for Python 3 to mean a Unicode string. + source = source.encode('utf-8') + + return path, source, is_pkg + + def _get_module_via_sys_modules(self, fullname): + """ + Attempt to fetch source code via sys.modules. This is specifically to + support __main__, but it may catch a few more cases. + """ + module = sys.modules.get(fullname) + LOG.debug('_get_module_via_sys_modules(%r) -> %r', fullname, module) + if not isinstance(module, types.ModuleType): + LOG.debug('sys.modules[%r] absent or not a regular module', + fullname) + return + + path = self._py_filename(getattr(module, '__file__', '')) + if not path: + return + + is_pkg = hasattr(module, '__path__') + try: + source = inspect.getsource(module) + except IOError: + # Work around inspect.getsourcelines() bug for 0-byte __init__.py + # files. + if not is_pkg: + raise + source = '\n' + + if isinstance(source, mitogen.core.UnicodeType): + # get_source() returns "string" according to PEP-302, which was + # reinterpreted for Python 3 to mean a Unicode string. + source = source.encode('utf-8') + + return path, source, is_pkg + + def _get_module_via_parent_enumeration(self, fullname): + """ + Attempt to fetch source code by examining the module's (hopefully less + insane) parent package. Required for older versions of + ansible.compat.six and plumbum.colors. + """ + if fullname not in sys.modules: + # Don't attempt this unless a module really exists in sys.modules, + # else we could return junk. + return + + pkgname, _, modname = str_rpartition(to_text(fullname), u'.') + pkg = sys.modules.get(pkgname) + if pkg is None or not hasattr(pkg, '__file__'): + return + + pkg_path = os.path.dirname(pkg.__file__) + try: + fp, path, ext = imp.find_module(modname, [pkg_path]) + try: + path = self._py_filename(path) + if not path: + fp.close() + return + + source = fp.read() + finally: + if fp: + fp.close() + + if isinstance(source, mitogen.core.UnicodeType): + # get_source() returns "string" according to PEP-302, which was + # reinterpreted for Python 3 to mean a Unicode string. + source = source.encode('utf-8') + return path, source, False + except ImportError: + e = sys.exc_info()[1] + LOG.debug('imp.find_module(%r, %r) -> %s', modname, [pkg_path], e) + + def add_source_override(self, fullname, path, source, is_pkg): + """ + Explicitly install a source cache entry, preventing usual lookup + methods from being used. + + Beware the value of `path` is critical when `is_pkg` is specified, + since it directs where submodules are searched for. + + :param str fullname: + Name of the module to override. + :param str path: + Module's path as it will appear in the cache. + :param bytes source: + Module source code as a bytestring. + :param bool is_pkg: + :data:`True` if the module is a package. + """ + self._found_cache[fullname] = (path, source, is_pkg) + + get_module_methods = [ + _get_main_module_defective_python_3x, + _get_module_via_pkgutil, + _get_module_via_sys_modules, + _get_module_via_parent_enumeration, + ] + + def get_module_source(self, fullname): + """Given the name of a loaded module `fullname`, attempt to find its + source code. + + :returns: + Tuple of `(module path, source text, is package?)`, or :data:`None` + if the source cannot be found. + """ + tup = self._found_cache.get(fullname) + if tup: + return tup + + for method in self.get_module_methods: + tup = method(self, fullname) + if tup: + #LOG.debug('%r returned %r', method, tup) + break + else: + tup = None, None, None + LOG.debug('get_module_source(%r): cannot find source', fullname) + + self._found_cache[fullname] = tup + return tup + + def resolve_relpath(self, fullname, level): + """Given an ImportFrom AST node, guess the prefix that should be tacked + on to an alias name to produce a canonical name. `fullname` is the name + of the module in which the ImportFrom appears. + """ + mod = sys.modules.get(fullname, None) + if hasattr(mod, '__path__'): + fullname += '.__init__' + + if level == 0 or not fullname: + return '' + + bits = fullname.split('.') + if len(bits) <= level: + # This would be an ImportError in real code. + return '' + + return '.'.join(bits[:-level]) + '.' + + def generate_parent_names(self, fullname): + while '.' in fullname: + fullname, _, _ = str_rpartition(to_text(fullname), u'.') + yield fullname + + def find_related_imports(self, fullname): + """ + Return a list of non-stdlib modules that are directly imported by + `fullname`, plus their parents. + + The list is determined by retrieving the source code of + `fullname`, compiling it, and examining all IMPORT_NAME ops. + + :param fullname: Fully qualified name of an _already imported_ module + for which source code can be retrieved + :type fullname: str + """ + related = self._related_cache.get(fullname) + if related is not None: + return related + + modpath, src, _ = self.get_module_source(fullname) + if src is None: + return [] + + maybe_names = list(self.generate_parent_names(fullname)) + + co = compile(src, modpath, 'exec') + for level, modname, namelist in scan_code_imports(co): + if level == -1: + modnames = [modname, '%s.%s' % (fullname, modname)] + else: + modnames = [ + '%s%s' % (self.resolve_relpath(fullname, level), modname) + ] + + maybe_names.extend(modnames) + maybe_names.extend( + '%s.%s' % (mname, name) + for mname in modnames + for name in namelist + ) + + return self._related_cache.setdefault(fullname, sorted( + set( + mitogen.core.to_text(name) + for name in maybe_names + if sys.modules.get(name) is not None + and not is_stdlib_name(name) + and u'six.moves' not in name # TODO: crap + ) + )) + + def find_related(self, fullname): + """ + Return a list of non-stdlib modules that are imported directly or + indirectly by `fullname`, plus their parents. + + This method is like :py:meth:`find_related_imports`, but also + recursively searches any modules which are imported by `fullname`. + + :param fullname: Fully qualified name of an _already imported_ module + for which source code can be retrieved + :type fullname: str + """ + stack = [fullname] + found = set() + + while stack: + name = stack.pop(0) + names = self.find_related_imports(name) + stack.extend(set(names).difference(set(found).union(stack))) + found.update(names) + + found.discard(fullname) + return sorted(found) + + +class ModuleResponder(object): + def __init__(self, router): + self._router = router + self._finder = ModuleFinder() + self._cache = {} # fullname -> pickled + self.blacklist = [] + self.whitelist = [''] + + #: Context -> set([fullname, ..]) + self._forwarded_by_context = {} + + #: Number of GET_MODULE messages received. + self.get_module_count = 0 + #: Total time spent in uncached GET_MODULE. + self.get_module_secs = 0.0 + #: Total time spent minifying modules. + self.minify_secs = 0.0 + #: Number of successful LOAD_MODULE messages sent. + self.good_load_module_count = 0 + #: Total bytes in successful LOAD_MODULE payloads. + self.good_load_module_size = 0 + #: Number of negative LOAD_MODULE messages sent. + self.bad_load_module_count = 0 + + router.add_handler( + fn=self._on_get_module, + handle=mitogen.core.GET_MODULE, + ) + + def __repr__(self): + return 'ModuleResponder(%r)' % (self._router,) + + def add_source_override(self, fullname, path, source, is_pkg): + """ + See :meth:`ModuleFinder.add_source_override. + """ + self._finder.add_source_override(fullname, path, source, is_pkg) + + MAIN_RE = re.compile(b(r'^if\s+__name__\s*==\s*.__main__.\s*:'), re.M) + main_guard_msg = ( + "A child context attempted to import __main__, however the main " + "module present in the master process lacks an execution guard. " + "Update %r to prevent unintended execution, using a guard like:\n" + "\n" + " if __name__ == '__main__':\n" + " # your code here.\n" + ) + + def whitelist_prefix(self, fullname): + if self.whitelist == ['']: + self.whitelist = ['mitogen'] + self.whitelist.append(fullname) + + def blacklist_prefix(self, fullname): + self.blacklist.append(fullname) + + def neutralize_main(self, path, src): + """Given the source for the __main__ module, try to find where it + begins conditional execution based on a "if __name__ == '__main__'" + guard, and remove any code after that point.""" + match = self.MAIN_RE.search(src) + if match: + return src[:match.start()] + + if b('mitogen.main(') in src: + return src + + LOG.error(self.main_guard_msg, path) + raise ImportError('refused') + + def _make_negative_response(self, fullname): + return (fullname, None, None, None, ()) + + minify_safe_re = re.compile(b(r'\s+#\s*!mitogen:\s*minify_safe')) + + def _build_tuple(self, fullname): + if fullname in self._cache: + return self._cache[fullname] + + if mitogen.core.is_blacklisted_import(self, fullname): + raise ImportError('blacklisted') + + path, source, is_pkg = self._finder.get_module_source(fullname) + if path and is_stdlib_path(path): + # Prevent loading of 2.x<->3.x stdlib modules! This costs one + # RTT per hit, so a client-side solution is also required. + LOG.debug('%r: refusing to serve stdlib module %r', + self, fullname) + tup = self._make_negative_response(fullname) + self._cache[fullname] = tup + return tup + + if source is None: + # TODO: make this .warning() or similar again once importer has its + # own logging category. + LOG.debug('_build_tuple(%r): could not locate source', fullname) + tup = self._make_negative_response(fullname) + self._cache[fullname] = tup + return tup + + if self.minify_safe_re.search(source): + # If the module contains a magic marker, it's safe to minify. + t0 = time.time() + source = mitogen.minify.minimize_source(source).encode('utf-8') + self.minify_secs += time.time() - t0 + + if is_pkg: + pkg_present = get_child_modules(path) + LOG.debug('_build_tuple(%r, %r) -> %r', + path, fullname, pkg_present) + else: + pkg_present = None + + if fullname == '__main__': + source = self.neutralize_main(path, source) + compressed = mitogen.core.Blob(zlib.compress(source, 9)) + related = [ + to_text(name) + for name in self._finder.find_related(fullname) + if not mitogen.core.is_blacklisted_import(self, name) + ] + # 0:fullname 1:pkg_present 2:path 3:compressed 4:related + tup = ( + to_text(fullname), + pkg_present, + to_text(path), + compressed, + related + ) + self._cache[fullname] = tup + return tup + + def _send_load_module(self, stream, fullname): + if fullname not in stream.sent_modules: + tup = self._build_tuple(fullname) + msg = mitogen.core.Message.pickled( + tup, + dst_id=stream.remote_id, + handle=mitogen.core.LOAD_MODULE, + ) + LOG.debug('%s: sending module %s (%.2f KiB)', + stream.name, fullname, len(msg.data) / 1024.0) + self._router._async_route(msg) + stream.sent_modules.add(fullname) + if tup[2] is not None: + self.good_load_module_count += 1 + self.good_load_module_size += len(msg.data) + else: + self.bad_load_module_count += 1 + + def _send_module_load_failed(self, stream, fullname): + self.bad_load_module_count += 1 + stream.send( + mitogen.core.Message.pickled( + self._make_negative_response(fullname), + dst_id=stream.remote_id, + handle=mitogen.core.LOAD_MODULE, + ) + ) + + def _send_module_and_related(self, stream, fullname): + if fullname in stream.sent_modules: + return + + try: + tup = self._build_tuple(fullname) + for name in tup[4]: # related + parent, _, _ = str_partition(name, '.') + if parent != fullname and parent not in stream.sent_modules: + # Parent hasn't been sent, so don't load submodule yet. + continue + + self._send_load_module(stream, name) + self._send_load_module(stream, fullname) + except Exception: + LOG.debug('While importing %r', fullname, exc_info=True) + self._send_module_load_failed(stream, fullname) + + def _on_get_module(self, msg): + if msg.is_dead: + return + + stream = self._router.stream_by_id(msg.src_id) + if stream is None: + return + + fullname = msg.data.decode() + LOG.debug('%s requested module %s', stream.name, fullname) + self.get_module_count += 1 + if fullname in stream.sent_modules: + LOG.warning('_on_get_module(): dup request for %r from %r', + fullname, stream) + + t0 = time.time() + try: + self._send_module_and_related(stream, fullname) + finally: + self.get_module_secs += time.time() - t0 + + def _send_forward_module(self, stream, context, fullname): + if stream.remote_id != context.context_id: + stream.send( + mitogen.core.Message( + data=b('%s\x00%s' % (context.context_id, fullname)), + handle=mitogen.core.FORWARD_MODULE, + dst_id=stream.remote_id, + ) + ) + + def _forward_one_module(self, context, fullname): + forwarded = self._forwarded_by_context.get(context) + if forwarded is None: + forwarded = set() + self._forwarded_by_context[context] = forwarded + + if fullname in forwarded: + return + + path = [] + while fullname: + path.append(fullname) + fullname, _, _ = str_rpartition(fullname, u'.') + + stream = self._router.stream_by_id(context.context_id) + if stream is None: + LOG.debug('%r: dropping forward of %s to no longer existent ' + '%r', self, path[0], context) + return + + for fullname in reversed(path): + self._send_module_and_related(stream, fullname) + self._send_forward_module(stream, context, fullname) + + def _forward_modules(self, context, fullnames): + IOLOG.debug('%r._forward_modules(%r, %r)', self, context, fullnames) + for fullname in fullnames: + self._forward_one_module(context, mitogen.core.to_text(fullname)) + + def forward_modules(self, context, fullnames): + self._router.broker.defer(self._forward_modules, context, fullnames) + + +class Broker(mitogen.core.Broker): + """ + .. note:: + + You may construct as many brokers as desired, and use the same broker + for multiple routers, however usually only one broker need exist. + Multiple brokers may be useful when dealing with sets of children with + differing lifetimes. For example, a subscription service where + non-payment results in termination for one customer. + + :param bool install_watcher: + If :data:`True`, an additional thread is started to monitor the + lifetime of the main thread, triggering :meth:`shutdown` + automatically in case the user forgets to call it, or their code + crashed. + + You should not rely on this functionality in your program, it is only + intended as a fail-safe and to simplify the API for new users. In + particular, alternative Python implementations may not be able to + support watching the main thread. + """ + shutdown_timeout = 5.0 + _watcher = None + poller_class = mitogen.parent.PREFERRED_POLLER + + def __init__(self, install_watcher=True): + if install_watcher: + self._watcher = ThreadWatcher.watch( + target=threading.currentThread(), + on_join=self.shutdown, + ) + super(Broker, self).__init__() + + def shutdown(self): + super(Broker, self).shutdown() + if self._watcher: + self._watcher.remove() + + +class Router(mitogen.parent.Router): + """ + Extend :class:`mitogen.core.Router` with functionality useful to masters, + and child contexts who later become masters. Currently when this class is + required, the target context's router is upgraded at runtime. + + .. note:: + + You may construct as many routers as desired, and use the same broker + for multiple routers, however usually only one broker and router need + exist. Multiple routers may be useful when dealing with separate trust + domains, for example, manipulating infrastructure belonging to separate + customers or projects. + + :param mitogen.master.Broker broker: + Broker to use. If not specified, a private :class:`Broker` is created. + + :param int max_message_size: + Override the maximum message size this router is willing to receive or + transmit. Any value set here is automatically inherited by any children + created by the router. + + This has a liberal default of 128 MiB, but may be set much lower. + Beware that setting it below 64KiB may encourage unexpected failures as + parents and children can no longer route large Python modules that may + be required by your application. + """ + + broker_class = Broker + + #: When :data:`True`, cause the broker thread and any subsequent broker and + #: main threads existing in any child to write + #: ``/tmp/mitogen.stats...log`` containing a + #: :mod:`cProfile` dump on graceful exit. Must be set prior to construction + #: of any :class:`Broker`, e.g. via:: + #: + #: mitogen.master.Router.profiling = True + profiling = os.environ.get('MITOGEN_PROFILING') is not None + + def __init__(self, broker=None, max_message_size=None): + if broker is None: + broker = self.broker_class() + if max_message_size: + self.max_message_size = max_message_size + super(Router, self).__init__(broker) + self.upgrade() + + def upgrade(self): + self.id_allocator = IdAllocator(self) + self.responder = ModuleResponder(self) + self.log_forwarder = LogForwarder(self) + self.route_monitor = mitogen.parent.RouteMonitor(router=self) + self.add_handler( # TODO: cutpaste. + fn=self._on_detaching, + handle=mitogen.core.DETACHING, + persist=True, + ) + + def _on_broker_exit(self): + super(Router, self)._on_broker_exit() + dct = self.get_stats() + dct['self'] = self + dct['minify_ms'] = 1000 * dct['minify_secs'] + dct['get_module_ms'] = 1000 * dct['get_module_secs'] + dct['good_load_module_size_kb'] = dct['good_load_module_size'] / 1024.0 + dct['good_load_module_size_avg'] = ( + ( + dct['good_load_module_size'] / + (float(dct['good_load_module_count']) or 1.0) + ) / 1024.0 + ) + + LOG.debug( + '%(self)r: stats: ' + '%(get_module_count)d module requests in ' + '%(get_module_ms)d ms, ' + '%(good_load_module_count)d sent ' + '(%(minify_ms)d ms minify time), ' + '%(bad_load_module_count)d negative responses. ' + 'Sent %(good_load_module_size_kb).01f kb total, ' + '%(good_load_module_size_avg).01f kb avg.' + % dct + ) + + def get_stats(self): + """ + Return performance data for the module responder. + + :returns: + + Dict containing keys: + + * `get_module_count`: Integer count of + :data:`mitogen.core.GET_MODULE` messages received. + * `get_module_secs`: Floating point total seconds spent servicing + :data:`mitogen.core.GET_MODULE` requests. + * `good_load_module_count`: Integer count of successful + :data:`mitogen.core.LOAD_MODULE` messages sent. + * `good_load_module_size`: Integer total bytes sent in + :data:`mitogen.core.LOAD_MODULE` message payloads. + * `bad_load_module_count`: Integer count of negative + :data:`mitogen.core.LOAD_MODULE` messages sent. + * `minify_secs`: CPU seconds spent minifying modules marked + minify-safe. + """ + return { + 'get_module_count': self.responder.get_module_count, + 'get_module_secs': self.responder.get_module_secs, + 'good_load_module_count': self.responder.good_load_module_count, + 'good_load_module_size': self.responder.good_load_module_size, + 'bad_load_module_count': self.responder.bad_load_module_count, + 'minify_secs': self.responder.minify_secs, + } + + def enable_debug(self): + """ + Cause this context and any descendant child contexts to write debug + logs to ``/tmp/mitogen..log``. + """ + mitogen.core.enable_debug_logging() + self.debug = True + + def __enter__(self): + return self + + def __exit__(self, e_type, e_val, tb): + self.broker.shutdown() + self.broker.join() + + def disconnect_stream(self, stream): + self.broker.defer(stream.on_disconnect, self.broker) + + def disconnect_all(self): + for stream in self._stream_by_id.values(): + self.disconnect_stream(stream) + + +class IdAllocator(object): + def __init__(self, router): + self.router = router + self.next_id = 1 + self.lock = threading.Lock() + router.add_handler( + fn=self.on_allocate_id, + handle=mitogen.core.ALLOCATE_ID, + ) + + def __repr__(self): + return 'IdAllocator(%r)' % (self.router,) + + BLOCK_SIZE = 1000 + + def allocate(self): + """ + Arrange for a unique context ID to be allocated and associated with a + route leading to the active context. In masters, the ID is generated + directly, in children it is forwarded to the master via a + :data:`mitogen.core.ALLOCATE_ID` message. + """ + self.lock.acquire() + try: + id_ = self.next_id + self.next_id += 1 + return id_ + finally: + self.lock.release() + + def allocate_block(self): + self.lock.acquire() + try: + id_ = self.next_id + self.next_id += self.BLOCK_SIZE + end_id = id_ + self.BLOCK_SIZE + LOG.debug('%r: allocating [%d..%d)', self, id_, end_id) + return id_, end_id + finally: + self.lock.release() + + def on_allocate_id(self, msg): + if msg.is_dead: + return + + id_, last_id = self.allocate_block() + requestee = self.router.context_by_id(msg.src_id) + LOG.debug('%r: allocating [%r..%r) to %r', + self, id_, last_id, requestee) + msg.reply((id_, last_id)) diff --git a/mitogen-0.2.7/mitogen/master.pyc b/mitogen-0.2.7/mitogen/master.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c2627f76f25596b55761d8e548cc767846c00b6 GIT binary patch literal 40240 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 2: + continue + prev_typ = typ + prev_end_col = end_col + yield typ, tok, (start_row, start_col), (end_row, end_col), line + + +def strip_docstrings(tokens): + """Replace docstring tokens with NL tokens in a `tokenize` stream. + + Any STRING token not part of an expression is deemed a docstring. + Indented docstrings are not yet recognised. + """ + stack = [] + state = 'wait_string' + for t in tokens: + typ = t[0] + if state == 'wait_string': + if typ in (tokenize.NL, tokenize.COMMENT): + yield t + elif typ in (tokenize.DEDENT, tokenize.INDENT, tokenize.STRING): + stack.append(t) + elif typ == tokenize.NEWLINE: + stack.append(t) + start_line, end_line = stack[0][2][0], stack[-1][3][0]+1 + for i in range(start_line, end_line): + yield tokenize.NL, '\n', (i, 0), (i,1), '\n' + for t in stack: + if t[0] in (tokenize.DEDENT, tokenize.INDENT): + yield t[0], t[1], (i+1, t[2][1]), (i+1, t[3][1]), t[4] + del stack[:] + else: + stack.append(t) + for t in stack: yield t + del stack[:] + state = 'wait_newline' + elif state == 'wait_newline': + if typ == tokenize.NEWLINE: + state = 'wait_string' + yield t + + +def reindent(tokens, indent=' '): + """Replace existing indentation in a token steam, with `indent`. + """ + old_levels = [] + old_level = 0 + new_level = 0 + for typ, tok, (start_row, start_col), (end_row, end_col), line in tokens: + if typ == tokenize.INDENT: + old_levels.append(old_level) + old_level = len(tok) + new_level += 1 + tok = indent * new_level + elif typ == tokenize.DEDENT: + old_level = old_levels.pop() + new_level -= 1 + start_col = max(0, start_col - old_level + new_level) + if start_row == end_row: + end_col = start_col + len(tok) + yield typ, tok, (start_row, start_col), (end_row, end_col), line diff --git a/mitogen-0.2.7/mitogen/minify.pyc b/mitogen-0.2.7/mitogen/minify.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ab7bb1e2a98c7d1ae8d287d1340ef28adac214b GIT binary patch literal 3572 zcmb_fO>ZPg5sj$ouKsX0Ue;!8^Rd*51HICW4Rc`82q9o>A&c1_)V9XR%&1+{m2Ove zRaUzyZFi5lBqZ;d|G*K%FMt~ykvJd$5=bj?;ScZwj9z4w?LmkO7z!+by7qVkHQX3iLy-mc@#G&Y0`0%Sa+%&r5`rvQ=;P*g-vO+DQrn& zg~B$4EA%PTaR(6|^twb8-Q3*V^m|N1rb$ExCX4iV|9RwKvVqSE5#1S>bgFdpMvbrp zMlKOO#o>PnA4C(Ti9<`GYo>z8xHPGesh1-YPsW){gO*&ioA=tSdu^8{?Xr6yA>@A} zqJc`%OXZK!BvIPrexO4?Oh>tdp8Ka+n)p9nn6p&-CwY32jnv6Qf0n6SWtWj2`*EaI z+xLB6UnH+pW*y`UZGyRq?r(>YeYy`(Y>%f;K`u!me5WIc&YI#$}$h-j2%N(N0jG-_@Ja$BoJ zG*((=fl))bm3#m^$~9&x2ysmFIoyoNqS>%YmwE4Y=q_pHqDY5I8`CDD3tioG54UVQ ztde9(({9+qhVsdu5Yf(Any8&g94vOmk=Y5=EKV1XUI)3_DbMrh8{3b!KdyQ^$inGj zd$y3%R5=;ieYUY<2RpRi=S}X&KY<Xj!MwrD+ZA^EJ{0gMw+uDWo!03EpRdtTSfhg;%1Zug zBHGQ;Sp|3BX5OMa@K35+IswL1kd(-KT9G5wg7@72=A*}ckSTwvX67NVS~5cUXF-1U zI?!VwlG39zjP%$7@-Pdc7@WB}i;T)=!ASX0?q`;P!n7cc?GAQDgbRy+P$?mOIfcNu zpK4{H3qbXs38edbN6+^Ad(wXT{F6`i`iDJS?*JDssGMc$a%dJagAwT{9S%onY*2YH zA-~h9X(3-G$jmTHukQ6=3r=AjW~w&E!7mWyogsMu?;-|YL?%3sJ?`-)Z*t4=xXWK+ zjCdBvsJQqqK>m!0#&i$Je_#Rf@40XRn$V%Gq6YYdEI|7eFKcDRMcs8O3_pkU3qJo# zHSfy2wXM^sD@ZSZ^oa-CYo>eF4RQX4-s>>s+cg*ecy(Nk+4||F5On(iq=<=IrIC^s&1tJ2gkp!7yiNF zV88!EYwqjR__H80P_aP!Y7UC$QL4e&P^mrfY@BUS6&RRZ{af$hF^TM4?{<_>RilRx11c$K^S5kKIsbCaPz2aq`POLuPb+<&iS)g#c+kKW6>Tb3z)t&6gIp~DWxUvI z9WX9*32!%%snDU1QFIs}Uxxtps^zz8=g_21*L1hy+eHoVHy+|zHsN}6S)+|>2ign4 zv&{J`CT};~DB40A5ytzfxNM6&u9S6Ri;hB<4tfAq{)2mDQu8P`5G(tP35*~g3=)y_ zbEAUfp{3Y~wK@4u(dze5zb!C7K+6YcfazH2!B?uzEVZ(e1oIv)#zGI*bMFb2#^Eql zmnzOhjBdNq)9T7j3M>a-fRhLKY~Ztr&lWyVk#`44ux^38;aKEaDe4IjJKW|juX2aG zyulq_Q(p@63`}V4t@G>?2w#G?KvY(jcw>_sI!*ekV!8i&%a4tH=qG-f>)B=Ps{1 S9cPu-oQ~UizxAN?wSNO$ehN(h literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/os_fork.py b/mitogen-0.2.7/mitogen/os_fork.py new file mode 100644 index 000000000..b27cfd5 --- /dev/null +++ b/mitogen-0.2.7/mitogen/os_fork.py @@ -0,0 +1,183 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +Support for operating in a mixed threading/forking environment. +""" + +import os +import socket +import sys +import weakref + +import mitogen.core + + +# List of weakrefs. On Python 2.4, mitogen.core registers its Broker on this +# list and mitogen.service registers its Pool too. +_brokers = weakref.WeakKeyDictionary() +_pools = weakref.WeakKeyDictionary() + + +def _notice_broker_or_pool(obj): + """ + Used by :mod:`mitogen.core` and :mod:`mitogen.service` to automatically + register every broker and pool on Python 2.4/2.5. + """ + if isinstance(obj, mitogen.core.Broker): + _brokers[obj] = True + else: + _pools[obj] = True + + +def wrap_os__fork(): + corker = Corker( + brokers=list(_brokers), + pools=list(_pools), + ) + try: + corker.cork() + return os__fork() + finally: + corker.uncork() + + +# If Python 2.4/2.5 where threading state is not fixed up, subprocess.Popen() +# may still deadlock due to the broker thread. In this case, pause os.fork() so +# that all active threads are paused during fork. +if sys.version_info < (2, 6): + os__fork = os.fork + os.fork = wrap_os__fork + + +class Corker(object): + """ + Arrange for :class:`mitogen.core.Broker` and optionally + :class:`mitogen.service.Pool` to be temporarily "corked" while fork + operations may occur. + + In a mixed threading/forking environment, it is critical no threads are + active at the moment of fork, as they could hold mutexes whose state is + unrecoverably snapshotted in the locked state in the fork child, causing + deadlocks at random future moments. + + To ensure a target thread has all locks dropped, it is made to write a + large string to a socket with a small buffer that has :data:`os.O_NONBLOCK` + disabled. CPython will drop the GIL and enter the ``write()`` system call, + where it will block until the socket buffer is drained, or the write side + is closed. + + :class:`mitogen.core.Poller` is used to ensure the thread really has + blocked outside any Python locks, by checking if the socket buffer has + started to fill. + + Since this necessarily involves posting a message to every existent thread + and verifying acknowledgement, it will never be a fast operation. + + This does not yet handle the case of corking being initiated from within a + thread that is also a cork target. + + :param brokers: + Sequence of :class:`mitogen.core.Broker` instances to cork. + :param pools: + Sequence of :class:`mitogen.core.Pool` instances to cork. + """ + def __init__(self, brokers=(), pools=()): + self.brokers = brokers + self.pools = pools + + def _do_cork(self, s, wsock): + try: + try: + while True: + # at least EINTR is possible. Do our best to keep handling + # outside the GIL in this case using sendall(). + wsock.sendall(s) + except socket.error: + pass + finally: + wsock.close() + + def _cork_one(self, s, obj): + """ + Construct a socketpair, saving one side of it, and passing the other to + `obj` to be written to by one of its threads. + """ + rsock, wsock = mitogen.parent.create_socketpair(size=4096) + mitogen.core.set_cloexec(rsock.fileno()) + mitogen.core.set_cloexec(wsock.fileno()) + mitogen.core.set_block(wsock) # gevent + self._rsocks.append(rsock) + obj.defer(self._do_cork, s, wsock) + + def _verify_one(self, rsock): + """ + Pause until the socket `rsock` indicates readability, due to + :meth:`_do_cork` triggering a blocking write on another thread. + """ + poller = mitogen.core.Poller() + poller.start_receive(rsock.fileno()) + try: + while True: + for fd in poller.poll(): + return + finally: + poller.close() + + def cork(self): + """ + Arrange for any associated brokers and pools to be paused with no locks + held. This will not return until each thread acknowledges it has ceased + execution. + """ + s = mitogen.core.b('CORK') * ((128 // 4) * 1024) + self._rsocks = [] + + # Pools must be paused first, as existing work may require the + # participation of a broker in order to complete. + for pool in self.pools: + if not pool.closed: + for x in range(pool.size): + self._cork_one(s, pool) + + for broker in self.brokers: + if broker._alive: + self._cork_one(s, broker) + + # Pause until we can detect every thread has entered write(). + for rsock in self._rsocks: + self._verify_one(rsock) + + def uncork(self): + """ + Arrange for paused threads to resume operation. + """ + for rsock in self._rsocks: + rsock.close() diff --git a/mitogen-0.2.7/mitogen/parent.py b/mitogen-0.2.7/mitogen/parent.py new file mode 100644 index 000000000..3d02bc4 --- /dev/null +++ b/mitogen-0.2.7/mitogen/parent.py @@ -0,0 +1,2330 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +This module defines functionality common to master and parent processes. It is +sent to any child context that is due to become a parent, due to recursive +connection. +""" + +import codecs +import errno +import fcntl +import getpass +import inspect +import logging +import os +import signal +import socket +import struct +import subprocess +import sys +import termios +import textwrap +import threading +import time +import zlib + +# Absolute imports for <2.5. +select = __import__('select') + +try: + import thread +except ImportError: + import threading as thread + +import mitogen.core +from mitogen.core import b +from mitogen.core import bytes_partition +from mitogen.core import LOG +from mitogen.core import IOLOG + +try: + next +except NameError: + # Python 2.4/2.5 + from mitogen.core import next + + +itervalues = getattr(dict, 'itervalues', dict.values) + +if mitogen.core.PY3: + xrange = range + closure_attr = '__closure__' + IM_SELF_ATTR = '__self__' +else: + closure_attr = 'func_closure' + IM_SELF_ATTR = 'im_self' + + +try: + SC_OPEN_MAX = os.sysconf('SC_OPEN_MAX') +except ValueError: + SC_OPEN_MAX = 1024 + +OPENPTY_MSG = ( + "Failed to create a PTY: %s. It is likely the maximum number of PTYs has " + "been reached. Consider increasing the 'kern.tty.ptmx_max' sysctl on OS " + "X, the 'kernel.pty.max' sysctl on Linux, or modifying your configuration " + "to avoid PTY use." +) + +SYS_EXECUTABLE_MSG = ( + "The Python sys.executable variable is unset, indicating Python was " + "unable to determine its original program name. Unless explicitly " + "configured otherwise, child contexts will be started using " + "'/usr/bin/python'" +) +_sys_executable_warning_logged = False + + +def _ioctl_cast(n): + """ + Linux ioctl() request parameter is unsigned, whereas on BSD/Darwin it is + signed. Until 2.5 Python exclusively implemented the BSD behaviour, + preventing use of large unsigned int requests like the TTY layer uses + below. So on 2.4, we cast our unsigned to look like signed for Python. + """ + if sys.version_info < (2, 5): + n, = struct.unpack('i', struct.pack('I', n)) + return n + + +# If not :data:`None`, called prior to exec() of any new child process. Used by +# :func:`mitogen.utils.reset_affinity` to allow the child to be freely +# scheduled. +_preexec_hook = None + +# Get PTY number; asm-generic/ioctls.h +LINUX_TIOCGPTN = _ioctl_cast(2147767344) + +# Lock/unlock PTY; asm-generic/ioctls.h +LINUX_TIOCSPTLCK = _ioctl_cast(1074025521) + +IS_LINUX = os.uname()[0] == 'Linux' + +SIGNAL_BY_NUM = dict( + (getattr(signal, name), name) + for name in sorted(vars(signal), reverse=True) + if name.startswith('SIG') and not name.startswith('SIG_') +) + + +def get_log_level(): + return (LOG.level or logging.getLogger().level or logging.INFO) + + +def get_sys_executable(): + """ + Return :data:`sys.executable` if it is set, otherwise return + ``"/usr/bin/python"`` and log a warning. + """ + if sys.executable: + return sys.executable + + global _sys_executable_warning_logged + if not _sys_executable_warning_logged: + LOG.warn(SYS_EXECUTABLE_MSG) + _sys_executable_warning_logged = True + + return '/usr/bin/python' + + +_core_source_lock = threading.Lock() +_core_source_partial = None + + +def _get_core_source(): + """ + In non-masters, simply fetch the cached mitogen.core source code via the + import mechanism. In masters, this function is replaced with a version that + performs minification directly. + """ + return inspect.getsource(mitogen.core) + + +def get_core_source_partial(): + """ + _get_core_source() is expensive, even with @lru_cache in minify.py, threads + can enter it simultaneously causing severe slowdowns. + """ + global _core_source_partial + + if _core_source_partial is None: + _core_source_lock.acquire() + try: + if _core_source_partial is None: + _core_source_partial = PartialZlib( + _get_core_source().encode('utf-8') + ) + finally: + _core_source_lock.release() + + return _core_source_partial + + +def get_default_remote_name(): + """ + Return the default name appearing in argv[0] of remote machines. + """ + s = u'%s@%s:%d' + s %= (getpass.getuser(), socket.gethostname(), os.getpid()) + # In mixed UNIX/Windows environments, the username may contain slashes. + return s.translate({ + ord(u'\\'): ord(u'_'), + ord(u'/'): ord(u'_') + }) + + +def is_immediate_child(msg, stream): + """ + Handler policy that requires messages to arrive only from immediately + connected children. + """ + return msg.src_id == stream.remote_id + + +def flags(names): + """Return the result of ORing a set of (space separated) :py:mod:`termios` + module constants together.""" + return sum(getattr(termios, name, 0) + for name in names.split()) + + +def cfmakeraw(tflags): + """Given a list returned by :py:func:`termios.tcgetattr`, return a list + modified in a manner similar to the `cfmakeraw()` C library function, but + additionally disabling local echo.""" + # BSD: https://github.com/freebsd/freebsd/blob/master/lib/libc/gen/termios.c#L162 + # Linux: https://github.com/lattera/glibc/blob/master/termios/cfmakeraw.c#L20 + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = tflags + iflag &= ~flags('IMAXBEL IXOFF INPCK BRKINT PARMRK ISTRIP INLCR ICRNL IXON IGNPAR') + iflag &= ~flags('IGNBRK BRKINT PARMRK') + oflag &= ~flags('OPOST') + lflag &= ~flags('ECHO ECHOE ECHOK ECHONL ICANON ISIG IEXTEN NOFLSH TOSTOP PENDIN') + cflag &= ~flags('CSIZE PARENB') + cflag |= flags('CS8 CREAD') + return [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] + + +def disable_echo(fd): + old = termios.tcgetattr(fd) + new = cfmakeraw(old) + flags = getattr(termios, 'TCSASOFT', 0) + if not mitogen.core.IS_WSL: + # issue #319: Windows Subsystem for Linux as of July 2018 throws EINVAL + # if TCSAFLUSH is specified. + flags |= termios.TCSAFLUSH + termios.tcsetattr(fd, flags, new) + + +def close_nonstandard_fds(): + for fd in xrange(3, SC_OPEN_MAX): + try: + os.close(fd) + except OSError: + pass + + +def create_socketpair(size=None): + """ + Create a :func:`socket.socketpair` to use for use as a child process's UNIX + stdio channels. As socket pairs are bidirectional, they are economical on + file descriptor usage as the same descriptor can be used for ``stdin`` and + ``stdout``. As they are sockets their buffers are tunable, allowing large + buffers to be configured in order to improve throughput for file transfers + and reduce :class:`mitogen.core.Broker` IO loop iterations. + """ + parentfp, childfp = socket.socketpair() + parentfp.setsockopt(socket.SOL_SOCKET, + socket.SO_SNDBUF, + size or mitogen.core.CHUNK_SIZE) + childfp.setsockopt(socket.SOL_SOCKET, + socket.SO_RCVBUF, + size or mitogen.core.CHUNK_SIZE) + return parentfp, childfp + + +def detach_popen(**kwargs): + """ + Use :class:`subprocess.Popen` to construct a child process, then hack the + Popen so that it forgets the child it created, allowing it to survive a + call to Popen.__del__. + + If the child process is not detached, there is a race between it exitting + and __del__ being called. If it exits before __del__ runs, then __del__'s + call to :func:`os.waitpid` will capture the one and only exit event + delivered to this process, causing later 'legitimate' calls to fail with + ECHILD. + + :param list close_on_error: + Array of integer file descriptors to close on exception. + :returns: + Process ID of the new child. + """ + # This allows Popen() to be used for e.g. graceful post-fork error + # handling, without tying the surrounding code into managing a Popen + # object, which isn't possible for at least :mod:`mitogen.fork`. This + # should be replaced by a swappable helper class in a future version. + real_preexec_fn = kwargs.pop('preexec_fn', None) + def preexec_fn(): + if _preexec_hook: + _preexec_hook() + if real_preexec_fn: + real_preexec_fn() + proc = subprocess.Popen(preexec_fn=preexec_fn, **kwargs) + proc._child_created = False + return proc.pid + + +def create_child(args, merge_stdio=False, stderr_pipe=False, preexec_fn=None): + """ + Create a child process whose stdin/stdout is connected to a socket. + + :param args: + Argument vector for execv() call. + :param bool merge_stdio: + If :data:`True`, arrange for `stderr` to be connected to the `stdout` + socketpair, rather than inherited from the parent process. This may be + necessary to ensure that not TTY is connected to any stdio handle, for + instance when using LXC. + :param bool stderr_pipe: + If :data:`True` and `merge_stdio` is :data:`False`, arrange for + `stderr` to be connected to a separate pipe, to allow any ongoing debug + logs generated by e.g. SSH to be outpu as the session progresses, + without interfering with `stdout`. + :returns: + `(pid, socket_obj, :data:`None` or pipe_fd)` + """ + parentfp, childfp = create_socketpair() + # When running under a monkey patches-enabled gevent, the socket module + # yields file descriptors who already have O_NONBLOCK, which is + # persisted across fork, totally breaking Python. Therefore, drop + # O_NONBLOCK from Python's future stdin fd. + mitogen.core.set_block(childfp.fileno()) + + stderr_r = None + extra = {} + if merge_stdio: + extra = {'stderr': childfp} + elif stderr_pipe: + stderr_r, stderr_w = os.pipe() + mitogen.core.set_cloexec(stderr_r) + mitogen.core.set_cloexec(stderr_w) + extra = {'stderr': stderr_w} + + try: + pid = detach_popen( + args=args, + stdin=childfp, + stdout=childfp, + close_fds=True, + preexec_fn=preexec_fn, + **extra + ) + except Exception: + childfp.close() + parentfp.close() + if stderr_pipe: + os.close(stderr_r) + os.close(stderr_w) + raise + + if stderr_pipe: + os.close(stderr_w) + childfp.close() + # Decouple the socket from the lifetime of the Python socket object. + fd = os.dup(parentfp.fileno()) + parentfp.close() + + LOG.debug('create_child() child %d fd %d, parent %d, cmd: %s', + pid, fd, os.getpid(), Argv(args)) + return pid, fd, stderr_r + + +def _acquire_controlling_tty(): + os.setsid() + if sys.platform in ('linux', 'linux2'): + # On Linux, the controlling tty becomes the first tty opened by a + # process lacking any prior tty. + os.close(os.open(os.ttyname(2), os.O_RDWR)) + if hasattr(termios, 'TIOCSCTTY') and not mitogen.core.IS_WSL: + # #550: prehistoric WSL does not like TIOCSCTTY. + # On BSD an explicit ioctl is required. For some inexplicable reason, + # Python 2.6 on Travis also requires it. + fcntl.ioctl(2, termios.TIOCSCTTY) + + +def _linux_broken_devpts_openpty(): + """ + #462: On broken Linux hosts with mismatched configuration (e.g. old + /etc/fstab template installed), /dev/pts may be mounted without the gid= + mount option, causing new slave devices to be created with the group ID of + the calling process. This upsets glibc, whose openpty() is required by + specification to produce a slave owned by a special group ID (which is + always the 'tty' group). + + Glibc attempts to use "pt_chown" to fix ownership. If that fails, it + chown()s the PTY directly, which fails due to non-root, causing openpty() + to fail with EPERM ("Operation not permitted"). Since we don't need the + magical TTY group to run sudo and su, open the PTY ourselves in this case. + """ + master_fd = None + try: + # Opening /dev/ptmx causes a PTY pair to be allocated, and the + # corresponding slave /dev/pts/* device to be created, owned by UID/GID + # matching this process. + master_fd = os.open('/dev/ptmx', os.O_RDWR) + # Clear the lock bit from the PTY. This a prehistoric feature from a + # time when slave device files were persistent. + fcntl.ioctl(master_fd, LINUX_TIOCSPTLCK, struct.pack('i', 0)) + # Since v4.13 TIOCGPTPEER exists to open the slave in one step, but we + # must support older kernels. Ask for the PTY number. + pty_num_s = fcntl.ioctl(master_fd, LINUX_TIOCGPTN, + struct.pack('i', 0)) + pty_num, = struct.unpack('i', pty_num_s) + pty_name = '/dev/pts/%d' % (pty_num,) + # Now open it with O_NOCTTY to ensure it doesn't change our controlling + # TTY. Otherwise when we close the FD we get killed by the kernel, and + # the child we spawn that should really attach to it will get EPERM + # during _acquire_controlling_tty(). + slave_fd = os.open(pty_name, os.O_RDWR|os.O_NOCTTY) + return master_fd, slave_fd + except OSError: + if master_fd is not None: + os.close(master_fd) + e = sys.exc_info()[1] + raise mitogen.core.StreamError(OPENPTY_MSG, e) + + +def openpty(): + """ + Call :func:`os.openpty`, raising a descriptive error if the call fails. + + :raises mitogen.core.StreamError: + Creating a PTY failed. + :returns: + See :func`os.openpty`. + """ + try: + return os.openpty() + except OSError: + e = sys.exc_info()[1] + if IS_LINUX and e.args[0] == errno.EPERM: + return _linux_broken_devpts_openpty() + raise mitogen.core.StreamError(OPENPTY_MSG, e) + + +def tty_create_child(args): + """ + Return a file descriptor connected to the master end of a pseudo-terminal, + whose slave end is connected to stdin/stdout/stderr of a new child process. + The child is created such that the pseudo-terminal becomes its controlling + TTY, ensuring access to /dev/tty returns a new file descriptor open on the + slave end. + + :param list args: + :py:func:`os.execl` argument list. + + :returns: + `(pid, tty_fd, None)` + """ + master_fd, slave_fd = openpty() + try: + mitogen.core.set_block(slave_fd) + disable_echo(master_fd) + disable_echo(slave_fd) + + pid = detach_popen( + args=args, + stdin=slave_fd, + stdout=slave_fd, + stderr=slave_fd, + preexec_fn=_acquire_controlling_tty, + close_fds=True, + ) + except Exception: + os.close(master_fd) + os.close(slave_fd) + raise + + os.close(slave_fd) + LOG.debug('tty_create_child() child %d fd %d, parent %d, cmd: %s', + pid, master_fd, os.getpid(), Argv(args)) + return pid, master_fd, None + + +def hybrid_tty_create_child(args): + """ + Like :func:`tty_create_child`, except attach stdin/stdout to a socketpair + like :func:`create_child`, but leave stderr and the controlling TTY + attached to a TTY. + + :param list args: + :py:func:`os.execl` argument list. + + :returns: + `(pid, socketpair_fd, tty_fd)` + """ + master_fd, slave_fd = openpty() + + try: + disable_echo(master_fd) + disable_echo(slave_fd) + mitogen.core.set_block(slave_fd) + + parentfp, childfp = create_socketpair() + try: + mitogen.core.set_block(childfp) + pid = detach_popen( + args=args, + stdin=childfp, + stdout=childfp, + stderr=slave_fd, + preexec_fn=_acquire_controlling_tty, + close_fds=True, + ) + except Exception: + parentfp.close() + childfp.close() + raise + except Exception: + os.close(master_fd) + os.close(slave_fd) + raise + + os.close(slave_fd) + childfp.close() + # Decouple the socket from the lifetime of the Python socket object. + stdio_fd = os.dup(parentfp.fileno()) + parentfp.close() + + LOG.debug('hybrid_tty_create_child() pid=%d stdio=%d, tty=%d, cmd: %s', + pid, stdio_fd, master_fd, Argv(args)) + return pid, stdio_fd, master_fd + + +def write_all(fd, s, deadline=None): + """Arrange for all of bytestring `s` to be written to the file descriptor + `fd`. + + :param int fd: + File descriptor to write to. + :param bytes s: + Bytestring to write to file descriptor. + :param float deadline: + If not :data:`None`, absolute UNIX timestamp after which timeout should + occur. + + :raises mitogen.core.TimeoutError: + Bytestring could not be written entirely before deadline was exceeded. + :raises mitogen.parent.EofError: + Stream indicated EOF, suggesting the child process has exitted. + :raises mitogen.core.StreamError: + File descriptor was disconnected before write could complete. + """ + timeout = None + written = 0 + poller = PREFERRED_POLLER() + poller.start_transmit(fd) + + try: + while written < len(s): + if deadline is not None: + timeout = max(0, deadline - time.time()) + if timeout == 0: + raise mitogen.core.TimeoutError('write timed out') + + if mitogen.core.PY3: + window = memoryview(s)[written:] + else: + window = buffer(s, written) + + for fd in poller.poll(timeout): + n, disconnected = mitogen.core.io_op(os.write, fd, window) + if disconnected: + raise EofError('EOF on stream during write') + + written += n + finally: + poller.close() + + +class PartialZlib(object): + """ + Because the mitogen.core source has a line appended to it during bootstrap, + it must be recompressed for each connection. This is not a problem for a + small number of connections, but it amounts to 30 seconds CPU time by the + time 500 targets are in use. + + For that reason, build a compressor containing mitogen.core and flush as + much of it as possible into an initial buffer. Then to append the custom + line, clone the compressor and compress just that line. + + A full compression costs ~6ms on a modern machine, this method costs ~35 + usec. + """ + def __init__(self, s): + self.s = s + if sys.version_info > (2, 5): + self._compressor = zlib.compressobj(9) + self._out = self._compressor.compress(s) + self._out += self._compressor.flush(zlib.Z_SYNC_FLUSH) + else: + self._compressor = None + + def append(self, s): + """ + Append the bytestring `s` to the compressor state and return the + final compressed output. + """ + if self._compressor is None: + return zlib.compress(self.s + s, 9) + else: + compressor = self._compressor.copy() + out = self._out + out += compressor.compress(s) + return out + compressor.flush() + + +class IteratingRead(object): + def __init__(self, fds, deadline=None): + self.deadline = deadline + self.timeout = None + self.poller = PREFERRED_POLLER() + for fd in fds: + self.poller.start_receive(fd) + + self.bits = [] + self.timeout = None + + def close(self): + self.poller.close() + + def __iter__(self): + return self + + def next(self): + while self.poller.readers: + if self.deadline is not None: + self.timeout = max(0, self.deadline - time.time()) + if self.timeout == 0: + break + + for fd in self.poller.poll(self.timeout): + s, disconnected = mitogen.core.io_op(os.read, fd, 4096) + if disconnected or not s: + LOG.debug('iter_read(%r) -> disconnected: %s', + fd, disconnected) + self.poller.stop_receive(fd) + else: + IOLOG.debug('iter_read(%r) -> %r', fd, s) + self.bits.append(s) + return s + + if not self.poller.readers: + raise EofError(u'EOF on stream; last 300 bytes received: %r' % + (b('').join(self.bits)[-300:].decode('latin1'),)) + + raise mitogen.core.TimeoutError('read timed out') + + __next__ = next + + +def iter_read(fds, deadline=None): + """Return a generator that arranges for up to 4096-byte chunks to be read + at a time from the file descriptor `fd` until the generator is destroyed. + + :param int fd: + File descriptor to read from. + :param float deadline: + If not :data:`None`, an absolute UNIX timestamp after which timeout + should occur. + + :raises mitogen.core.TimeoutError: + Attempt to read beyond deadline. + :raises mitogen.parent.EofError: + All streams indicated EOF, suggesting the child process has exitted. + :raises mitogen.core.StreamError: + Attempt to read past end of file. + """ + return IteratingRead(fds=fds, deadline=deadline) + + +def discard_until(fd, s, deadline): + """Read chunks from `fd` until one is encountered that ends with `s`. This + is used to skip output produced by ``/etc/profile``, ``/etc/motd`` and + mandatory SSH banners while waiting for :attr:`Stream.EC0_MARKER` to + appear, indicating the first stage is ready to receive the compressed + :mod:`mitogen.core` source. + + :param int fd: + File descriptor to read from. + :param bytes s: + Marker string to discard until encountered. + :param float deadline: + Absolute UNIX timestamp after which timeout should occur. + + :raises mitogen.core.TimeoutError: + Attempt to read beyond deadline. + :raises mitogen.parent.EofError: + All streams indicated EOF, suggesting the child process has exitted. + :raises mitogen.core.StreamError: + Attempt to read past end of file. + """ + it = iter_read([fd], deadline) + try: + for buf in it: + if IOLOG.level == logging.DEBUG: + for line in buf.splitlines(): + IOLOG.debug('discard_until: discarding %r', line) + if buf.endswith(s): + return + finally: + it.close() # ensure Poller.close() is called. + + +def _upgrade_broker(broker): + """ + Extract the poller state from Broker and replace it with the industrial + strength poller for this OS. Must run on the Broker thread. + """ + # This function is deadly! The act of calling start_receive() generates log + # messages which must be silenced as the upgrade progresses, otherwise the + # poller state will change as it is copied, resulting in write fds that are + # lost. (Due to LogHandler->Router->Stream->Broker->Poller, where Stream + # only calls start_transmit() when transitioning from empty to non-empty + # buffer. If the start_transmit() is lost, writes from the child hang + # permanently). + root = logging.getLogger() + old_level = root.level + root.setLevel(logging.CRITICAL) + + old = broker.poller + new = PREFERRED_POLLER() + for fd, data in old.readers: + new.start_receive(fd, data) + for fd, data in old.writers: + new.start_transmit(fd, data) + + old.close() + broker.poller = new + root.setLevel(old_level) + LOG.debug('replaced %r with %r (new: %d readers, %d writers; ' + 'old: %d readers, %d writers)', old, new, + len(new.readers), len(new.writers), + len(old.readers), len(old.writers)) + + +@mitogen.core.takes_econtext +def upgrade_router(econtext): + if not isinstance(econtext.router, Router): # TODO + econtext.broker.defer(_upgrade_broker, econtext.broker) + econtext.router.__class__ = Router # TODO + econtext.router.upgrade( + importer=econtext.importer, + parent=econtext.parent, + ) + + +def stream_by_method_name(name): + """ + Given the name of a Mitogen connection method, import its implementation + module and return its Stream subclass. + """ + if name == u'local': + name = u'parent' + module = mitogen.core.import_module(u'mitogen.' + name) + return module.Stream + + +@mitogen.core.takes_econtext +def _proxy_connect(name, method_name, kwargs, econtext): + """ + Implements the target portion of Router._proxy_connect() by upgrading the + local context to a parent if it was not already, then calling back into + Router._connect() using the arguments passed to the parent's + Router.connect(). + + :returns: + Dict containing: + * ``id``: :data:`None`, or integer new context ID. + * ``name``: :data:`None`, or string name attribute of new Context. + * ``msg``: :data:`None`, or StreamError exception text. + """ + upgrade_router(econtext) + + try: + context = econtext.router._connect( + klass=stream_by_method_name(method_name), + name=name, + **kwargs + ) + except mitogen.core.StreamError: + return { + u'id': None, + u'name': None, + u'msg': 'error occurred on host %s: %s' % ( + socket.gethostname(), + sys.exc_info()[1], + ), + } + + return { + u'id': context.context_id, + u'name': context.name, + u'msg': None, + } + + +def wstatus_to_str(status): + """ + Parse and format a :func:`os.waitpid` exit status. + """ + if os.WIFEXITED(status): + return 'exited with return code %d' % (os.WEXITSTATUS(status),) + if os.WIFSIGNALED(status): + n = os.WTERMSIG(status) + return 'exited due to signal %d (%s)' % (n, SIGNAL_BY_NUM.get(n)) + if os.WIFSTOPPED(status): + n = os.WSTOPSIG(status) + return 'stopped due to signal %d (%s)' % (n, SIGNAL_BY_NUM.get(n)) + return 'unknown wait status (%d)' % (status,) + + +class EofError(mitogen.core.StreamError): + """ + Raised by :func:`iter_read` and :func:`write_all` when EOF is detected by + the child process. + """ + # inherits from StreamError to maintain compatibility. + pass + + +class Argv(object): + """ + Wrapper to defer argv formatting when debug logging is disabled. + """ + def __init__(self, argv): + self.argv = argv + + must_escape = frozenset('\\$"`!') + must_escape_or_space = must_escape | frozenset(' ') + + def escape(self, x): + if not self.must_escape_or_space.intersection(x): + return x + + s = '"' + for c in x: + if c in self.must_escape: + s += '\\' + s += c + s += '"' + return s + + def __str__(self): + return ' '.join(map(self.escape, self.argv)) + + +class CallSpec(object): + """ + Wrapper to defer call argument formatting when debug logging is disabled. + """ + def __init__(self, func, args, kwargs): + self.func = func + self.args = args + self.kwargs = kwargs + + def _get_name(self): + bits = [self.func.__module__] + if inspect.ismethod(self.func): + im_self = getattr(self.func, IM_SELF_ATTR) + bits.append(getattr(im_self, '__name__', None) or + getattr(type(im_self), '__name__', None)) + bits.append(self.func.__name__) + return u'.'.join(bits) + + def _get_args(self): + return u', '.join(repr(a) for a in self.args) + + def _get_kwargs(self): + s = u'' + if self.kwargs: + s = u', '.join('%s=%r' % (k, v) for k, v in self.kwargs.items()) + if self.args: + s = u', ' + s + return s + + def __repr__(self): + return '%s(%s%s)' % ( + self._get_name(), + self._get_args(), + self._get_kwargs(), + ) + + +class PollPoller(mitogen.core.Poller): + """ + Poller based on the POSIX poll(2) interface. Not available on some versions + of OS X, otherwise it is the preferred poller for small FD counts. + """ + SUPPORTED = hasattr(select, 'poll') + _repr = 'PollPoller()' + + def __init__(self): + super(PollPoller, self).__init__() + self._pollobj = select.poll() + + # TODO: no proof we dont need writemask too + _readmask = ( + getattr(select, 'POLLIN', 0) | + getattr(select, 'POLLHUP', 0) + ) + + def _update(self, fd): + mask = (((fd in self._rfds) and self._readmask) | + ((fd in self._wfds) and select.POLLOUT)) + if mask: + self._pollobj.register(fd, mask) + else: + try: + self._pollobj.unregister(fd) + except KeyError: + pass + + def _poll(self, timeout): + if timeout: + timeout *= 1000 + + events, _ = mitogen.core.io_op(self._pollobj.poll, timeout) + for fd, event in events: + if event & self._readmask: + IOLOG.debug('%r: POLLIN|POLLHUP for %r', self, fd) + data, gen = self._rfds.get(fd, (None, None)) + if gen and gen < self._generation: + yield data + if event & select.POLLOUT: + IOLOG.debug('%r: POLLOUT for %r', self, fd) + data, gen = self._wfds.get(fd, (None, None)) + if gen and gen < self._generation: + yield data + + +class KqueuePoller(mitogen.core.Poller): + """ + Poller based on the FreeBSD/Darwin kqueue(2) interface. + """ + SUPPORTED = hasattr(select, 'kqueue') + _repr = 'KqueuePoller()' + + def __init__(self): + super(KqueuePoller, self).__init__() + self._kqueue = select.kqueue() + self._changelist = [] + + def close(self): + super(KqueuePoller, self).close() + self._kqueue.close() + + def _control(self, fd, filters, flags): + mitogen.core._vv and IOLOG.debug( + '%r._control(%r, %r, %r)', self, fd, filters, flags) + # TODO: at shutdown it is currently possible for KQ_EV_ADD/KQ_EV_DEL + # pairs to be pending after the associated file descriptor has already + # been closed. Fixing this requires maintaining extra state, or perhaps + # making fd closure the poller's responsibility. In the meantime, + # simply apply changes immediately. + # self._changelist.append(select.kevent(fd, filters, flags)) + changelist = [select.kevent(fd, filters, flags)] + events, _ = mitogen.core.io_op(self._kqueue.control, changelist, 0, 0) + assert not events + + def start_receive(self, fd, data=None): + mitogen.core._vv and IOLOG.debug('%r.start_receive(%r, %r)', + self, fd, data) + if fd not in self._rfds: + self._control(fd, select.KQ_FILTER_READ, select.KQ_EV_ADD) + self._rfds[fd] = (data or fd, self._generation) + + def stop_receive(self, fd): + mitogen.core._vv and IOLOG.debug('%r.stop_receive(%r)', self, fd) + if fd in self._rfds: + self._control(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE) + del self._rfds[fd] + + def start_transmit(self, fd, data=None): + mitogen.core._vv and IOLOG.debug('%r.start_transmit(%r, %r)', + self, fd, data) + if fd not in self._wfds: + self._control(fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD) + self._wfds[fd] = (data or fd, self._generation) + + def stop_transmit(self, fd): + mitogen.core._vv and IOLOG.debug('%r.stop_transmit(%r)', self, fd) + if fd in self._wfds: + self._control(fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE) + del self._wfds[fd] + + def _poll(self, timeout): + changelist = self._changelist + self._changelist = [] + events, _ = mitogen.core.io_op(self._kqueue.control, + changelist, 32, timeout) + for event in events: + fd = event.ident + if event.flags & select.KQ_EV_ERROR: + LOG.debug('ignoring stale event for fd %r: errno=%d: %s', + fd, event.data, errno.errorcode.get(event.data)) + elif event.filter == select.KQ_FILTER_READ: + data, gen = self._rfds.get(fd, (None, None)) + # Events can still be read for an already-discarded fd. + if gen and gen < self._generation: + mitogen.core._vv and IOLOG.debug('%r: POLLIN: %r', self, fd) + yield data + elif event.filter == select.KQ_FILTER_WRITE and fd in self._wfds: + data, gen = self._wfds.get(fd, (None, None)) + if gen and gen < self._generation: + mitogen.core._vv and IOLOG.debug('%r: POLLOUT: %r', self, fd) + yield data + + +class EpollPoller(mitogen.core.Poller): + """ + Poller based on the Linux epoll(2) interface. + """ + SUPPORTED = hasattr(select, 'epoll') + _repr = 'EpollPoller()' + + def __init__(self): + super(EpollPoller, self).__init__() + self._epoll = select.epoll(32) + self._registered_fds = set() + + def close(self): + super(EpollPoller, self).close() + self._epoll.close() + + def _control(self, fd): + mitogen.core._vv and IOLOG.debug('%r._control(%r)', self, fd) + mask = (((fd in self._rfds) and select.EPOLLIN) | + ((fd in self._wfds) and select.EPOLLOUT)) + if mask: + if fd in self._registered_fds: + self._epoll.modify(fd, mask) + else: + self._epoll.register(fd, mask) + self._registered_fds.add(fd) + elif fd in self._registered_fds: + self._epoll.unregister(fd) + self._registered_fds.remove(fd) + + def start_receive(self, fd, data=None): + mitogen.core._vv and IOLOG.debug('%r.start_receive(%r, %r)', + self, fd, data) + self._rfds[fd] = (data or fd, self._generation) + self._control(fd) + + def stop_receive(self, fd): + mitogen.core._vv and IOLOG.debug('%r.stop_receive(%r)', self, fd) + self._rfds.pop(fd, None) + self._control(fd) + + def start_transmit(self, fd, data=None): + mitogen.core._vv and IOLOG.debug('%r.start_transmit(%r, %r)', + self, fd, data) + self._wfds[fd] = (data or fd, self._generation) + self._control(fd) + + def stop_transmit(self, fd): + mitogen.core._vv and IOLOG.debug('%r.stop_transmit(%r)', self, fd) + self._wfds.pop(fd, None) + self._control(fd) + + _inmask = (getattr(select, 'EPOLLIN', 0) | + getattr(select, 'EPOLLHUP', 0)) + + def _poll(self, timeout): + the_timeout = -1 + if timeout is not None: + the_timeout = timeout + + events, _ = mitogen.core.io_op(self._epoll.poll, the_timeout, 32) + for fd, event in events: + if event & self._inmask: + data, gen = self._rfds.get(fd, (None, None)) + if gen and gen < self._generation: + # Events can still be read for an already-discarded fd. + mitogen.core._vv and IOLOG.debug('%r: POLLIN: %r', self, fd) + yield data + if event & select.EPOLLOUT: + data, gen = self._wfds.get(fd, (None, None)) + if gen and gen < self._generation: + mitogen.core._vv and IOLOG.debug('%r: POLLOUT: %r', self, fd) + yield data + + +# 2.4 and 2.5 only had select.select() and select.poll(). +for _klass in mitogen.core.Poller, PollPoller, KqueuePoller, EpollPoller: + if _klass.SUPPORTED: + PREFERRED_POLLER = _klass + +# For apps that start threads dynamically, it's possible Latch will also get +# very high-numbered wait fds when there are many connections, and so select() +# becomes useless there too. So swap in our favourite poller. +if PollPoller.SUPPORTED: + mitogen.core.Latch.poller_class = PollPoller +else: + mitogen.core.Latch.poller_class = PREFERRED_POLLER + + +class DiagLogStream(mitogen.core.BasicStream): + """ + For "hybrid TTY/socketpair" mode, after a connection has been setup, a + spare TTY file descriptor will exist that cannot be closed, and to which + SSH or sudo may continue writing log messages. + + The descriptor cannot be closed since the UNIX TTY layer will send a + termination signal to any processes whose controlling TTY is the TTY that + has been closed. + + DiagLogStream takes over this descriptor and creates corresponding log + messages for anything written to it. + """ + + def __init__(self, fd, stream): + self.receive_side = mitogen.core.Side(self, fd) + self.transmit_side = self.receive_side + self.stream = stream + self.buf = '' + + def __repr__(self): + return "mitogen.parent.DiagLogStream(fd=%r, '%s')" % ( + self.receive_side.fd, + self.stream.name, + ) + + def on_receive(self, broker): + """ + This handler is only called after the stream is registered with the IO + loop, the descriptor is manually read/written by _connect_bootstrap() + prior to that. + """ + buf = self.receive_side.read() + if not buf: + return self.on_disconnect(broker) + + self.buf += buf.decode('utf-8', 'replace') + while u'\n' in self.buf: + lines = self.buf.split('\n') + self.buf = lines[-1] + for line in lines[:-1]: + LOG.debug('%s: %s', self.stream.name, line.rstrip()) + + +class Stream(mitogen.core.Stream): + """ + Base for streams capable of starting new slaves. + """ + #: The path to the remote Python interpreter. + python_path = get_sys_executable() + + #: Maximum time to wait for a connection attempt. + connect_timeout = 30.0 + + #: Derived from :py:attr:`connect_timeout`; absolute floating point + #: UNIX timestamp after which the connection attempt should be abandoned. + connect_deadline = None + + #: True to cause context to write verbose /tmp/mitogen..log. + debug = False + + #: True to cause context to write /tmp/mitogen.stats...log. + profiling = False + + #: Set to the child's PID by connect(). + pid = None + + #: Passed via Router wrapper methods, must eventually be passed to + #: ExternalContext.main(). + max_message_size = None + + #: If :attr:`create_child` supplied a diag_fd, references the corresponding + #: :class:`DiagLogStream`, allowing it to be disconnected when this stream + #: is disconnected. Set to :data:`None` if no `diag_fd` was present. + diag_stream = None + + #: Function with the semantics of :func:`create_child` used to create the + #: child process. + create_child = staticmethod(create_child) + + #: Dictionary of extra kwargs passed to :attr:`create_child`. + create_child_args = {} + + #: :data:`True` if the remote has indicated that it intends to detach, and + #: should not be killed on disconnect. + detached = False + + #: If :data:`True`, indicates the child should not be killed during + #: graceful detachment, as it the actual process implementing the child + #: context. In all other cases, the subprocess is SSH, sudo, or a similar + #: tool that should be reminded to quit during disconnection. + child_is_immediate_subprocess = True + + #: Prefix given to default names generated by :meth:`connect`. + name_prefix = u'local' + + _reaped = False + + def __init__(self, *args, **kwargs): + super(Stream, self).__init__(*args, **kwargs) + self.sent_modules = set(['mitogen', 'mitogen.core']) + + def construct(self, max_message_size, remote_name=None, python_path=None, + debug=False, connect_timeout=None, profiling=False, + unidirectional=False, old_router=None, **kwargs): + """Get the named context running on the local machine, creating it if + it does not exist.""" + super(Stream, self).construct(**kwargs) + self.max_message_size = max_message_size + if python_path: + self.python_path = python_path + if connect_timeout: + self.connect_timeout = connect_timeout + if remote_name is None: + remote_name = get_default_remote_name() + if '/' in remote_name or '\\' in remote_name: + raise ValueError('remote_name= cannot contain slashes') + self.remote_name = remote_name + self.debug = debug + self.profiling = profiling + self.unidirectional = unidirectional + self.max_message_size = max_message_size + self.connect_deadline = time.time() + self.connect_timeout + + def on_shutdown(self, broker): + """Request the slave gracefully shut itself down.""" + LOG.debug('%r closing CALL_FUNCTION channel', self) + self._send( + mitogen.core.Message( + src_id=mitogen.context_id, + dst_id=self.remote_id, + handle=mitogen.core.SHUTDOWN, + ) + ) + + def _reap_child(self): + """ + Reap the child process during disconnection. + """ + if self.detached and self.child_is_immediate_subprocess: + LOG.debug('%r: immediate child is detached, won\'t reap it', self) + return + + if self.profiling: + LOG.info('%r: wont kill child because profiling=True', self) + return + + if self._reaped: + # on_disconnect() may be invoked more than once, for example, if + # there is still a pending message to be sent after the first + # on_disconnect() call. + return + + try: + pid, status = os.waitpid(self.pid, os.WNOHANG) + except OSError: + e = sys.exc_info()[1] + if e.args[0] == errno.ECHILD: + LOG.warn('%r: waitpid(%r) produced ECHILD', self, self.pid) + return + raise + + self._reaped = True + if pid: + LOG.debug('%r: PID %d %s', self, pid, wstatus_to_str(status)) + return + + if not self._router.profiling: + # For processes like sudo we cannot actually send sudo a signal, + # because it is setuid, so this is best-effort only. + LOG.debug('%r: child process still alive, sending SIGTERM', self) + try: + os.kill(self.pid, signal.SIGTERM) + except OSError: + e = sys.exc_info()[1] + if e.args[0] != errno.EPERM: + raise + + def on_disconnect(self, broker): + super(Stream, self).on_disconnect(broker) + if self.diag_stream is not None: + self.diag_stream.on_disconnect(broker) + self._reap_child() + + # Minimised, gzipped, base64'd and passed to 'python -c'. It forks, dups + # file descriptor 0 as 100, creates a pipe, then execs a new interpreter + # with a custom argv. + # * Optimized for minimum byte count after minification & compression. + # * 'CONTEXT_NAME' and 'PREAMBLE_COMPRESSED_LEN' are substituted with + # their respective values. + # * CONTEXT_NAME must be prefixed with the name of the Python binary in + # order to allow virtualenvs to detect their install prefix. + # * For Darwin, OS X installs a craptacular argv0-introspecting Python + # version switcher as /usr/bin/python. Override attempts to call it + # with an explicit call to python2.7 + # + # Locals: + # R: read side of interpreter stdin. + # W: write side of interpreter stdin. + # r: read side of core_src FD. + # w: write side of core_src FD. + # C: the decompressed core source. + + # Final os.close(2) to avoid --py-debug build from corrupting stream with + # "[1234 refs]" during exit. + @staticmethod + def _first_stage(): + R,W=os.pipe() + r,w=os.pipe() + if os.fork(): + os.dup2(0,100) + os.dup2(R,0) + os.dup2(r,101) + os.close(R) + os.close(r) + os.close(W) + os.close(w) + if sys.platform == 'darwin' and sys.executable == '/usr/bin/python': + sys.executable += sys.version[:3] + os.environ['ARGV0']=sys.executable + os.execl(sys.executable,sys.executable+'(mitogen:CONTEXT_NAME)') + os.write(1,'MITO000\n'.encode()) + C=_(os.fdopen(0,'rb').read(PREAMBLE_COMPRESSED_LEN),'zip') + fp=os.fdopen(W,'wb',0) + fp.write(C) + fp.close() + fp=os.fdopen(w,'wb',0) + fp.write(C) + fp.close() + os.write(1,'MITO001\n'.encode()) + os.close(2) + + def get_python_argv(self): + """ + Return the initial argument vector elements necessary to invoke Python, + by returning a 1-element list containing :attr:`python_path` if it is a + string, or simply returning it if it is already a list. + + This allows emulation of existing tools where the Python invocation may + be set to e.g. `['/usr/bin/env', 'python']`. + """ + if isinstance(self.python_path, list): + return self.python_path + return [self.python_path] + + def get_boot_command(self): + source = inspect.getsource(self._first_stage) + source = textwrap.dedent('\n'.join(source.strip().split('\n')[2:])) + source = source.replace(' ', '\t') + source = source.replace('CONTEXT_NAME', self.remote_name) + preamble_compressed = self.get_preamble() + source = source.replace('PREAMBLE_COMPRESSED_LEN', + str(len(preamble_compressed))) + compressed = zlib.compress(source.encode(), 9) + encoded = codecs.encode(compressed, 'base64').replace(b('\n'), b('')) + # We can't use bytes.decode() in 3.x since it was restricted to always + # return unicode, so codecs.decode() is used instead. In 3.x + # codecs.decode() requires a bytes object. Since we must be compatible + # with 2.4 (no bytes literal), an extra .encode() either returns the + # same str (2.x) or an equivalent bytes (3.x). + return self.get_python_argv() + [ + '-c', + 'import codecs,os,sys;_=codecs.decode;' + 'exec(_(_("%s".encode(),"base64"),"zip"))' % (encoded.decode(),) + ] + + def get_econtext_config(self): + assert self.max_message_size is not None + parent_ids = mitogen.parent_ids[:] + parent_ids.insert(0, mitogen.context_id) + return { + 'parent_ids': parent_ids, + 'context_id': self.remote_id, + 'debug': self.debug, + 'profiling': self.profiling, + 'unidirectional': self.unidirectional, + 'log_level': get_log_level(), + 'whitelist': self._router.get_module_whitelist(), + 'blacklist': self._router.get_module_blacklist(), + 'max_message_size': self.max_message_size, + 'version': mitogen.__version__, + } + + def get_preamble(self): + suffix = ( + '\nExternalContext(%r).main()\n' %\ + (self.get_econtext_config(),) + ) + partial = get_core_source_partial() + return partial.append(suffix.encode('utf-8')) + + def start_child(self): + args = self.get_boot_command() + try: + return self.create_child(args, **self.create_child_args) + except OSError: + e = sys.exc_info()[1] + msg = 'Child start failed: %s. Command was: %s' % (e, Argv(args)) + raise mitogen.core.StreamError(msg) + + eof_error_hint = None + + def _adorn_eof_error(self, e): + """ + Used by subclasses to provide additional information in the case of a + failed connection. + """ + if self.eof_error_hint: + e.args = ('%s\n\n%s' % (e.args[0], self.eof_error_hint),) + + def _get_name(self): + """ + Called by :meth:`connect` after :attr:`pid` is known. Subclasses can + override it to specify a default stream name, or set + :attr:`name_prefix` to generate a default format. + """ + return u'%s.%s' % (self.name_prefix, self.pid) + + def connect(self): + LOG.debug('%r.connect()', self) + self.pid, fd, diag_fd = self.start_child() + self.name = self._get_name() + self.receive_side = mitogen.core.Side(self, fd) + self.transmit_side = mitogen.core.Side(self, os.dup(fd)) + if diag_fd is not None: + self.diag_stream = DiagLogStream(diag_fd, self) + else: + self.diag_stream = None + + LOG.debug('%r.connect(): pid:%r stdin:%r, stdout:%r, diag:%r', + self, self.pid, self.receive_side.fd, self.transmit_side.fd, + self.diag_stream and self.diag_stream.receive_side.fd) + + try: + self._connect_bootstrap() + except EofError: + self.on_disconnect(self._router.broker) + e = sys.exc_info()[1] + self._adorn_eof_error(e) + raise + except Exception: + self.on_disconnect(self._router.broker) + self._reap_child() + raise + + #: Sentinel value emitted by the first stage to indicate it is ready to + #: receive the compressed bootstrap. For :mod:`mitogen.ssh` this must have + #: length of at least `max(len('password'), len('debug1:'))` + EC0_MARKER = mitogen.core.b('MITO000\n') + EC1_MARKER = mitogen.core.b('MITO001\n') + + def _ec0_received(self): + LOG.debug('%r._ec0_received()', self) + write_all(self.transmit_side.fd, self.get_preamble()) + discard_until(self.receive_side.fd, self.EC1_MARKER, + self.connect_deadline) + if self.diag_stream: + self._router.broker.start_receive(self.diag_stream) + + def _connect_bootstrap(self): + discard_until(self.receive_side.fd, self.EC0_MARKER, + self.connect_deadline) + self._ec0_received() + + +class ChildIdAllocator(object): + def __init__(self, router): + self.router = router + self.lock = threading.Lock() + self.it = iter(xrange(0)) + + def allocate(self): + self.lock.acquire() + try: + for id_ in self.it: + return id_ + + master = mitogen.core.Context(self.router, 0) + start, end = master.send_await( + mitogen.core.Message(dst_id=0, handle=mitogen.core.ALLOCATE_ID) + ) + self.it = iter(xrange(start, end)) + finally: + self.lock.release() + + return self.allocate() + + +class CallChain(object): + """ + Deliver :data:`mitogen.core.CALL_FUNCTION` messages to a target context, + optionally threading related calls so an exception in an earlier call + cancels subsequent calls. + + :param mitogen.core.Context context: + Target context. + :param bool pipelined: + Enable pipelining. + + :meth:`call`, :meth:`call_no_reply` and :meth:`call_async` + normally issue calls and produce responses with no memory of prior + exceptions. If a call made with :meth:`call_no_reply` fails, the exception + is logged to the target context's logging framework. + + **Pipelining** + + When pipelining is enabled, if an exception occurs during a call, + subsequent calls made by the same :class:`CallChain` fail with the same + exception, including those already in-flight on the network, and no further + calls execute until :meth:`reset` is invoked. + + No exception is logged for calls made with :meth:`call_no_reply`, instead + the exception is saved and reported as the result of subsequent + :meth:`call` or :meth:`call_async` calls. + + Sequences of asynchronous calls can be made without wasting network + round-trips to discover if prior calls succeed, and chains originating from + multiple unrelated source contexts may overlap concurrently at a target + context without interference. + + In this example, 4 calls complete in one round-trip:: + + chain = mitogen.parent.CallChain(context, pipelined=True) + chain.call_no_reply(os.mkdir, '/tmp/foo') + + # If previous mkdir() failed, this never runs: + chain.call_no_reply(os.mkdir, '/tmp/foo/bar') + + # If either mkdir() failed, this never runs, and the exception is + # asynchronously delivered to the receiver. + recv = chain.call_async(subprocess.check_output, '/tmp/foo') + + # If anything so far failed, this never runs, and raises the exception. + chain.call(do_something) + + # If this code was executed, the exception would also be raised. + if recv.get().unpickle() == 'baz': + pass + + When pipelining is enabled, :meth:`reset` must be invoked to ensure any + exception is discarded, otherwise unbounded memory usage is possible in + long-running programs. The context manager protocol is supported to ensure + :meth:`reset` is always invoked:: + + with mitogen.parent.CallChain(context, pipelined=True) as chain: + chain.call_no_reply(...) + chain.call_no_reply(...) + chain.call_no_reply(...) + chain.call(...) + + # chain.reset() automatically invoked. + """ + def __init__(self, context, pipelined=False): + self.context = context + if pipelined: + self.chain_id = self.make_chain_id() + else: + self.chain_id = None + + @classmethod + def make_chain_id(cls): + return '%s-%s-%x-%x' % ( + socket.gethostname(), + os.getpid(), + thread.get_ident(), + int(1e6 * time.time()), + ) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.context) + + def __enter__(self): + return self + + def __exit__(self, _1, _2, _3): + self.reset() + + def reset(self): + """ + Instruct the target to forget any related exception. + """ + if not self.chain_id: + return + + saved, self.chain_id = self.chain_id, None + try: + self.call_no_reply(mitogen.core.Dispatcher.forget_chain, saved) + finally: + self.chain_id = saved + + closures_msg = ( + 'Mitogen cannot invoke closures, as doing so would require ' + 'serializing arbitrary program state, and no universal ' + 'method exists to recover a reference to them.' + ) + + lambda_msg = ( + 'Mitogen cannot invoke anonymous functions, as no universal method ' + 'exists to recover a reference to an anonymous function.' + ) + + method_msg = ( + 'Mitogen cannot invoke instance methods, as doing so would require ' + 'serializing arbitrary program state.' + ) + + def make_msg(self, fn, *args, **kwargs): + if getattr(fn, closure_attr, None) is not None: + raise TypeError(self.closures_msg) + if fn.__name__ == '': + raise TypeError(self.lambda_msg) + + if inspect.ismethod(fn): + im_self = getattr(fn, IM_SELF_ATTR) + if not inspect.isclass(im_self): + raise TypeError(self.method_msg) + klass = mitogen.core.to_text(im_self.__name__) + else: + klass = None + + tup = ( + self.chain_id, + mitogen.core.to_text(fn.__module__), + klass, + mitogen.core.to_text(fn.__name__), + args, + mitogen.core.Kwargs(kwargs) + ) + return mitogen.core.Message.pickled(tup, + handle=mitogen.core.CALL_FUNCTION) + + def call_no_reply(self, fn, *args, **kwargs): + """ + Like :meth:`call_async`, but do not wait for a return value, and inform + the target context no reply is expected. If the call fails and + pipelining is disabled, the exception will be logged to the target + context's logging framework. + """ + LOG.debug('%r.call_no_reply(): %r', self, CallSpec(fn, args, kwargs)) + self.context.send(self.make_msg(fn, *args, **kwargs)) + + def call_async(self, fn, *args, **kwargs): + """ + Arrange for `fn(*args, **kwargs)` to be invoked on the context's main + thread. + + :param fn: + A free function in module scope or a class method of a class + directly reachable from module scope: + + .. code-block:: python + + # mymodule.py + + def my_func(): + '''A free function reachable as mymodule.my_func''' + + class MyClass: + @classmethod + def my_classmethod(cls): + '''Reachable as mymodule.MyClass.my_classmethod''' + + def my_instancemethod(self): + '''Unreachable: requires a class instance!''' + + class MyEmbeddedClass: + @classmethod + def my_classmethod(cls): + '''Not directly reachable from module scope!''' + + :param tuple args: + Function arguments, if any. See :ref:`serialization-rules` for + permitted types. + :param dict kwargs: + Function keyword arguments, if any. See :ref:`serialization-rules` + for permitted types. + :returns: + :class:`mitogen.core.Receiver` configured to receive the result of + the invocation: + + .. code-block:: python + + recv = context.call_async(os.check_output, 'ls /tmp/') + try: + # Prints output once it is received. + msg = recv.get() + print(msg.unpickle()) + except mitogen.core.CallError, e: + print('Call failed:', str(e)) + + Asynchronous calls may be dispatched in parallel to multiple + contexts and consumed as they complete using + :class:`mitogen.select.Select`. + """ + LOG.debug('%r.call_async(): %r', self, CallSpec(fn, args, kwargs)) + return self.context.send_async(self.make_msg(fn, *args, **kwargs)) + + def call(self, fn, *args, **kwargs): + """ + Like :meth:`call_async`, but block until the return value is available. + Equivalent to:: + + call_async(fn, *args, **kwargs).get().unpickle() + + :returns: + The function's return value. + :raises mitogen.core.CallError: + An exception was raised in the remote context during execution. + """ + receiver = self.call_async(fn, *args, **kwargs) + return receiver.get().unpickle(throw_dead=False) + + +class Context(mitogen.core.Context): + """ + Extend :class:`mitogen.core.Context` with functionality useful to masters, + and child contexts who later become parents. Currently when this class is + required, the target context's router is upgraded at runtime. + """ + #: A :class:`CallChain` instance constructed by default, with pipelining + #: disabled. :meth:`call`, :meth:`call_async` and :meth:`call_no_reply` use + #: this instance. + call_chain_class = CallChain + + via = None + + def __init__(self, *args, **kwargs): + super(Context, self).__init__(*args, **kwargs) + self.default_call_chain = self.call_chain_class(self) + + def __ne__(self, other): + return not (self == other) + + def __eq__(self, other): + return (isinstance(other, mitogen.core.Context) and + (other.context_id == self.context_id) and + (other.router == self.router)) + + def __hash__(self): + return hash((self.router, self.context_id)) + + def call_async(self, fn, *args, **kwargs): + """ + See :meth:`CallChain.call_async`. + """ + return self.default_call_chain.call_async(fn, *args, **kwargs) + + def call(self, fn, *args, **kwargs): + """ + See :meth:`CallChain.call`. + """ + return self.default_call_chain.call(fn, *args, **kwargs) + + def call_no_reply(self, fn, *args, **kwargs): + """ + See :meth:`CallChain.call_no_reply`. + """ + self.default_call_chain.call_no_reply(fn, *args, **kwargs) + + def shutdown(self, wait=False): + """ + Arrange for the context to receive a ``SHUTDOWN`` message, triggering + graceful shutdown. + + Due to a lack of support for timers, no attempt is made yet to force + terminate a hung context using this method. This will be fixed shortly. + + :param bool wait: + If :data:`True`, block the calling thread until the context has + completely terminated. + + :returns: + If `wait` is :data:`False`, returns a :class:`mitogen.core.Latch` + whose :meth:`get() ` method returns + :data:`None` when shutdown completes. The `timeout` parameter may + be used to implement graceful timeouts. + """ + LOG.debug('%r.shutdown() sending SHUTDOWN', self) + latch = mitogen.core.Latch() + mitogen.core.listen(self, 'disconnect', lambda: latch.put(None)) + self.send( + mitogen.core.Message( + handle=mitogen.core.SHUTDOWN, + ) + ) + + if wait: + latch.get() + else: + return latch + + +class RouteMonitor(object): + """ + Generate and respond to :data:`mitogen.core.ADD_ROUTE` and + :data:`mitogen.core.DEL_ROUTE` messages sent to the local context by + maintaining a table of available routes, and propagating messages towards + parents and siblings as appropriate. + + :class:`RouteMonitor` is responsible for generating routing messages for + directly attached children. It learns of new children via + :meth:`notice_stream` called by :class:`Router`, and subscribes to their + ``disconnect`` event to learn when they disappear. + + In children, constructing this class overwrites the stub + :data:`mitogen.core.DEL_ROUTE` handler installed by + :class:`mitogen.core.ExternalContext`, which is expected behaviour when a + child is beging upgraded in preparation to become a parent of children of + its own. + + By virtue of only being active while responding to messages from a handler, + RouteMonitor lives entirely on the broker thread, so its data requires no + locking. + + :param Router router: + Router to install handlers on. + :param Context parent: + :data:`None` in the master process, or reference to the parent context + we should propagate route updates towards. + """ + def __init__(self, router, parent=None): + self.router = router + self.parent = parent + #: Mapping of Stream instance to integer context IDs reachable via the + #: stream; used to cleanup routes during disconnection. + self._routes_by_stream = {} + self.router.add_handler( + fn=self._on_add_route, + handle=mitogen.core.ADD_ROUTE, + persist=True, + policy=is_immediate_child, + overwrite=True, + ) + self.router.add_handler( + fn=self._on_del_route, + handle=mitogen.core.DEL_ROUTE, + persist=True, + policy=is_immediate_child, + overwrite=True, + ) + + def __repr__(self): + return 'RouteMonitor()' + + def _send_one(self, stream, handle, target_id, name): + """ + Compose and send an update message on a stream. + + :param mitogen.core.Stream stream: + Stream to send it on. + :param int handle: + :data:`mitogen.core.ADD_ROUTE` or :data:`mitogen.core.DEL_ROUTE` + :param int target_id: + ID of the connecting or disconnecting context. + :param str name: + Context name or :data:`None`. + """ + if not stream: + # We may not have a stream during shutdown. + return + + data = str(target_id) + if name: + data = '%s:%s' % (target_id, name) + stream.send( + mitogen.core.Message( + handle=handle, + data=data.encode('utf-8'), + dst_id=stream.remote_id, + ) + ) + + def _propagate_up(self, handle, target_id, name=None): + """ + In a non-master context, propagate an update towards the master. + + :param int handle: + :data:`mitogen.core.ADD_ROUTE` or :data:`mitogen.core.DEL_ROUTE` + :param int target_id: + ID of the connecting or disconnecting context. + :param str name: + For :data:`mitogen.core.ADD_ROUTE`, the name of the new context + assigned by its parent. This is used by parents to assign the + :attr:`mitogen.core.Context.name` attribute. + """ + if self.parent: + stream = self.router.stream_by_id(self.parent.context_id) + self._send_one(stream, handle, target_id, name) + + def _propagate_down(self, handle, target_id): + """ + For DEL_ROUTE, we additionally want to broadcast the message to any + stream that has ever communicated with the disconnecting ID, so + core.py's :meth:`mitogen.core.Router._on_del_route` can turn the + message into a disconnect event. + + :param int handle: + :data:`mitogen.core.ADD_ROUTE` or :data:`mitogen.core.DEL_ROUTE` + :param int target_id: + ID of the connecting or disconnecting context. + """ + for stream in self.router.get_streams(): + if target_id in stream.egress_ids and ( + (self.parent is None) or + (self.parent.context_id != stream.remote_id) + ): + self._send_one(stream, mitogen.core.DEL_ROUTE, target_id, None) + + def notice_stream(self, stream): + """ + When this parent is responsible for a new directly connected child + stream, we're also responsible for broadcasting DEL_ROUTE upstream + if/when that child disconnects. + """ + self._routes_by_stream[stream] = set([stream.remote_id]) + self._propagate_up(mitogen.core.ADD_ROUTE, stream.remote_id, + stream.name) + mitogen.core.listen( + obj=stream, + name='disconnect', + func=lambda: self._on_stream_disconnect(stream), + ) + + def get_routes(self, stream): + """ + Return the set of context IDs reachable on a stream. + + :param mitogen.core.Stream stream: + :returns: set([int]) + """ + return self._routes_by_stream.get(stream) or set() + + def _on_stream_disconnect(self, stream): + """ + Respond to disconnection of a local stream by propagating DEL_ROUTE for + any contexts we know were attached to it. + """ + # During a stream crash it is possible for disconnect signal to fire + # twice, in which case ignore the second instance. + routes = self._routes_by_stream.pop(stream, None) + if routes is None: + return + + LOG.debug('%r: %r is gone; propagating DEL_ROUTE for %r', + self, stream, routes) + for target_id in routes: + self.router.del_route(target_id) + self._propagate_up(mitogen.core.DEL_ROUTE, target_id) + self._propagate_down(mitogen.core.DEL_ROUTE, target_id) + + context = self.router.context_by_id(target_id, create=False) + if context: + mitogen.core.fire(context, 'disconnect') + + def _on_add_route(self, msg): + """ + Respond to :data:`mitogen.core.ADD_ROUTE` by validating the source of + the message, updating the local table, and propagating the message + upwards. + """ + if msg.is_dead: + return + + target_id_s, _, target_name = bytes_partition(msg.data, b(':')) + target_name = target_name.decode() + target_id = int(target_id_s) + self.router.context_by_id(target_id).name = target_name + stream = self.router.stream_by_id(msg.auth_id) + current = self.router.stream_by_id(target_id) + if current and current.remote_id != mitogen.parent_id: + LOG.error('Cannot add duplicate route to %r via %r, ' + 'already have existing route via %r', + target_id, stream, current) + return + + LOG.debug('Adding route to %d via %r', target_id, stream) + self._routes_by_stream[stream].add(target_id) + self.router.add_route(target_id, stream) + self._propagate_up(mitogen.core.ADD_ROUTE, target_id, target_name) + + def _on_del_route(self, msg): + """ + Respond to :data:`mitogen.core.DEL_ROUTE` by validating the source of + the message, updating the local table, propagating the message + upwards, and downwards towards any stream that every had a message + forwarded from it towards the disconnecting context. + """ + if msg.is_dead: + return + + target_id = int(msg.data) + registered_stream = self.router.stream_by_id(target_id) + if registered_stream is None: + return + + stream = self.router.stream_by_id(msg.auth_id) + if registered_stream != stream: + LOG.error('%r: received DEL_ROUTE for %d from %r, expected %r', + self, target_id, stream, registered_stream) + return + + context = self.router.context_by_id(target_id, create=False) + if context: + LOG.debug('%r: firing local disconnect for %r', self, context) + mitogen.core.fire(context, 'disconnect') + + LOG.debug('%r: deleting route to %d via %r', self, target_id, stream) + routes = self._routes_by_stream.get(stream) + if routes: + routes.discard(target_id) + + self.router.del_route(target_id) + if stream.remote_id != mitogen.parent_id: + self._propagate_up(mitogen.core.DEL_ROUTE, target_id) + self._propagate_down(mitogen.core.DEL_ROUTE, target_id) + + +class Router(mitogen.core.Router): + context_class = Context + debug = False + profiling = False + + id_allocator = None + responder = None + log_forwarder = None + route_monitor = None + + def upgrade(self, importer, parent): + LOG.debug('%r.upgrade()', self) + self.id_allocator = ChildIdAllocator(router=self) + self.responder = ModuleForwarder( + router=self, + parent_context=parent, + importer=importer, + ) + self.route_monitor = RouteMonitor(self, parent) + self.add_handler( + fn=self._on_detaching, + handle=mitogen.core.DETACHING, + persist=True, + ) + + def _on_detaching(self, msg): + if msg.is_dead: + return + stream = self.stream_by_id(msg.src_id) + if stream.remote_id != msg.src_id or stream.detached: + LOG.warning('bad DETACHING received on %r: %r', stream, msg) + return + LOG.debug('%r: marking as detached', stream) + stream.detached = True + msg.reply(None) + + def get_streams(self): + """ + Return a snapshot of all streams in existence at time of call. + """ + self._write_lock.acquire() + try: + return itervalues(self._stream_by_id) + finally: + self._write_lock.release() + + def add_route(self, target_id, stream): + """ + Arrange for messages whose `dst_id` is `target_id` to be forwarded on + the directly connected stream for `via_id`. This method is called + automatically in response to :data:`mitogen.core.ADD_ROUTE` messages, + but remains public while the design has not yet settled, and situations + may arise where routing is not fully automatic. + """ + LOG.debug('%r.add_route(%r, %r)', self, target_id, stream) + assert isinstance(target_id, int) + assert isinstance(stream, Stream) + + self._write_lock.acquire() + try: + self._stream_by_id[target_id] = stream + finally: + self._write_lock.release() + + def del_route(self, target_id): + LOG.debug('%r.del_route(%r)', self, target_id) + # DEL_ROUTE may be sent by a parent if it knows this context sent + # messages to a peer that has now disconnected, to let us raise + # 'disconnect' event on the appropriate Context instance. In that case, + # we won't a matching _stream_by_id entry for the disappearing route, + # so don't raise an error for a missing key here. + self._write_lock.acquire() + try: + self._stream_by_id.pop(target_id, None) + finally: + self._write_lock.release() + + def get_module_blacklist(self): + if mitogen.context_id == 0: + return self.responder.blacklist + return self.importer.master_blacklist + + def get_module_whitelist(self): + if mitogen.context_id == 0: + return self.responder.whitelist + return self.importer.master_whitelist + + def allocate_id(self): + return self.id_allocator.allocate() + + connection_timeout_msg = u"Connection timed out." + + def _connect(self, klass, name=None, **kwargs): + context_id = self.allocate_id() + context = self.context_class(self, context_id) + kwargs['old_router'] = self + kwargs['max_message_size'] = self.max_message_size + stream = klass(self, context_id, **kwargs) + if name is not None: + stream.name = name + try: + stream.connect() + except mitogen.core.TimeoutError: + raise mitogen.core.StreamError(self.connection_timeout_msg) + context.name = stream.name + self.route_monitor.notice_stream(stream) + self.register(context, stream) + return context + + def connect(self, method_name, name=None, **kwargs): + klass = stream_by_method_name(method_name) + kwargs.setdefault(u'debug', self.debug) + kwargs.setdefault(u'profiling', self.profiling) + kwargs.setdefault(u'unidirectional', self.unidirectional) + + via = kwargs.pop(u'via', None) + if via is not None: + return self.proxy_connect(via, method_name, name=name, + **mitogen.core.Kwargs(kwargs)) + return self._connect(klass, name=name, + **mitogen.core.Kwargs(kwargs)) + + def proxy_connect(self, via_context, method_name, name=None, **kwargs): + resp = via_context.call(_proxy_connect, + name=name, + method_name=method_name, + kwargs=mitogen.core.Kwargs(kwargs), + ) + if resp['msg'] is not None: + raise mitogen.core.StreamError(resp['msg']) + + name = u'%s.%s' % (via_context.name, resp['name']) + context = self.context_class(self, resp['id'], name=name) + context.via = via_context + self._write_lock.acquire() + try: + self._context_by_id[context.context_id] = context + finally: + self._write_lock.release() + return context + + def doas(self, **kwargs): + return self.connect(u'doas', **kwargs) + + def docker(self, **kwargs): + return self.connect(u'docker', **kwargs) + + def kubectl(self, **kwargs): + return self.connect(u'kubectl', **kwargs) + + def fork(self, **kwargs): + return self.connect(u'fork', **kwargs) + + def jail(self, **kwargs): + return self.connect(u'jail', **kwargs) + + def local(self, **kwargs): + return self.connect(u'local', **kwargs) + + def lxc(self, **kwargs): + return self.connect(u'lxc', **kwargs) + + def lxd(self, **kwargs): + return self.connect(u'lxd', **kwargs) + + def setns(self, **kwargs): + return self.connect(u'setns', **kwargs) + + def su(self, **kwargs): + return self.connect(u'su', **kwargs) + + def sudo(self, **kwargs): + return self.connect(u'sudo', **kwargs) + + def ssh(self, **kwargs): + return self.connect(u'ssh', **kwargs) + + +class ProcessMonitor(object): + """ + Install a :data:`signal.SIGCHLD` handler that generates callbacks when a + specific child process has exitted. This class is obsolete, do not use. + """ + def __init__(self): + # pid -> callback() + self.callback_by_pid = {} + signal.signal(signal.SIGCHLD, self._on_sigchld) + + def _on_sigchld(self, _signum, _frame): + for pid, callback in self.callback_by_pid.items(): + pid, status = os.waitpid(pid, os.WNOHANG) + if pid: + callback(status) + del self.callback_by_pid[pid] + + def add(self, pid, callback): + """ + Add a callback function to be notified of the exit status of a process. + + :param int pid: + Process ID to be notified of. + + :param callback: + Function invoked as `callback(status)`, where `status` is the raw + exit status of the child process. + """ + self.callback_by_pid[pid] = callback + + _instance = None + + @classmethod + def instance(cls): + if cls._instance is None: + cls._instance = cls() + return cls._instance + + +class ModuleForwarder(object): + """ + Respond to GET_MODULE requests in a slave by forwarding the request to our + parent context, or satisfying the request from our local Importer cache. + """ + def __init__(self, router, parent_context, importer): + self.router = router + self.parent_context = parent_context + self.importer = importer + router.add_handler( + fn=self._on_forward_module, + handle=mitogen.core.FORWARD_MODULE, + persist=True, + policy=mitogen.core.has_parent_authority, + ) + router.add_handler( + fn=self._on_get_module, + handle=mitogen.core.GET_MODULE, + persist=True, + policy=is_immediate_child, + ) + + def __repr__(self): + return 'ModuleForwarder(%r)' % (self.router,) + + def _on_forward_module(self, msg): + if msg.is_dead: + return + + context_id_s, _, fullname = bytes_partition(msg.data, b('\x00')) + fullname = mitogen.core.to_text(fullname) + context_id = int(context_id_s) + stream = self.router.stream_by_id(context_id) + if stream.remote_id == mitogen.parent_id: + LOG.error('%r: dropping FORWARD_MODULE(%d, %r): no route to child', + self, context_id, fullname) + return + + if fullname in stream.sent_modules: + return + + LOG.debug('%r._on_forward_module() sending %r to %r via %r', + self, fullname, context_id, stream.remote_id) + self._send_module_and_related(stream, fullname) + if stream.remote_id != context_id: + stream._send( + mitogen.core.Message( + data=msg.data, + handle=mitogen.core.FORWARD_MODULE, + dst_id=stream.remote_id, + ) + ) + + def _on_get_module(self, msg): + LOG.debug('%r._on_get_module(%r)', self, msg) + if msg.is_dead: + return + + fullname = msg.data.decode('utf-8') + callback = lambda: self._on_cache_callback(msg, fullname) + self.importer._request_module(fullname, callback) + + def _on_cache_callback(self, msg, fullname): + LOG.debug('%r._on_get_module(): sending %r', self, fullname) + stream = self.router.stream_by_id(msg.src_id) + self._send_module_and_related(stream, fullname) + + def _send_module_and_related(self, stream, fullname): + tup = self.importer._cache[fullname] + for related in tup[4]: + rtup = self.importer._cache.get(related) + if rtup: + self._send_one_module(stream, rtup) + else: + LOG.debug('%r._send_module_and_related(%r): absent: %r', + self, fullname, related) + + self._send_one_module(stream, tup) + + def _send_one_module(self, stream, tup): + if tup[0] not in stream.sent_modules: + stream.sent_modules.add(tup[0]) + self.router._async_route( + mitogen.core.Message.pickled( + tup, + dst_id=stream.remote_id, + handle=mitogen.core.LOAD_MODULE, + ) + ) diff --git a/mitogen-0.2.7/mitogen/parent.pyc b/mitogen-0.2.7/mitogen/parent.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e65f72d9d9f6dd5a439575751a9661a069875a0 GIT binary patch literal 83619 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/profiler.py b/mitogen-0.2.7/mitogen/profiler.py new file mode 100644 index 000000000..74bbdb2 --- /dev/null +++ b/mitogen-0.2.7/mitogen/profiler.py @@ -0,0 +1,166 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +"""mitogen.profiler + Record and report cProfile statistics from a run. Creates one aggregated + output file, one aggregate containing only workers, and one for the + top-level process. + +Usage: + mitogen.profiler record [args ..] + mitogen.profiler report [sort_mode] + mitogen.profiler stat [args ..] + +Mode: + record: Record a trace. + report: Report on a previously recorded trace. + stat: Record and report in a single step. + +Where: + dest_path: Filesystem prefix to write .pstats files to. + sort_mode: Sorting mode; defaults to "cumulative". See: + https://docs.python.org/2/library/profile.html#pstats.Stats.sort_stats + +Example: + mitogen.profiler record /tmp/mypatch ansible-playbook foo.yml + mitogen.profiler dump /tmp/mypatch-worker.pstats +""" + +from __future__ import print_function +import os +import pstats +import cProfile +import shutil +import subprocess +import sys +import tempfile +import time + +import mitogen.core + + +def try_merge(stats, path): + try: + stats.add(path) + return True + except Exception as e: + print('Failed. Race? Will retry. %s' % (e,)) + return False + + +def merge_stats(outpath, inpaths): + first, rest = inpaths[0], inpaths[1:] + for x in range(5): + try: + stats = pstats.Stats(first) + except EOFError: + time.sleep(0.2) + continue + + print("Writing %r..." % (outpath,)) + for path in rest: + #print("Merging %r into %r.." % (os.path.basename(path), outpath)) + for x in range(5): + if try_merge(stats, path): + break + time.sleep(0.2) + + stats.dump_stats(outpath) + + +def generate_stats(outpath, tmpdir): + print('Generating stats..') + all_paths = [] + paths_by_ident = {} + + for name in os.listdir(tmpdir): + if name.endswith('-dump.pstats'): + ident, _, pid = name.partition('-') + path = os.path.join(tmpdir, name) + all_paths.append(path) + paths_by_ident.setdefault(ident, []).append(path) + + merge_stats('%s-all.pstat' % (outpath,), all_paths) + for ident, paths in paths_by_ident.items(): + merge_stats('%s-%s.pstat' % (outpath, ident), paths) + + +def do_record(tmpdir, path, *args): + env = os.environ.copy() + fmt = '%(identity)s-%(pid)s.%(now)s-dump.%(ext)s' + env['MITOGEN_PROFILING'] = '1' + env['MITOGEN_PROFILE_FMT'] = os.path.join(tmpdir, fmt) + rc = subprocess.call(args, env=env) + generate_stats(path, tmpdir) + return rc + + +def do_report(tmpdir, path, sort='cumulative'): + stats = pstats.Stats(path).sort_stats(sort) + stats.print_stats(100) + + +def do_stat(tmpdir, sort, *args): + valid_sorts = pstats.Stats.sort_arg_dict_default + if sort not in valid_sorts: + sys.stderr.write('Invalid sort %r, must be one of %s\n' % + (sort, ', '.join(sorted(valid_sorts)))) + sys.exit(1) + + outfile = os.path.join(tmpdir, 'combined') + do_record(tmpdir, outfile, *args) + aggs = ('app.main', 'mitogen.broker', 'mitogen.child_main', + 'mitogen.service.pool', 'Strategy', 'WorkerProcess', + 'all') + for agg in aggs: + path = '%s-%s.pstat' % (outfile, agg) + if os.path.exists(path): + print() + print() + print('------ Aggregation %r ------' % (agg,)) + print() + do_report(tmpdir, path, sort) + print() + + +def main(): + if len(sys.argv) < 2 or sys.argv[1] not in ('record', 'report', 'stat'): + sys.stderr.write(__doc__) + sys.exit(1) + + func = globals()['do_' + sys.argv[1]] + tmpdir = tempfile.mkdtemp(prefix='mitogen.profiler') + try: + sys.exit(func(tmpdir, *sys.argv[2:]) or 0) + finally: + shutil.rmtree(tmpdir) + +if __name__ == '__main__': + main() diff --git a/mitogen-0.2.7/mitogen/select.py b/mitogen-0.2.7/mitogen/select.py new file mode 100644 index 000000000..51aebc2 --- /dev/null +++ b/mitogen-0.2.7/mitogen/select.py @@ -0,0 +1,333 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +import mitogen.core + + +class Error(mitogen.core.Error): + pass + + +class Event(object): + """ + Represents one selected event. + """ + #: The first Receiver or Latch the event traversed. + source = None + + #: The :class:`mitogen.core.Message` delivered to a receiver, or the object + #: posted to a latch. + data = None + + +class Select(object): + """ + Support scatter/gather asynchronous calls and waiting on multiple + :class:`receivers `, + :class:`channels `, + :class:`latches `, and + :class:`sub-selects