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

gebI>%Hw$Xg(%Hf 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 E0JH7_)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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 e-&kH)URi#;e0jO33!r6+AyidX=F7~t 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 Tf=-JzD$R;pZLBsI8yEf`%vaw} literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/runner.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/runner.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa45d241caa4c99ab2a2189a287bbaa4f883b45f GIT binary patch literal 30523 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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-hV*d9*nnAlp@B+?GxZPSYV`* zXUL51gg$jRQgSCu#;DPzc1ZXZu$G2=_eRY&MlQ&sox?!hD$e+?AOD@rG$HUz~d{ z^)+-_I8{7=mbtOmu#iZi0jTF(9hY_%|NQGHs0FfeFBcEC4E$eLY?W^{& zl82tuZEJHA*#W-UenY_}s~gz8jYl^;Pb7io<#rfrg*HD&DP}WNORe8Gj#JM%G@I~!_ zn_rqxvFjNs|2+GEwU_eMyHuxpOOA@dgGojU{{d$l$?Ms zLeg$D!dgnY#chTtq{Ua`&IHR4Cui*ZAb((OIbq?J5^r z4iKMIL`1d6l(T}Hr3{t5S-lUsMv9A~6|A2gvPRc_Xd`R1XB#An%Al!@7)Ci75~eZ% zlP@y5*)R*u!Ib3g0o08uF^ z){gi2U&~Q2tHmd;{}QA4|B(|iMkFT$j3lRTewm!oIXM*)vkrJ>cZ)3Fywqsg)t^ES z{|r0CZDjJ)7sjzbN(!}v=FY!`v&F>BlodkxU*&Yv?Ixf6>dDg)j8mNqJv|=%aw1Vv zJh`xdkdcwbs-akCo&C@9;mm5;lh=OX!`nQskCG@1cH6alvB)<-Q>p{f;l)(?D(D*> zq{ky92`GEV7(;+xm1f{qBpGJe+N)=EVA<`4+R}Vr4Q#|r`0dC(&zvql&jkP6-fIB8 z77?67Wj0RMpje7c7mDu-Suc#>_;T^4-H>t-P8#^JK?87=bGmSV!@q+bTPE~5{G8cbm>`Ix^bgSH zA7V#}G1HEJj6|(f78fj_2&PWaF>1^ZFH>#mOUHzU(Y0SX;^Ng$@dQ%SKfo)Uejruo zN8v<@@5ehxndQr&`C$p1)TV^;CFFX`*nH={2-+})?+0- zPa%h?V7BuH_zP5v5_)MQ={i}!=eXfl>5a=wSM{4Lbs86maygo}%9l&B*8I@w{84%j zc@wEp(vQw7N3R~%mt!e46X)d%J*n>))`jpGa*22k>KRF1pc!*$V9pY1S;8cy=$epd z?tqju=m?Rq+Q$w*!ZBiQ6i9%8i*cxx7-p)LH{UTMeiu%a@R0)@vzom4vzoK-ylJ;; ZEA|R+H7s7QHCLKztIpXUu^%z@e*v0#-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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 KR(rjD<9`8G4uv%U literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/__pycache__/target.cpython-37.pyc b/mitogen-0.2.7/ansible_mitogen/__pycache__/target.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10328252402dd7e254b0928647951289458f7ddd GIT binary patch literal 20112 zcmch9dyE`MdS6v{&rI*^gR2!qkurU1>yF~i-Ev4t)ai~V$s$Ek;)z@`T*{x?n>AEICX=wGif_MdT6{#?e` zTa2;vOEzPIwV2?-ig=6D*=kvIwp%uxot8uAN~=QWYO6};TB}B9x8>4#sx?LDdaF+7 z>DDxzXIe9Ko^8#_IblEMtz&dQ-+GG93&r{A;(VOWPBh??PjPmi#h%0DbDMSbKSrf0(f9REwN7z1DtVepK4(gP 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 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 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 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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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/loaders.py b/mitogen-0.2.7/ansible_mitogen/loaders.py new file mode 100644 index 000000000..ff06c0c --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/loaders.py @@ -0,0 +1,48 @@ +# 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. + +""" +Stable names for PluginLoader instances across Ansible versions. +""" + +from __future__ import absolute_import + +try: + from ansible.plugins.loader import action_loader + from ansible.plugins.loader import connection_loader + from ansible.plugins.loader import module_loader + from ansible.plugins.loader import module_utils_loader + from ansible.plugins.loader import shell_loader + from ansible.plugins.loader import strategy_loader +except ImportError: # Ansible <2.4 + from ansible.plugins import action_loader + from ansible.plugins import connection_loader + from ansible.plugins import module_loader + from ansible.plugins import module_utils_loader + from ansible.plugins import shell_loader + from ansible.plugins import strategy_loader diff --git a/mitogen-0.2.7/ansible_mitogen/loaders.pyc b/mitogen-0.2.7/ansible_mitogen/loaders.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc2f9d004e267668a8ec560c1f579e716ec0f312 GIT binary patch literal 797 zcmZWn&8pNe7(Hn_otf6b5yho;Ih)&x8#f|X#f2aua~DbxLZ{808%>kBNhSe%7k5{Ny0k1q>qh5o2l($G&hOZg)PY~h>Vu<`7 z!W?tL9CN}PbHW^R!W2>=aka|;m{$O@E883dD`e9%Q(P-ud=SHt%Xg~jm@c?9 zwvjC}Mbm0!Oz~10?)twmZMc%A%(%03M*!Hn0GfkQVz7+z{-m@Gg^eq;mMckxUmVi+ z0JKU<_J7Gu^r{;Kn@!yRn;t9|W>&o8MBOkUW?6TXvAVJBI8KH4JJ)KL04VKLOR05+ zP= 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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 Os&n+$e>>S->HZJiKo^_< 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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 LX}{c_Z6Em`5<~|1 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/__init__.py b/mitogen-0.2.7/ansible_mitogen/plugins/__init__.py new file mode 100644 index 000000000..e69de29 diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/action/__init__.py b/mitogen-0.2.7/ansible_mitogen/plugins/action/__init__.py new file mode 100644 index 000000000..e69de29 diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/action/mitogen_get_stack.py b/mitogen-0.2.7/ansible_mitogen/plugins/action/mitogen_get_stack.py new file mode 100644 index 000000000..12afbfb --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/action/mitogen_get_stack.py @@ -0,0 +1,54 @@ +# 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 + +""" +Fetch the connection configuration stack that would be used to connect to a +target, without actually connecting to it. +""" + +import ansible_mitogen.connection + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + def run(self, tmp=None, task_vars=None): + if not isinstance(self._connection, + ansible_mitogen.connection.Connection): + return { + 'skipped': True, + } + + return { + 'changed': True, + 'result': self._connection._build_stack(), + '_ansible_verbose_always': True, + } diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/__init__.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/__init__.py new file mode 100644 index 000000000..e69de29 diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_doas.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_doas.py new file mode 100644 index 000000000..1113d7c --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_doas.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 = 'mitogen_doas' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_docker.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_docker.py new file mode 100644 index 000000000..b71ef5f --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_docker.py @@ -0,0 +1,51 @@ +# 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 = 'docker' + + @property + def docker_cmd(self): + """ + Ansible 2.3 synchronize module wants to know how we run Docker. + """ + return 'docker' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_jail.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_jail.py new file mode 100644 index 000000000..c7475fb --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_jail.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 = 'jail' diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_kubectl.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_kubectl.py new file mode 100644 index 000000000..2dab131 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_kubectl.py @@ -0,0 +1,71 @@ +# coding: utf-8 +# Copyright 2018, Yannig Perré +# +# 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: + from ansible.plugins.connection import kubectl +except ImportError: + kubectl = None + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils.six import iteritems + +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 = 'kubectl' + + not_supported_msg = ( + 'The "mitogen_kubectl" plug-in requires a version of Ansible ' + 'that ships with the "kubectl" connection plug-in.' + ) + + def __init__(self, *args, **kwargs): + if kubectl is None: + raise AnsibleConnectionFailure(self.not_supported_msg) + super(Connection, self).__init__(*args, **kwargs) + + def get_extra_args(self): + parameters = [] + for key, option in iteritems(kubectl.CONNECTION_OPTIONS): + if self.get_task_var('ansible_' + key) is not None: + parameters += [ option, self.get_task_var('ansible_' + key) ] + + return parameters diff --git a/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_local.py b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_local.py new file mode 100644 index 000000000..24b84a0 --- /dev/null +++ b/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_local.py @@ -0,0 +1,86 @@ +# 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 +import ansible_mitogen.process + + +if sys.version_info > (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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 H(~bWFkHd+t 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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 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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 H@$&xyZpkRg 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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 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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⁢L&i8@=6=fp2nd-uIPsM?;Ov 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 Ac>n+a 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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 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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'M-s(&pocA@W=Yop;x8}UL^ezH4C>nH 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 G%=&*B=&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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'), + 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 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 Ws$Q?3te>uX^_6<9Ua8lX8vg;u(eCa5 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/doas.py b/mitogen-0.2.7/mitogen/doas.py new file mode 100644 index 000000000..1b687fb --- /dev/null +++ b/mitogen-0.2.7/mitogen/doas.py @@ -0,0 +1,113 @@ +# 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 mitogen.core +import mitogen.parent +from mitogen.core import b + + +LOG = logging.getLogger(__name__) + + +class PasswordError(mitogen.core.StreamError): + pass + + +class Stream(mitogen.parent.Stream): + create_child = staticmethod(mitogen.parent.hybrid_tty_create_child) + child_is_immediate_subprocess = False + + username = 'root' + password = None + doas_path = 'doas' + password_prompt = b('Password:') + incorrect_prompts = ( + b('doas: authentication failed'), + ) + + def construct(self, username=None, password=None, doas_path=None, + password_prompt=None, incorrect_prompts=None, **kwargs): + super(Stream, self).construct(**kwargs) + if username is not None: + self.username = username + if password is not None: + self.password = password + if doas_path is not None: + self.doas_path = doas_path + if password_prompt is not None: + self.password_prompt = password_prompt.lower() + if incorrect_prompts is not None: + self.incorrect_prompts = map(str.lower, incorrect_prompts) + + def _get_name(self): + return u'doas.' + mitogen.core.to_text(self.username) + + def get_boot_command(self): + bits = [self.doas_path, '-u', self.username, '--'] + bits = bits + super(Stream, self).get_boot_command() + LOG.debug('doas command line: %r', bits) + return bits + + password_incorrect_msg = 'doas password is incorrect' + password_required_msg = 'doas password is required' + + def _connect_input_loop(self, it): + password_sent = False + for buf in it: + LOG.debug('%r: received %r', self, buf) + if buf.endswith(self.EC0_MARKER): + self._ec0_received() + return + if any(s in buf.lower() for s in self.incorrect_prompts): + if password_sent: + raise PasswordError(self.password_incorrect_msg) + elif self.password_prompt in buf.lower(): + if self.password is None: + raise PasswordError(self.password_required_msg) + if password_sent: + raise PasswordError(self.password_incorrect_msg) + LOG.debug('sending password') + self.diag_stream.transmit_side.write( + mitogen.core.to_text(self.password + '\n').encode('utf-8') + ) + password_sent = True + raise mitogen.core.StreamError('bootstrap failed') + + def _connect_bootstrap(self): + it = mitogen.parent.iter_read( + fds=[self.receive_side.fd, self.diag_stream.receive_side.fd], + deadline=self.connect_deadline, + ) + try: + self._connect_input_loop(it) + finally: + it.close() diff --git a/mitogen-0.2.7/mitogen/docker.py b/mitogen-0.2.7/mitogen/docker.py new file mode 100644 index 000000000..0c0d40e --- /dev/null +++ b/mitogen-0.2.7/mitogen/docker.py @@ -0,0 +1,81 @@ +# 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 mitogen.core +import mitogen.parent + + +LOG = logging.getLogger(__name__) + + +class Stream(mitogen.parent.Stream): + child_is_immediate_subprocess = False + + container = None + image = None + username = None + docker_path = 'docker' + + # TODO: better way of capturing errors such as "No such container." + create_child_args = { + 'merge_stdio': True + } + + def construct(self, container=None, image=None, + docker_path=None, username=None, + **kwargs): + assert container or image + super(Stream, self).construct(**kwargs) + if container: + self.container = container + if image: + self.image = image + if docker_path: + self.docker_path = docker_path + if username: + self.username = username + + def _get_name(self): + return u'docker.' + (self.container or self.image) + + def get_boot_command(self): + args = ['--interactive'] + if self.username: + args += ['--user=' + self.username] + + bits = [self.docker_path] + if self.container: + bits += ['exec'] + args + [self.container] + elif self.image: + bits += ['run'] + args + ['--rm', self.image] + + return bits + super(Stream, self).get_boot_command() diff --git a/mitogen-0.2.7/mitogen/fakessh.py b/mitogen-0.2.7/mitogen/fakessh.py new file mode 100644 index 000000000..d39a710 --- /dev/null +++ b/mitogen-0.2.7/mitogen/fakessh.py @@ -0,0 +1,461 @@ +# 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 + +""" +:mod:`mitogen.fakessh` is a stream implementation that starts a subprocess with +its environment modified such that ``PATH`` searches for `ssh` return a Mitogen +implementation of SSH. When invoked, this implementation arranges for the +command line supplied by the caller to be executed in a remote context, reusing +the parent context's (possibly proxied) connection to that remote context. + +This allows tools like `rsync` and `scp` to transparently reuse the connections +and tunnels already established by the host program to connect to a target +machine, without wasteful redundant SSH connection setup, 3-way handshakes, or +firewall hopping configurations, and enables these tools to be used in +impossible scenarios, such as over `sudo` with ``requiretty`` enabled. + +The fake `ssh` command source is written to a temporary file on disk, and +consists of a copy of the :py:mod:`mitogen.core` source code (just like any +other child context), with a line appended to cause it to connect back to the +host process over an FD it inherits. As there is no reliance on an existing +filesystem file, it is possible for child contexts to use fakessh. + +As a consequence of connecting back through an inherited FD, only one SSH +invocation is possible, which is fine for tools like `rsync`, however in future +this restriction will be lifted. + +Sequence: + + 1. ``fakessh`` Context and Stream created by parent context. The stream's + buffer has a :py:func:`_fakessh_main` :py:data:`CALL_FUNCTION + ` 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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 Ov(UNJx!zgrT>cNC=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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 literal 0 HcmV?d00001 diff --git a/mitogen-0.2.7/mitogen/minify.py b/mitogen-0.2.7/mitogen/minify.py new file mode 100644 index 000000000..dc9f517 --- /dev/null +++ b/mitogen-0.2.7/mitogen/minify.py @@ -0,0 +1,139 @@ +# Copyright 2017, Alex Willmer +# +# 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 sys + +try: + from io import StringIO +except ImportError: + from StringIO import StringIO + +import mitogen.core + +if sys.version_info < (2, 7, 11): + from mitogen.compat import tokenize +else: + import tokenize + + +def minimize_source(source): + """Remove comments and docstrings from Python `source`, preserving line + numbers and syntax of empty blocks. + + :param str source: + The source to minimize. + + :returns str: + The minimized source. + """ + source = mitogen.core.to_text(source) + tokens = tokenize.generate_tokens(StringIO(source).readline) + tokens = strip_comments(tokens) + tokens = strip_docstrings(tokens) + tokens = reindent(tokens) + return tokenize.untokenize(tokens) + + +def strip_comments(tokens): + """Drop comment tokens from a `tokenize` stream. + + Comments on lines 1-2 are kept, to preserve hashbang and encoding. + Trailing whitespace is remove from all lines. + """ + prev_typ = None + prev_end_col = 0 + for typ, tok, (start_row, start_col), (end_row, end_col), line in tokens: + if typ in (tokenize.NL, tokenize.NEWLINE): + if prev_typ in (tokenize.NL, tokenize.NEWLINE): + start_col = 0 + else: + start_col = prev_end_col + end_col = start_col + 1 + elif typ == tokenize.COMMENT and start_row > 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{sY{5NhURqas#ol&zQgn^1&bx zNk2C#NFG{>omiWb{}io$5B1vu^8>VefCiY3l^%Sh>daCrJ4rC_;bJWGa6R{)P-z?v zV|A(GT*T6xw@ (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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 LUp(@Aix2-Fp)W+U 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