From fe33aee2fefbd1b80bc82c771797221d2a934af1 Mon Sep 17 00:00:00 2001 From: hogweed1 Date: Tue, 22 Oct 2024 16:16:43 +1000 Subject: [PATCH] w8 wut --- .drone.yml | 28 +- .gitignore | 1 + ansible.cfg | 41 +- .../just-created/group_vars/all/ssh-creds.yml | 5 + environments/just-created/group_vars/lxc.yml | 5 + environments/just-created/hosts.yml | 11 + environments/proxmoxes/group_vars/all/all.yml | 2 + .../proxmoxes/group_vars/all/ssh-creds.yml | 4 + environments/proxmoxes/hosts.yml | 55 + mitogen-0.3.9.tar.gz | Bin 0 -> 224792 bytes mitogen-0.3.9/LICENSE | 26 + mitogen-0.3.9/MANIFEST.in | 1 + mitogen-0.3.9/PKG-INFO | 39 + mitogen-0.3.9/README.md | 9 + mitogen-0.3.9/ansible_mitogen/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 165 bytes .../__pycache__/affinity.cpython-310.pyc | Bin 0 -> 10067 bytes .../__pycache__/connection.cpython-310.pyc | Bin 0 -> 27626 bytes .../__pycache__/loaders.cpython-310.pyc | Bin 0 -> 1927 bytes .../__pycache__/logging.cpython-310.pyc | Bin 0 -> 2539 bytes .../__pycache__/mixins.cpython-310.pyc | Bin 0 -> 15979 bytes .../__pycache__/module_finder.cpython-310.pyc | Bin 0 -> 6611 bytes .../__pycache__/parsing.cpython-310.pyc | Bin 0 -> 1539 bytes .../__pycache__/planner.cpython-310.pyc | Bin 0 -> 19261 bytes .../__pycache__/process.cpython-310.pyc | Bin 0 -> 20588 bytes .../__pycache__/runner.cpython-310.pyc | Bin 0 -> 35855 bytes .../__pycache__/services.cpython-310.pyc | Bin 0 -> 16157 bytes .../__pycache__/strategy.cpython-310.pyc | Bin 0 -> 10788 bytes .../__pycache__/target.cpython-310.pyc | Bin 0 -> 20351 bytes .../transport_config.cpython-310.pyc | Bin 0 -> 27554 bytes mitogen-0.3.9/ansible_mitogen/affinity.py | 288 ++ .../ansible_mitogen/compat/__init__.py | 0 mitogen-0.3.9/ansible_mitogen/connection.py | 1134 +++++ mitogen-0.3.9/ansible_mitogen/loaders.py | 103 + mitogen-0.3.9/ansible_mitogen/logging.py | 130 + mitogen-0.3.9/ansible_mitogen/mixins.py | 533 +++ .../ansible_mitogen/module_finder.py | 281 ++ mitogen-0.3.9/ansible_mitogen/parsing.py | 77 + mitogen-0.3.9/ansible_mitogen/planner.py | 631 +++ .../ansible_mitogen/plugins/__init__.py | 0 .../plugins/action/__init__.py | 0 .../plugins/action/mitogen_fetch.py | 207 + .../plugins/action/mitogen_get_stack.py | 58 + .../plugins/connection/__init__.py | 0 .../__pycache__/mitogen_local.cpython-310.pyc | Bin 0 -> 2030 bytes .../__pycache__/mitogen_ssh.cpython-310.pyc | Bin 0 -> 2123 bytes .../plugins/connection/mitogen_buildah.py | 46 + .../plugins/connection/mitogen_doas.py | 46 + .../plugins/connection/mitogen_docker.py | 53 + .../plugins/connection/mitogen_jail.py | 46 + .../plugins/connection/mitogen_kubectl.py | 82 + .../plugins/connection/mitogen_local.py | 88 + .../plugins/connection/mitogen_lxc.py | 46 + .../plugins/connection/mitogen_lxd.py | 46 + .../plugins/connection/mitogen_machinectl.py | 46 + .../plugins/connection/mitogen_podman.py | 46 + .../plugins/connection/mitogen_setns.py | 46 + .../plugins/connection/mitogen_ssh.py | 87 + .../plugins/connection/mitogen_su.py | 46 + .../plugins/connection/mitogen_sudo.py | 46 + .../plugins/strategy/__init__.py | 0 .../mitogen_linear.cpython-310.pyc | Bin 0 -> 843 bytes .../plugins/strategy/mitogen.py | 63 + .../plugins/strategy/mitogen_free.py | 64 + .../plugins/strategy/mitogen_host_pinned.py | 69 + .../plugins/strategy/mitogen_linear.py | 64 + mitogen-0.3.9/ansible_mitogen/process.py | 710 +++ mitogen-0.3.9/ansible_mitogen/runner.py | 1101 +++++ mitogen-0.3.9/ansible_mitogen/services.py | 565 +++ mitogen-0.3.9/ansible_mitogen/strategy.py | 328 ++ mitogen-0.3.9/ansible_mitogen/target.py | 771 +++ .../ansible_mitogen/transport_config.py | 820 ++++ .../ansible_mitogen/utils/__init__.py | 29 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 719 bytes .../utils/__pycache__/unsafe.cpython-310.pyc | Bin 0 -> 2896 bytes mitogen-0.3.9/ansible_mitogen/utils/unsafe.py | 79 + mitogen-0.3.9/mitogen.egg-info/PKG-INFO | 39 + mitogen-0.3.9/mitogen.egg-info/SOURCES.txt | 82 + .../mitogen.egg-info/dependency_links.txt | 1 + mitogen-0.3.9/mitogen.egg-info/not-zip-safe | 1 + mitogen-0.3.9/mitogen.egg-info/top_level.txt | 2 + mitogen-0.3.9/mitogen/__init__.py | 120 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2215 bytes .../mitogen/__pycache__/core.cpython-310.pyc | Bin 0 -> 127909 bytes .../mitogen/__pycache__/debug.cpython-310.pyc | Bin 0 -> 6557 bytes .../mitogen/__pycache__/fork.cpython-310.pyc | Bin 0 -> 5898 bytes .../__pycache__/master.cpython-310.pyc | Bin 0 -> 44260 bytes .../__pycache__/minify.cpython-310.pyc | Bin 0 -> 2883 bytes .../__pycache__/parent.cpython-310.pyc | Bin 0 -> 85117 bytes .../__pycache__/select.cpython-310.pyc | Bin 0 -> 10624 bytes .../__pycache__/service.cpython-310.pyc | Bin 0 -> 35617 bytes .../mitogen/__pycache__/ssh.cpython-310.pyc | Bin 0 -> 7085 bytes .../mitogen/__pycache__/sudo.cpython-310.pyc | Bin 0 -> 6075 bytes .../mitogen/__pycache__/unix.cpython-310.pyc | Bin 0 -> 6095 bytes .../mitogen/__pycache__/utils.cpython-310.pyc | Bin 0 -> 6501 bytes mitogen-0.3.9/mitogen/buildah.py | 72 + mitogen-0.3.9/mitogen/compat/__init__.py | 0 mitogen-0.3.9/mitogen/compat/pkgutil.py | 594 +++ mitogen-0.3.9/mitogen/compat/tokenize.py | 453 ++ mitogen-0.3.9/mitogen/core.py | 4196 +++++++++++++++++ mitogen-0.3.9/mitogen/debug.py | 236 + mitogen-0.3.9/mitogen/doas.py | 142 + mitogen-0.3.9/mitogen/docker.py | 83 + mitogen-0.3.9/mitogen/fakessh.py | 455 ++ mitogen-0.3.9/mitogen/fork.py | 250 + mitogen-0.3.9/mitogen/jail.py | 65 + mitogen-0.3.9/mitogen/kubectl.py | 66 + mitogen-0.3.9/mitogen/lxc.py | 73 + mitogen-0.3.9/mitogen/lxd.py | 75 + mitogen-0.3.9/mitogen/master.py | 1572 ++++++ mitogen-0.3.9/mitogen/minify.py | 143 + mitogen-0.3.9/mitogen/os_fork.py | 186 + mitogen-0.3.9/mitogen/parent.py | 2771 +++++++++++ mitogen-0.3.9/mitogen/podman.py | 73 + mitogen-0.3.9/mitogen/profiler.py | 164 + mitogen-0.3.9/mitogen/select.py | 348 ++ mitogen-0.3.9/mitogen/service.py | 1182 +++++ mitogen-0.3.9/mitogen/setns.py | 241 + mitogen-0.3.9/mitogen/ssh.py | 310 ++ mitogen-0.3.9/mitogen/su.py | 160 + mitogen-0.3.9/mitogen/sudo.py | 294 ++ mitogen-0.3.9/mitogen/unix.py | 226 + mitogen-0.3.9/mitogen/utils.py | 236 + mitogen-0.3.9/setup.cfg | 18 + mitogen-0.3.9/setup.py | 86 + mitogen-0.3.9/tests/testlib.py | 670 +++ playbooks/exporters.yml | 21 + playbooks/packages.yml | 59 + playbooks/users.yml | 28 + requirements.yml | 9 + 130 files changed, 24883 insertions(+), 21 deletions(-) create mode 100644 environments/just-created/group_vars/all/ssh-creds.yml create mode 100644 environments/just-created/group_vars/lxc.yml create mode 100644 environments/just-created/hosts.yml create mode 100644 environments/proxmoxes/group_vars/all/all.yml create mode 100644 environments/proxmoxes/group_vars/all/ssh-creds.yml create mode 100644 environments/proxmoxes/hosts.yml create mode 100644 mitogen-0.3.9.tar.gz create mode 100644 mitogen-0.3.9/LICENSE create mode 100644 mitogen-0.3.9/MANIFEST.in create mode 100644 mitogen-0.3.9/PKG-INFO create mode 100644 mitogen-0.3.9/README.md create mode 100644 mitogen-0.3.9/ansible_mitogen/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/__init__.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/affinity.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/connection.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/loaders.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/logging.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/mixins.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/module_finder.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/parsing.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/planner.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/process.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/runner.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/services.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/strategy.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/target.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/__pycache__/transport_config.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/affinity.py create mode 100644 mitogen-0.3.9/ansible_mitogen/compat/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/connection.py create mode 100644 mitogen-0.3.9/ansible_mitogen/loaders.py create mode 100644 mitogen-0.3.9/ansible_mitogen/logging.py create mode 100644 mitogen-0.3.9/ansible_mitogen/mixins.py create mode 100644 mitogen-0.3.9/ansible_mitogen/module_finder.py create mode 100644 mitogen-0.3.9/ansible_mitogen/parsing.py create mode 100644 mitogen-0.3.9/ansible_mitogen/planner.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/action/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_fetch.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_get_stack.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/__pycache__/mitogen_local.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/__pycache__/mitogen_ssh.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_buildah.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_doas.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_docker.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_jail.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_kubectl.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_local.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxc.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxd.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_machinectl.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_podman.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_setns.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_ssh.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_su.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_sudo.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/strategy/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/strategy/__pycache__/mitogen_linear.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_free.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py create mode 100644 mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_linear.py create mode 100644 mitogen-0.3.9/ansible_mitogen/process.py create mode 100644 mitogen-0.3.9/ansible_mitogen/runner.py create mode 100644 mitogen-0.3.9/ansible_mitogen/services.py create mode 100644 mitogen-0.3.9/ansible_mitogen/strategy.py create mode 100644 mitogen-0.3.9/ansible_mitogen/target.py create mode 100644 mitogen-0.3.9/ansible_mitogen/transport_config.py create mode 100644 mitogen-0.3.9/ansible_mitogen/utils/__init__.py create mode 100644 mitogen-0.3.9/ansible_mitogen/utils/__pycache__/__init__.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/utils/__pycache__/unsafe.cpython-310.pyc create mode 100644 mitogen-0.3.9/ansible_mitogen/utils/unsafe.py create mode 100644 mitogen-0.3.9/mitogen.egg-info/PKG-INFO create mode 100644 mitogen-0.3.9/mitogen.egg-info/SOURCES.txt create mode 100644 mitogen-0.3.9/mitogen.egg-info/dependency_links.txt create mode 100644 mitogen-0.3.9/mitogen.egg-info/not-zip-safe create mode 100644 mitogen-0.3.9/mitogen.egg-info/top_level.txt create mode 100644 mitogen-0.3.9/mitogen/__init__.py create mode 100644 mitogen-0.3.9/mitogen/__pycache__/__init__.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/core.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/debug.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/fork.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/master.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/minify.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/parent.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/select.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/service.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/ssh.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/sudo.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/unix.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/__pycache__/utils.cpython-310.pyc create mode 100644 mitogen-0.3.9/mitogen/buildah.py create mode 100644 mitogen-0.3.9/mitogen/compat/__init__.py create mode 100644 mitogen-0.3.9/mitogen/compat/pkgutil.py create mode 100644 mitogen-0.3.9/mitogen/compat/tokenize.py create mode 100644 mitogen-0.3.9/mitogen/core.py create mode 100644 mitogen-0.3.9/mitogen/debug.py create mode 100644 mitogen-0.3.9/mitogen/doas.py create mode 100644 mitogen-0.3.9/mitogen/docker.py create mode 100644 mitogen-0.3.9/mitogen/fakessh.py create mode 100644 mitogen-0.3.9/mitogen/fork.py create mode 100644 mitogen-0.3.9/mitogen/jail.py create mode 100644 mitogen-0.3.9/mitogen/kubectl.py create mode 100644 mitogen-0.3.9/mitogen/lxc.py create mode 100644 mitogen-0.3.9/mitogen/lxd.py create mode 100644 mitogen-0.3.9/mitogen/master.py create mode 100644 mitogen-0.3.9/mitogen/minify.py create mode 100644 mitogen-0.3.9/mitogen/os_fork.py create mode 100644 mitogen-0.3.9/mitogen/parent.py create mode 100644 mitogen-0.3.9/mitogen/podman.py create mode 100644 mitogen-0.3.9/mitogen/profiler.py create mode 100644 mitogen-0.3.9/mitogen/select.py create mode 100644 mitogen-0.3.9/mitogen/service.py create mode 100644 mitogen-0.3.9/mitogen/setns.py create mode 100644 mitogen-0.3.9/mitogen/ssh.py create mode 100644 mitogen-0.3.9/mitogen/su.py create mode 100644 mitogen-0.3.9/mitogen/sudo.py create mode 100644 mitogen-0.3.9/mitogen/unix.py create mode 100644 mitogen-0.3.9/mitogen/utils.py create mode 100644 mitogen-0.3.9/setup.cfg create mode 100644 mitogen-0.3.9/setup.py create mode 100644 mitogen-0.3.9/tests/testlib.py create mode 100644 playbooks/exporters.yml create mode 100644 playbooks/packages.yml create mode 100644 playbooks/users.yml create mode 100644 requirements.yml diff --git a/.drone.yml b/.drone.yml index 653a8b7..4ad799e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,19 +1,19 @@ --- -kind: pipeline -type: exec -name: default +# kind: pipeline +# type: exec +# name: default -platform: - os: linux - arch: amd64 +# platform: +# os: linux +# arch: amd64 -clone: - # убрано так как сейчас не тестим ничего предварительно а сразу тянем в директорию - disable: true +# clone: +# # убрано так как сейчас не тестим ничего предварительно а сразу тянем в директорию +# disable: true -steps: -- name: pull into the folduh - commands: - - cd /etc/ansible - - git pull origin some-kind-of-lobster \ No newline at end of file +# steps: +# - name: pull into the folduh +# commands: +# - cd /etc/ansible +# - git pull origin some-kind-of-lobster \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1fa222a..dcb25f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vaulto +asdf \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg index 42749e3..e820ee7 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,4 +1,15 @@ +#### export ANSIBLE_CONFIG=./ansible.cfg + [defaults] +gathering = smart +fact_caching = jsonfile +fact_caching_connection = /tmp/facts_cache +# two hours timeout +fact_caching_timeout = 7200 + + +interpreter_python = auto_silent +ansible_python_interpreter = auto_silent # Use the YAML callback plugin. stdout_callback = yaml # Use the stdout_callback when running ad-hoc commands. @@ -6,12 +17,28 @@ bin_ansible_callbacks = True host_key_checking = false -vault_password_file = /etc/ansible/.vaulto +#vault_password_file = /etc/ansible/.vaulto +vault_password_file = /tmp/.vaulto -callback_plugins = /etc/ansible/plugins/callback -callback_whitelist = telegram -callbacks_enabled = telegram +# callback_plugins = /etc/ansible/plugins/callback +# callback_whitelist = telegram +# callbacks_enabled = telegram -[callback_telegram] -tg_token = 6472915685:AAHPvgrQoqG7DxtfbnHWPe3Lfild-CGJ1j8 -tg_chat_id = -4023350326 \ No newline at end of file +strategy_plugins = mitogen-0.3.9/ansible_mitogen/plugins/strategy +strategy = mitogen_linear + +#### TODO чому-то не делается +roles_path = roles:internal_roles +# # [callback_telegram] +# # tg_token = 6472915685:AAHPvgrQoqG7DxtfbnHWPe3Lfild-CGJ1j8 +# # tg_chat_id = -4023350326 + + +# добавление юзера +# useradd -m hogweed1 -s /usr/bin/bash +# passwd hogweed1 +# sudo adduser hogweed1 sudo + +[ssh_connection] +# Enable pipelining, requires disabling requiretty in sudoers +pipelining = True \ No newline at end of file diff --git a/environments/just-created/group_vars/all/ssh-creds.yml b/environments/just-created/group_vars/all/ssh-creds.yml new file mode 100644 index 0000000..322f857 --- /dev/null +++ b/environments/just-created/group_vars/all/ssh-creds.yml @@ -0,0 +1,5 @@ +--- +ansible_ssh_user: root +ansible_ssh_pass: admin +ansible_sudo_pass: admin +ansible_ssh_private_key_file: '/home/hogweed1/id25519.key' \ No newline at end of file diff --git a/environments/just-created/group_vars/lxc.yml b/environments/just-created/group_vars/lxc.yml new file mode 100644 index 0000000..e907b26 --- /dev/null +++ b/environments/just-created/group_vars/lxc.yml @@ -0,0 +1,5 @@ +--- +ansible_ssh_user: hogweed1 +ansible_ssh_pass: coloredhorses +ansible_sudo_pass: coloredhorses +ansible_ssh_private_key_file: '/home/hogweed1/id25519.key' \ No newline at end of file diff --git a/environments/just-created/hosts.yml b/environments/just-created/hosts.yml new file mode 100644 index 0000000..a37d47c --- /dev/null +++ b/environments/just-created/hosts.yml @@ -0,0 +1,11 @@ +--- +# all: # keys must be unique, i.e. only one 'hosts' per group +# hosts: +# #nexus.guaranteedstruggle.host: +# #printing-slut.guaranteedstruggle.host: +# harbor.guaranteedstruggle.host: + +lxc: # keys must be unique, i.e. only one 'hosts' per group + hosts: + ### but its a vm wtf + harbor.guaranteedstruggle.host: \ No newline at end of file diff --git a/environments/proxmoxes/group_vars/all/all.yml b/environments/proxmoxes/group_vars/all/all.yml new file mode 100644 index 0000000..48b125b --- /dev/null +++ b/environments/proxmoxes/group_vars/all/all.yml @@ -0,0 +1,2 @@ +# отключаем ворнинг с митогена - https://github.com/mitogen-hq/mitogen/issues/740#issuecomment-731513058 +ansible_python_interpreter: /usr/bin/python3 \ No newline at end of file diff --git a/environments/proxmoxes/group_vars/all/ssh-creds.yml b/environments/proxmoxes/group_vars/all/ssh-creds.yml new file mode 100644 index 0000000..b5cc034 --- /dev/null +++ b/environments/proxmoxes/group_vars/all/ssh-creds.yml @@ -0,0 +1,4 @@ +--- +ansible_ssh_user: hogweed1 +ansible_ssh_pass: coloredhorses +ansible_sudo_pass: coloredhorses \ No newline at end of file diff --git a/environments/proxmoxes/hosts.yml b/environments/proxmoxes/hosts.yml new file mode 100644 index 0000000..d8edfc3 --- /dev/null +++ b/environments/proxmoxes/hosts.yml @@ -0,0 +1,55 @@ +--- +physical_machines: + hosts: + cyberbully.guaranteedstruggle.host: + gpu-slut.guaranteedstruggle.host: + children: + proxmoxes: + +proxmoxes: # keys must be unique, i.e. only one 'hosts' per group + hosts: + king-albert.guaranteedstruggle.host: + children: + semyons: + +semyons: # keys must be unique, i.e. only one 'hosts' per group + hosts: + semyon-0x01.guaranteedstruggle.host: + semyon-0x02.guaranteedstruggle.host: + semyon-0x03.guaranteedstruggle.host: + semyon-0x04.guaranteedstruggle.host: + semyon-0x05.guaranteedstruggle.host: +vms: + children: + printer: + kubernetes: + docker: + +docker: + hosts: + swarm-node1.guaranteedstruggle.host: + swarm-node2.guaranteedstruggle.host: + swarm-node3.guaranteedstruggle.host: + + harbor.guaranteedstruggle.host: + +kubernetes: + hosts: + rke2-master1.guaranteedstruggle.host: + rke2-master2.guaranteedstruggle.host: + rke2-master3.guaranteedstruggle.host: + rke2-worker1.guaranteedstruggle.host: + rke2-worker2.guaranteedstruggle.host: + rke2-worker3.guaranteedstruggle.host: + rke2-worker4.guaranteedstruggle.host: + rke2-worker5.guaranteedstruggle.host: + + k3s-rancher.guaranteedstruggle.host: + k3s-awx.guaranteedstruggle.host: + +printer: + hosts: + printing-slut.guaranteedstruggle.host: + +#### TODO +# lxc: diff --git a/mitogen-0.3.9.tar.gz b/mitogen-0.3.9.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d2a6e2e1d9bda14e007215fc90088b1e03e872a5 GIT binary patch literal 224792 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

Wm*FbwF$x&$>8Sl$^|hwt!QbJb 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

Ax_@D|}Vpmx_X_ubW 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�z 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 F5CF`+v+n=^ literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/LICENSE b/mitogen-0.3.9/LICENSE new file mode 100644 index 0000000..62ef15d --- /dev/null +++ b/mitogen-0.3.9/LICENSE @@ -0,0 +1,26 @@ +Copyright 2021, the Mitogen authors + +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.3.9/MANIFEST.in b/mitogen-0.3.9/MANIFEST.in new file mode 100644 index 0000000..1aba38f --- /dev/null +++ b/mitogen-0.3.9/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE diff --git a/mitogen-0.3.9/PKG-INFO b/mitogen-0.3.9/PKG-INFO new file mode 100644 index 0000000..e324817 --- /dev/null +++ b/mitogen-0.3.9/PKG-INFO @@ -0,0 +1,39 @@ +Metadata-Version: 2.1 +Name: mitogen +Version: 0.3.9 +Summary: Library for writing distributed self-replicating programs. +Home-page: https://github.com/mitogen-hq/mitogen/ +Author: David Wilson +License: New BSD +Classifier: Environment :: Console +Classifier: Framework :: Ansible +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: System :: Distributed Computing +Classifier: Topic :: System :: Systems Administration +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* +Description-Content-Type: text/markdown +License-File: LICENSE + +# Mitogen + +Please see the documentation. + +![](https://i.imgur.com/eBM6LhJ.gif) + +[![Total alerts](https://img.shields.io/lgtm/alerts/g/mitogen-hq/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mitogen-hq/mitogen/alerts/) + +[![Build Status](https://dev.azure.com/mitogen-hq/mitogen/_apis/build/status/mitogen-hq.mitogen?branchName=master)](https://dev.azure.com/mitogen-hq/mitogen/_build/latest?definitionId=1&branchName=master) diff --git a/mitogen-0.3.9/README.md b/mitogen-0.3.9/README.md new file mode 100644 index 0000000..0d4d1b3 --- /dev/null +++ b/mitogen-0.3.9/README.md @@ -0,0 +1,9 @@ +# Mitogen + +Please see the documentation. + +![](https://i.imgur.com/eBM6LhJ.gif) + +[![Total alerts](https://img.shields.io/lgtm/alerts/g/mitogen-hq/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mitogen-hq/mitogen/alerts/) + +[![Build Status](https://dev.azure.com/mitogen-hq/mitogen/_apis/build/status/mitogen-hq.mitogen?branchName=master)](https://dev.azure.com/mitogen-hq/mitogen/_build/latest?definitionId=1&branchName=master) diff --git a/mitogen-0.3.9/ansible_mitogen/__init__.py b/mitogen-0.3.9/ansible_mitogen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/__init__.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..109c61aba14e93a604fc10e03c71b5bf3615140d GIT binary patch literal 165 zcmd1j<>g`kf@^Bql0fuh5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!HAer{fgezJaY zeoAUueojhik$z%cab{9Zs%}wgLB4)&W=Vc}YM!ovp0S=KOe!8KsvjSpnU`4-AFo$X Xd5gm)H$SB`C)EyQLopMOU||3NgX|}C literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/affinity.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/affinity.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a3095e5b03521ad7e1219e4f2ecc0ccfc82bc35 GIT binary patch literal 10067 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/connection.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/connection.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c77892c786aeb7d1290496db8175f1aabf7db604 GIT binary patch literal 27626 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/module_finder.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/module_finder.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d582354d14d4d3b9c9e390013d42a983bb54d477 GIT binary patch literal 6611 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/parsing.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/parsing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43256c792bb259fa1ad0307235f5a76e40f332b2 GIT binary patch literal 1539 zcmds%PmdEf6u`%x$)AQ~K`UCIs*qVYFw&B#;M9mBDh{ZkijcNQRW1`Jb~f(Lct*B| z-AF+~+VVX%N4@|b#@C+q%#91YH_5KTi9443?DuTHd4B%QD|)?70PV&750i)8Aow4Z z?M6X)3f-a*fgo9slM%`3i00uaBxs{xi$)O%WMk9>J^&!F2Sh-GjFfeI?ZRrrQ1iHMG<*>yyOjxK{!a!SDpVK2!$dMh{!SHT$s zE1E0%hQbbdN<e=1?*Z8(U{SL;A`8Z*2EpS9=G@_2(H7Rz2wG9=H`;w zsZph4a4%^}BSA8}HC&GsGsfAg;h+DoIk)o(*N3dK3M8I0RVHoi>o29N6IhY9->D6Q zk{g~g>kP9+Wu|h#4jg3ekY(yfvc06TCYdOmNd20X3pX$H-o;UfpfcJHYDXC9-&L7O z3}t+OE5?1s&0##=XUB7CBpc(>I{GnFcI#r7ezp!Xt_2%|gLYal;>|ze1g0(76r1bxd8#uXO(4+sf%y?!etMqd+9lHj z(VG>SkS5{Us!1jv7+Ds{#^Zxu`;Yp+T`Z*=YXZ-M&)6>){wB_4x`rU#%F#XejNJjT z(`%C*ax?0Z>x2<=1595bj#rGJ*NESS$r|w$u`Pj6BF>?R5a&|@5H|Q9aHLM!8o~`? zdzXx5O3S@1((FPze!^^3BA8OJ$%56)T`=2P?C(_u_~$2xAD zJyvdBzf(-iE-cl^ti?~DLKUmu{374KgUvQUbb1lFM$OMKjj#Al3Uvbo1n`be_5iklIb+UHgn$grtnX+O>j0hQzlVq~Gp Q2iJe;l8CfvJM7;20<6@n-2eap literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/planner.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/planner.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c36c82b1be097f7df831c02ef0c6407aff6cc68 GIT binary patch literal 19261 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/process.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/process.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93e7dff30bebdd920f14e363a255d93dfbb77242 GIT binary patch literal 20588 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/runner.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/runner.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f69ca83e4cbe245b0418ead01fb0e4e542471c0 GIT binary patch literal 35855 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 Ru{^i1wCota%l|X;{|8>Vw*3GA literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/strategy.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/strategy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4bf582d874a10b9c325dd922cf828c7b93fb547 GIT binary patch literal 10788 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 V3^kY_VKc1H(m&4@@&^i2{}&9`x(NUP literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/__pycache__/transport_config.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/__pycache__/transport_config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9da3bac2176fc6d26e74f0d424d186f02c63c085 GIT binary patch literal 27554 zcmb__32T^F@JVyjY{IH|-bZ^$IJmrf<+smQ?6{IDSEWkM zVa1m6eSg2#@68P;3)b{|{rYwPNB{rd$N%?Zb;E{i41d42;pu#-8jJlGW}<%?WX|E| z9gfFhDmEKaag`{=%Zb@UIXRmwr)E=eK1<@+!0bRg=B8%{kx!K}vl+P#%?`;mJ3B1b zk@AMwQK>Uf8Y_>_j>~shhEhUCzFAES9}itW_7>O3r;yb1TZrov(PsxsrRt%hepc;MQ`5YGuB-P}fea zSgqvBZeh`>6uok;SjloX2OxBqsc>xO-9nXMj!K+fO<-GdRQdJX$xn)bWQ_DG}(s2|P7ITgVjLN1r@}9St zSMFSWAzuQIOG1wa4uQ2HF~rkzz-aK_RPWBgCPV?Wdf$tXhTf}^QOYg33l6bFzKZsB zt_)TcE3P+bu_l?7Dv*^9s-XYpHL$o})pyB1u&?MHM`tQXf#r>Kg~*YctJXnx2&<`M zL)J-`0Mi_#u2}PO)%%sn+*QbdarRB? zW;j6C2ymook^P}ucd4|T^B{(fGKhn&MMtX>_aW!PC-=b;gfd_c6sPXh!IW}Ufz

q@b-E5cH`kG~q!lSM&_kDt8|}<7Sttp6Dt& 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 sxe=wA!O13`dc?t)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. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import ctypes +import logging +import mmap +import multiprocessing +import os +import struct + +import mitogen.core +import mitogen.parent + + +LOG = logging.getLogger(__name__) + + +try: + _libc = ctypes.CDLL(None, use_errno=True) + _strerror = _libc.strerror + _strerror.restype = ctypes.c_char_p + _sem_init = _libc.sem_init + _sem_wait = _libc.sem_wait + _sem_post = _libc.sem_post + _sched_setaffinity = _libc.sched_setaffinity +except (OSError, AttributeError): + _libc = None + _strerror = None + _sem_init = None + _sem_wait = None + _sem_post = None + _sched_setaffinity = None + + +class sem_t(ctypes.Structure): + """ + Wrap sem_t to allow storing a lock in shared memory. + """ + _fields_ = [ + ('data', ctypes.c_uint8 * 128), + ] + + def init(self): + if _sem_init(self.data, 1, 1): + raise Exception(_strerror(ctypes.get_errno())) + + def acquire(self): + if _sem_wait(self.data): + raise Exception(_strerror(ctypes.get_errno())) + + def release(self): + if _sem_post(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', sem_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, index): + """ + 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.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, descr, mask): + if descr: + LOG.debug('CPU mask for %s: %#08x', descr, mask) + mitogen.parent._preexec_hook = self._clear + self._set_cpu_mask(mask) + + def _balance(self, descr): + self.state.lock.acquire() + try: + n = self.state.counter + self.state.counter += 1 + finally: + self.state.lock.release() + + self._set_cpu(descr, self._reserve_shift + ( + (n % (self.cpu_count - self._reserve_shift)) + )) + + def _set_cpu(self, descr, cpu): + self._set_affinity(descr, 1 << (cpu % self.cpu_count)) + + def _clear(self): + all_cpus = (1 << self.cpu_count) - 1 + self._set_affinity(None, all_cpus & ~self._reserve_mask) + + def assign_controller(self): + if self._reserve_controller: + self._set_cpu('Ansible top-level process', 1) + else: + self._balance('Ansible top-level process') + + def assign_muxprocess(self, index): + self._set_cpu('MuxProcess %d' % (index,), index) + + def assign_worker(self): + self._balance('WorkerProcess') + + 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 _get_thread_ids(self): + try: + ents = os.listdir('/proc/self/task') + except OSError: + LOG.debug('cannot fetch thread IDs for current process') + return [os.getpid()] + + return [int(s) for s in ents if s.isdigit()] + + def _set_cpu_mask(self, mask): + s = self._mask_to_bytes(mask) + for tid in self._get_thread_ids(): + _sched_setaffinity(tid, len(s), s) + + +if _sched_setaffinity is not None: + policy = LinuxPolicy() +else: + policy = Policy() diff --git a/mitogen-0.3.9/ansible_mitogen/compat/__init__.py b/mitogen-0.3.9/ansible_mitogen/compat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/ansible_mitogen/connection.py b/mitogen-0.3.9/ansible_mitogen/connection.py new file mode 100644 index 0000000..6bdf11b --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/connection.py @@ -0,0 +1,1134 @@ +# 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, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +import errno +import logging +import os +import pprint +import stat +import sys +import time + +import ansible.constants as C +import ansible.errors +import ansible.plugins.connection + +import mitogen.core + +import ansible_mitogen.mixins +import ansible_mitogen.parsing +import ansible_mitogen.process +import ansible_mitogen.services +import ansible_mitogen.target +import ansible_mitogen.transport_config +import ansible_mitogen.utils.unsafe + + +LOG = logging.getLogger(__name__) + +task_vars_msg = ( + 'could not recover task_vars. This means some connection ' + 'settings may erroneously be reset to their defaults. ' + 'Please report a bug if you encounter this message.' +) + + +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 spec.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), + 'keepalive_count': ( + spec.mitogen_ssh_keepalive_count() or 10 + ), + 'keepalive_interval': ( + spec.mitogen_ssh_keepalive_interval() or 30 + ), + } + } + +def _connect_buildah(spec): + """ + Return ContextService arguments for a Buildah connection. + """ + return { + 'method': 'buildah', + '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_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(rediscover_python=True), + '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_podman(spec): + """ + Return ContextService arguments for a Docker connection. + """ + return { + 'method': 'podman', + 'kwargs': { + 'username': spec.remote_user(), + 'container': spec.remote_addr(), + 'python_path': spec.python_path(rediscover_python=True), + 'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(), + 'remote_name': get_remote_name(spec), + } + } + +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.ansible_doas_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 = { + 'buildah': _connect_buildah, + 'docker': _connect_docker, + 'kubectl': _connect_kubectl, + 'jail': _connect_jail, + 'local': _connect_local, + 'lxc': _connect_lxc, + 'lxd': _connect_lxd, + 'machinectl': _connect_machinectl, + 'podman': _connect_podman, + '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 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): + #: The :class:`ansible_mitogen.process.Binding` representing the connection + #: multiplexer this connection's target is assigned to. :data:`None` when + #: disconnected. + binding = 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. + # Ansible ConnectionBase attribute, removed in Ansible >= 2.8 + 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 task_vars by on_action_run(). + _task_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 + + # set by `_get_task_vars()` for interpreter discovery + _action = None + + 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._task_vars = task_vars + self.delegate_to_hostname = delegate_to_hostname + self.loader_basedir = loader_basedir + self._put_connection() + + def _get_task_vars(self): + """ + More information is needed than normally provided to an Ansible + connection. For proxied connections, intermediary configuration must + be inferred, and for any connection the configured Python interpreter + must be known. + + There is no clean way to access this information that would not deviate + from the running Ansible version. The least invasive method known is to + reuse the running task's task_vars dict. + + This method walks the stack to find task_vars of the Action plugin's + run(), or if no Action is present, from Strategy's _execute_meta(), as + in the case of 'meta: reset_connection'. The stack is walked in + addition to subclassing Action.run()/on_action_run(), as it is possible + for new connections to be constructed in addition to the preconstructed + connection passed into any running action. + """ + if self._task_vars is not None: + # check for if self._action has already been set or not + # there are some cases where the ansible executor passes in task_vars + # so we don't walk the stack to find them + # TODO: is there a better way to get the ActionModuleMixin object? + # ansible python discovery needs it to run discover_interpreter() + if not isinstance(self._action, ansible_mitogen.mixins.ActionModuleMixin): + f = sys._getframe() + while f: + if f.f_code.co_name == 'run': + f_self = f.f_locals.get('self') + if isinstance(f_self, ansible_mitogen.mixins.ActionModuleMixin): + self._action = f_self + break + elif f.f_code.co_name == '_execute_meta': + break + f = f.f_back + + return self._task_vars + + f = sys._getframe() + while f: + if f.f_code.co_name == 'run': + f_locals = f.f_locals + f_self = f_locals.get('self') + if isinstance(f_self, ansible_mitogen.mixins.ActionModuleMixin): + # backref for python interpreter discovery, should be safe because _get_task_vars + # is always called before running interpreter discovery + self._action = f_self + task_vars = f_locals.get('task_vars') + if task_vars: + LOG.debug('recovered task_vars from Action') + return task_vars + elif f.f_code.co_name == '_execute_meta': + f_all_vars = f.f_locals.get('all_vars') + if isinstance(f_all_vars, dict): + LOG.debug('recovered task_vars from meta:') + return f_all_vars + + f = f.f_back + + raise ansible.errors.AnsibleConnectionFailure(task_vars_msg) + + def get_host_vars(self, inventory_hostname): + """ + Fetch the HostVars for a host. + + :returns: + Variables dictionary or :data:`None`. + :raises ansible.errors.AnsibleConnectionFailure: + Task vars unavailable. + """ + task_vars = self._get_task_vars() + hostvars = task_vars.get('hostvars') + if hostvars: + return hostvars.get(inventory_hostname) + + raise ansible.errors.AnsibleConnectionFailure(task_vars_msg) + + 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. + """ + def _fetch_task_var(task_vars, key): + """ + Special helper func in case vars can be templated + """ + SPECIAL_TASK_VARS = [ + 'ansible_python_interpreter' + ] + if key in task_vars: + val = task_vars[key] + if '{' in str(val) and key in SPECIAL_TASK_VARS: + # template every time rather than storing in a cache + # in case a different template value is used in a different task + val = self.templar.template( + val, + preserve_trailing_newlines=True, + escape_backslashes=False + ) + return val + + task_vars = self._get_task_vars() + if self.delegate_to_hostname is None: + return _fetch_task_var(task_vars, key) + else: + delegated_vars = task_vars['ansible_delegated_vars'] + if self.delegate_to_hostname in delegated_vars: + task_vars = delegated_vars[self.delegate_to_hostname] + return _fetch_task_var(task_vars, key) + + return default + + @property + def homedir(self): + self._connect() + return self.init_child_result['home_dir'] + + def get_binding(self): + """ + Return the :class:`ansible_mitogen.process.Binding` representing the + process that hosts the physical connection and services (context + establishment, file transfer, ..) for our desired target. + """ + assert self.binding is not None + return self.binding + + @property + def connected(self): + """ + Ansible connection plugin property. Used by ansible-connection command. + """ + 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. + via_vars = self.get_host_vars(inventory_name) + if via_vars is None: + raise ansible.errors.AnsibleConnectionFailure( + self.unknown_via_msg % ( + via_spec, + proxied_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 + task_vars=self._get_task_vars(), # needed for interpreter discovery in parse_python_path + action=self._action, + 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 _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. + """ + spec = ansible_mitogen.transport_config.PlayContextSpec( + connection=self, + play_context=self._play_context, + transport=self.transport, + inventory_name=self.get_task_var('inventory_hostname'), + ) + stack = self._stack_from_spec(spec) + return spec.inventory_name(), stack + + 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 = mitogen.service.call( + call_context=self.binding.get_service_context(), + service_name='ansible_mitogen.services.ContextService', + method_name='get', + stack=ansible_mitogen.utils.unsafe.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. + + Ansible connection plugin method. + """ + # In some Ansible connection plugins this method returns self. + # However nothing I've found uses it, it's not even assigned. + if self.connected: + return + + inventory_name, stack = self._build_stack() + worker_model = ansible_mitogen.process.get_worker_model() + self.binding = worker_model.get_binding( + ansible_mitogen.utils.unsafe.cast(inventory_name) + ) + self._connect_stack(stack) + + def _put_connection(self): + """ + 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. + """ + if not self.context: + return + + self.chain.reset() + mitogen.service.call( + call_context=self.binding.get_service_context(), + service_name='ansible_mitogen.services.ContextService', + method_name='put', + context=self.context + ) + + self.context = None + self.login_context = None + self.init_child_result = None + self.chain = None + + 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. + + Ansible connection plugin method. + """ + self._put_connection() + if self.binding: + self.binding.close() + self.binding = None + + 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. + + Ansible connection plugin method. + """ + 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 + ) + + # Strategy's _execute_meta doesn't have an action obj but we'll need one for + # running interpreter_discovery + # will create a new temporary action obj for this purpose + self._action = ansible_mitogen.mixins.ActionModuleMixin( + task=0, + connection=self, + play_context=self._play_context, + loader=0, + templar=0, + shared_loader_obj=0 + ) + + # Clear out state in case we were ever connected. + self.close() + + inventory_name, stack = self._build_stack() + if self._play_context.become: + stack = stack[:-1] + + worker_model = ansible_mitogen.process.get_worker_model() + binding = worker_model.get_binding(inventory_name) + try: + mitogen.service.call( + call_context=binding.get_service_context(), + service_name='ansible_mitogen.services.ContextService', + method_name='reset', + stack=ansible_mitogen.utils.unsafe.cast(list(stack)), + ) + finally: + binding.close() + + # 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) + + Ansible connection plugin method. + """ + emulate_tty = (not in_data and sudoable) + rc, stdout, stderr = self.get_chain().call( + ansible_mitogen.target.exec_command, + cmd=ansible_mitogen.utils.unsafe.cast(cmd), + in_data=ansible_mitogen.utils.unsafe.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. + + Ansible connection plugin method. + """ + self._connect() + ansible_mitogen.target.transfer_file( + context=self.context, + # in_path may be AnsibleUnicode + in_path=ansible_mitogen.utils.unsafe.cast(in_path), + out_path=out_path + ) + + 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, + ansible_mitogen.utils.unsafe.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. + + Ansible connection plugin method. + """ + 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 as e: + 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() + mitogen.service.call( + call_context=self.binding.get_service_context(), + service_name='mitogen.service.FileService', + method_name='register', + path=ansible_mitogen.utils.unsafe.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.binding.get_child_service_context(), + in_path=in_path, + out_path=out_path + ) diff --git a/mitogen-0.3.9/ansible_mitogen/loaders.py b/mitogen-0.3.9/ansible_mitogen/loaders.py new file mode 100644 index 0000000..9d9876a --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/loaders.py @@ -0,0 +1,103 @@ +# 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, division, print_function +__metaclass__ = type + +import ansible.errors + +import ansible_mitogen.utils + +__all__ = [ + 'action_loader', + 'become_loader', + 'connection_loader', + 'module_loader', + 'module_utils_loader', + 'shell_loader', + 'strategy_loader', +] + + +ANSIBLE_VERSION_MIN = (2, 10) +ANSIBLE_VERSION_MAX = (2, 17) + +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/mitogen-hq/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_mitogen.utils.ansible_version + if v[:2] < ANSIBLE_VERSION_MIN: + raise ansible.errors.AnsibleError( + OLD_VERSION_MSG % (v, ANSIBLE_VERSION_MIN) + ) + + if v[:2] > ANSIBLE_VERSION_MAX: + raise ansible.errors.AnsibleError( + NEW_VERSION_MSG % (v, ANSIBLE_VERSION_MAX) + ) + + +# this is the first file our strategy plugins import, so we need to check this here +# in prior Ansible versions, connection_loader.get_with_context didn't exist, so if a user +# is trying to load an old Ansible version, we'll fail and error gracefully +assert_supported_release() + + +from ansible.plugins.loader import action_loader +from ansible.plugins.loader import become_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 + +# These are original, unwrapped implementations +action_loader__get = action_loader.get +connection_loader__get = connection_loader.get_with_context diff --git a/mitogen-0.3.9/ansible_mitogen/logging.py b/mitogen-0.3.9/ansible_mitogen/logging.py new file mode 100644 index 0000000..40b2b33 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/logging.py @@ -0,0 +1,130 @@ +# 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, division, print_function +__metaclass__ = type + +import logging +import os + +import mitogen.core +import mitogen.utils + +try: + from __main__ import display +except ImportError: + import ansible.utils.display + display = ansible.utils.display.Display() + + +#: The process name set via :func:`set_process_name`. +_process_name = None + +#: The PID of the process that last called :func:`set_process_name`, so its +#: value can be ignored in unknown fork children. +_process_pid = None + + +def set_process_name(name): + """ + Set a name to adorn log messages with. + """ + global _process_name + _process_name = name + + global _process_pid + _process_pid = os.getpid() + + +class Handler(logging.Handler): + """ + Use Mitogen's log format, but send the result to a Display method. + """ + def __init__(self, normal_method): + logging.Handler.__init__(self) + self.formatter = mitogen.utils.log_get_formatter() + self.normal_method = normal_method + + #: Set of target loggers that produce warnings and errors that spam the + #: console needlessly. Their log level is forced to INFO. A better strategy + #: may simply be to bury all target logs in DEBUG output, but not by + #: overriding their log level as done here. + NOISY_LOGGERS = frozenset([ + 'dnf', # issue #272; warns when a package is already installed. + 'boto', # issue #541; normal boto retry logic can cause ERROR logs. + ]) + + def emit(self, record): + mitogen_name = getattr(record, 'mitogen_name', '') + if mitogen_name == 'stderr': + record.levelno = logging.ERROR + if mitogen_name in self.NOISY_LOGGERS and record.levelno >= 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') + l_operon = logging.getLogger('operon') + + for logger in l_mitogen, l_mitogen_io, l_ansible_mitogen, l_operon: + 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.3.9/ansible_mitogen/mixins.py b/mitogen-0.3.9/ansible_mitogen/mixins.py new file mode 100644 index 0000000..0ba41aa --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/mixins.py @@ -0,0 +1,533 @@ +# 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, division, print_function +__metaclass__ = type + +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 ansible_mitogen.connection +import ansible_mitogen.planner +import ansible_mitogen.target +import ansible_mitogen.utils +import ansible_mitogen.utils.unsafe + +from ansible.module_utils._text import to_text + +try: + from ansible.utils.unsafe_proxy import wrap_var +except ImportError: + from ansible.vars.unsafe_proxy import wrap_var + +try: + # ansible 2.8 moved remove_internal_keys to the clean module + from ansible.vars.clean import remove_internal_keys +except ImportError: + try: + from ansible.vars.manager import remove_internal_keys + except ImportError: + # ansible 2.3.3 has remove_internal_keys as a protected func on the action class + # we'll fallback to calling self._remove_internal_keys in this case + remove_internal_keys = lambda a: "Not found" + + +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__ + + # required for python interpreter discovery + connection.templar = self._templar + self._finding_python_interpreter = False + self._rediscovered_python = False + # redeclaring interpreter discovery vars here in case running ansible < 2.8.0 + self._discovered_interpreter_key = None + self._discovered_interpreter = False + self._discovery_deprecation_warnings = [] + self._discovery_warnings = [] + + 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, + ansible_mitogen.utils.unsafe.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 _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_mitogen.utils.ansible_version[:2] >= (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, + ansible_mitogen.utils.unsafe.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 _set_temp_file_args(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_mitogen.utils.ansible_version[:2] >= (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_mitogen.utils.ansible_version[:2] >= (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, + ignore_unknown_opts=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 = {} + + if ansible_mitogen.utils.ansible_version[:2] >= (2, 17): + self._update_module_args( + module_name, module_args, task_vars, + ignore_unknown_opts=ignore_unknown_opts, + ) + else: + self._update_module_args(module_name, module_args, task_vars) + env = {} + self._compute_environment_string(env) + self._set_temp_file_args(module_args, wrap_async) + + # there's a case where if a task shuts down the node and then immediately calls + # wait_for_connection, the `ping` test from Ansible won't pass because we lost connection + # clearing out context forces a reconnect + # see https://github.com/dw/mitogen/issues/655 and Ansible's `wait_for_connection` module for more info + if module_name == 'ansible.legacy.ping' and type(self).__name__ == 'wait_for_connection': + self._connection.context = None + + self._connection._connect() + result = ansible_mitogen.planner.invoke( + ansible_mitogen.planner.Invocation( + action=self, + connection=self._connection, + module_name=ansible_mitogen.utils.unsafe.cast(mitogen.core.to_text(module_name)), + module_args=ansible_mitogen.utils.unsafe.cast(module_args), + task_vars=task_vars, + templar=self._templar, + env=ansible_mitogen.utils.unsafe.cast(env), + wrap_async=wrap_async, + timeout_secs=self.get_task_timeout_secs(), + ) + ) + + if tmp and delete_remote_tmp and ansible_mitogen.utils.ansible_version[:2] < (2, 5): + # Built-in actions expected tmpdir to be cleaned up automatically + # on _execute_module(). + self._remove_tmp_path(tmp) + + # prevents things like discovered_interpreter_* or ansible_discovered_interpreter_* from being set + # handle ansible 2.3.3 that has remove_internal_keys in a different place + check = remove_internal_keys(result) + if check == 'Not found': + self._remove_internal_keys(result) + + # taken from _execute_module of ansible 2.8.6 + # propagate interpreter discovery results back to the controller + if self._discovered_interpreter_key: + if result.get('ansible_facts') is None: + result['ansible_facts'] = {} + + # only cache discovered_interpreter if we're not running a rediscovery + # rediscovery happens in places like docker connections that could have different + # python interpreters than the main host + if not self._rediscovered_python: + result['ansible_facts'][self._discovered_interpreter_key] = self._discovered_interpreter + + if self._discovery_warnings: + if result.get('warnings') is None: + result['warnings'] = [] + result['warnings'].extend(self._discovery_warnings) + + if self._discovery_deprecation_warnings: + if result.get('deprecations') is None: + result['deprecations'] = [] + result['deprecations'].extend(self._discovery_deprecation_warnings) + + return wrap_var(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) + + # TODO: HACK: if finding python interpreter then we need to keep + # calling exec_command until we run into the right python we'll use + # chicken-and-egg issue, mitogen needs a python to run low_level_execute_command + # which is required by Ansible's discover_interpreter function + if self._finding_python_interpreter: + possible_pythons = [ + '/usr/bin/python', + 'python3', + 'python3.7', + 'python3.6', + 'python3.5', + 'python2.7', + 'python2.6', + '/usr/libexec/platform-python', + '/usr/bin/python3', + 'python' + ] + else: + # not used, just adding a filler value + possible_pythons = ['python'] + + def _run_cmd(): + return self._connection.exec_command( + cmd=cmd, + in_data=in_data, + sudoable=sudoable, + mitogen_chdir=chdir, + ) + + for possible_python in possible_pythons: + try: + self._possible_python_interpreter = possible_python + rc, stdout, stderr = _run_cmd() + # TODO: what exception is thrown? + except: + # we've reached the last python attempted and failed + # TODO: could use enumerate(), need to check which version of python first had it though + if possible_python == 'python': + raise + else: + continue + + 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.3.9/ansible_mitogen/module_finder.py b/mitogen-0.3.9/ansible_mitogen/module_finder.py new file mode 100644 index 0000000..a187083 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/module_finder.py @@ -0,0 +1,281 @@ +# 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, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +import collections +import logging +import os +import re +import sys + +try: + # Python >= 3.4, PEP 451 ModuleSpec API + import importlib.machinery + import importlib.util +except ImportError: + # Python < 3.4, PEP 302 Import Hooks + import imp + +import mitogen.master + + +LOG = logging.getLogger(__name__) +PREFIX = 'ansible.module_utils.' + + +# Analog of `importlib.machinery.ModuleSpec` or `pkgutil.ModuleInfo`. +# name Unqualified name of the module. +# path Filesystem path of the module. +# kind One of the constants in `imp`, as returned in `imp.find_module()` +# parent `ansible_mitogen.module_finder.Module` of parent package (if any). +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, 'rb') + 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): + """Return an iterator of (level, name) for explicit imports in a code + object. + + Not all names identify a module. `from os import name, path` generates + `(0, 'os.name'), (0, 'os.path')`, but `os.name` is usually a string. + + >>> src = 'import a; import b.c; from d.e import f; from g import h, i\\n' + >>> code = compile(src, '', 'exec') + >>> list(scan_fromlist(code)) + [(0, 'a'), (0, 'b.c'), (0, 'd.e.f'), (0, 'g.h'), (0, 'g.i')] + """ + for level, modname_s, fromlist in mitogen.master.scan_code_imports(code): + for name in fromlist: + yield level, str('%s.%s' % (modname_s, name)) + if not fromlist: + yield level, modname_s + + +def walk_imports(code, prefix=None): + """Return an iterator of names for implicit parent imports & explicit + imports in a code object. + + If a prefix is provided, then only children of that prefix are included. + Not all names identify a module. `from os import name, path` generates + `'os', 'os.name', 'os.path'`, but `os.name` is usually a string. + + >>> source = 'import a; import b; import b.c; from b.d import e, f\\n' + >>> code = compile(source, '', 'exec') + >>> list(walk_imports(code)) + ['a', 'b', 'b', 'b.c', 'b', 'b.d', 'b.d.e', 'b.d.f'] + >>> list(walk_imports(code, prefix='b')) + ['b.c', 'b.d', 'b.d.e', 'b.d.f'] + """ + if prefix is None: + prefix = '' + pattern = re.compile(r'(^|\.)(\w+)') + start = len(prefix) + for _, name, fromlist in mitogen.master.scan_code_imports(code): + if not name.startswith(prefix): + continue + for match in pattern.finditer(name, start): + yield name[:match.end()] + for leaf in fromlist: + yield str('%s.%s' % (name, leaf)) + + +def scan(module_name, module_path, search_path): + # type: (str, str, list[str]) -> list[(str, str, bool)] + """Return a list of (name, path, is_package) for ansible.module_utils + imports used by an Ansible module. + """ + log = LOG.getChild('scan') + log.debug('%r, %r, %r', module_name, module_path, search_path) + + if sys.version_info >= (3, 4): + result = _scan_importlib_find_spec( + module_name, module_path, search_path, + ) + log.debug('_scan_importlib_find_spec %r', result) + else: + result = _scan_imp_find_module(module_name, module_path, search_path) + log.debug('_scan_imp_find_module %r', result) + return result + + +def _scan_importlib_find_spec(module_name, module_path, search_path): + # type: (str, str, list[str]) -> list[(str, str, bool)] + module = importlib.machinery.ModuleSpec( + module_name, loader=None, origin=module_path, + ) + prefix = importlib.machinery.ModuleSpec( + PREFIX.rstrip('.'), loader=None, + ) + prefix.submodule_search_locations = search_path + queue = collections.deque([module]) + specs = {prefix.name: prefix} + while queue: + spec = queue.popleft() + if spec.origin is None: + continue + try: + with open(spec.origin, 'rb') as f: + code = compile(f.read(), spec.name, 'exec') + except Exception as exc: + raise ValueError((exc, module, spec, specs)) + + for name in walk_imports(code, prefix.name): + if name in specs: + continue + + parent_name = name.rpartition('.')[0] + parent = specs[parent_name] + if parent is None or not parent.submodule_search_locations: + specs[name] = None + continue + + child = importlib.util._find_spec( + name, parent.submodule_search_locations, + ) + if child is None or child.origin is None: + specs[name] = None + continue + + specs[name] = child + queue.append(child) + + del specs[prefix.name] + return sorted( + (spec.name, spec.origin, spec.submodule_search_locations is not None) + for spec in specs.values() if spec is not None + ) + + +def _scan_imp_find_module(module_name, module_path, search_path): + # type: (str, str, list[str]) -> list[(str, str, bool)] + 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.3.9/ansible_mitogen/parsing.py b/mitogen-0.3.9/ansible_mitogen/parsing.py new file mode 100644 index 0000000..2ca5031 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/parsing.py @@ -0,0 +1,77 @@ +# 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, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +import mitogen.core + + +def parse_script_interpreter(source): + """ + Parse the script interpreter portion of a UNIX hashbang using the rules + Linux uses. + + :param str source: String like "/usr/bin/env python". + + :returns: + Tuple of `(interpreter, arg)`, where `intepreter` is the script + interpreter and `arg` is its sole argument if present, otherwise + :py:data:`None`. + """ + # Find terminating newline. Assume last byte of binprm_buf if absent. + nl = source.find(b'\n', 0, 128) + if nl == -1: + nl = min(128, len(source)) + + # Split once on the first run of whitespace. If no whitespace exists, + # bits just contains the interpreter filename. + bits = source[0:nl].strip().split(None, 1) + if len(bits) == 1: + return mitogen.core.to_text(bits[0]), None + return mitogen.core.to_text(bits[0]), mitogen.core.to_text(bits[1]) + + +def parse_hashbang(source): + """ + Parse a UNIX "hashbang line" using the syntax supported by Linux. + + :param str source: String like "#!/usr/bin/env python". + + :returns: + Tuple of `(interpreter, arg)`, where `intepreter` is the script + interpreter and `arg` is its sole argument if present, otherwise + :py:data:`None`. + """ + # Linux requires first 2 bytes with no whitespace, pretty sure it's the + # same everywhere. See binfmt_script.c. + if not source.startswith(b'#!'): + return None, None + + return parse_script_interpreter(source[2:]) diff --git a/mitogen-0.3.9/ansible_mitogen/planner.py b/mitogen-0.3.9/ansible_mitogen/planner.py new file mode 100644 index 0000000..0a91039 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/planner.py @@ -0,0 +1,631 @@ +# 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. + +""" +Classes to detect each case from [0] and prepare arguments necessary for the +corresponding Runner class within the target, including preloading requisite +files/modules known missing. + +[0] "Ansible Module Architecture", developing_program_flow_modules.html +""" + +from __future__ import absolute_import, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +import json +import logging +import os +import random +import re + +import ansible.collections.list +import ansible.errors +import ansible.executor.module_common + +import mitogen.core +import mitogen.select + +import ansible_mitogen.loaders +import ansible_mitogen.parsing +import ansible_mitogen.target +import ansible_mitogen.utils.unsafe + + +LOG = logging.getLogger(__name__) +NO_METHOD_MSG = 'Mitogen: no invocation method found for: ' +NO_INTERPRETER_MSG = 'module (%s) is missing interpreter line' +# NOTE: Ansible 2.10 no longer has a `.` at the end of NO_MODULE_MSG error +NO_MODULE_MSG = 'The module %s was not found in configured module paths' + +_planner_by_path = {} + + +class Invocation(object): + """ + Collect up a module's execution environment then use it to invoke + target.run_module() or helpers.run_module_async() in the target context. + """ + def __init__(self, action, connection, module_name, module_args, + task_vars, templar, env, wrap_async, timeout_secs): + #: ActionBase instance invoking the module. Required to access some + #: output postprocessing methods that don't belong in ActionBase at + #: all. + self.action = action + #: Ansible connection to use to contact the target. Must be an + #: ansible_mitogen connection. + self.connection = connection + #: Name of the module ('command', 'shell', etc.) to execute. + self.module_name = module_name + #: Final module arguments. + self.module_args = module_args + #: Task variables, needed to extract ansible_*_interpreter. + self.task_vars = task_vars + #: Templar, needed to extract ansible_*_interpreter. + self.templar = templar + #: Final module environment. + self.env = env + #: Boolean, if :py:data:`True`, launch the module asynchronously. + self.wrap_async = wrap_async + #: Integer, if >0, 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 + #: Initially ``{}``, but set by :func:`invoke`. Optional source to send + #: to :func:`propagate_paths_and_modules` to fix Python3.5 relative import errors + self._overridden_sources = {} + #: Initially ``set()``, but set by :func:`invoke`. Optional source paths to send + #: to :func:`propagate_paths_and_modules` to handle loading source dependencies from + #: places outside of the main source path, such as collections + self._extra_sys_paths = set() + + def get_module_source(self): + if self._module_source is None: + self._module_source = read_file(self.module_path) + return self._module_source + + 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 + + @classmethod + def detect(cls, path, source): + """ + 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`. + } + """ + binding = self._inv.connection.get_binding() + + 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', binding.get_child_service_context()) + 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' + + @classmethod + def detect(cls, path, source): + return ansible.executor.module_common._is_binary(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 interpreter path (from the script's hashbang line), return + the desired interpreter path. This tries, in order + + 1. Look up & render the `ansible_*_interpreter` variable, if set + 2. Look up the `discovered_interpreter_*` fact, if present + 3. The unmodified path from the hashbang line. + + :param str path: + Absolute path to original interpreter (e.g. '/usr/bin/python'). + + :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. + """ + interpreter_name = os.path.basename(path).strip() + key = u'ansible_%s_interpreter' % interpreter_name + try: + template = self._inv.task_vars[key] + except KeyError: + pass + else: + configured_interpreter = self._inv.templar.template(template) + return ansible_mitogen.utils.unsafe.cast(configured_interpreter) + + key = u'discovered_interpreter_%s' % interpreter_name + try: + discovered_interpreter = self._inv.task_vars['ansible_facts'][key] + except KeyError: + pass + else: + return ansible_mitogen.utils.unsafe.cast(discovered_interpreter) + + return path + + def _get_interpreter(self): + path, arg = ansible_mitogen.parsing.parse_hashbang( + self._inv.get_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 + + is_python = path.startswith('python') + return fragment, is_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' + + @classmethod + def detect(cls, path, source): + return ansible.executor.module_common.REPLACER_JSONARGS in 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' + + @classmethod + def detect(cls, path, source): + return b'WANT_JSON' in 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 = re.compile(br'from ansible(?:_collections|\.module_utils)\.') + + @classmethod + def detect(cls, path, source): + return cls.MARKER.search(source) is not None + + 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 + 'ansible.legacy.dnf', # issue #776 + 'ansible.builtin.dnf', # issue #832 + ]) + + 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 + ) + ) + + _module_map = None + + def get_module_map(self): + if self._module_map is None: + binding = self._inv.connection.get_binding() + self._module_map = mitogen.service.call( + call_context=binding.get_service_context(), + 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=ansible.executor.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(), + py_module_name=py_modname_from_path( + self._inv.module_name, + self._inv.module_path, + ), + ) + + +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' + + @classmethod + def detect(cls, path, source): + return ansible.executor.module_common.REPLACER in source + + +class OldStylePlanner(ScriptPlanner): + runner_name = 'OldStyleRunner' + + @classmethod + def detect(cls, path, source): + # Everything else. + return True + + +_planners = [ + BinaryPlanner, + # ReplacerPlanner, + NewStylePlanner, + JsonArgsPlanner, + WantJsonPlanner, + OldStylePlanner, +] + + +def py_modname_from_path(name, path): + """ + Fetch the logical name of a new-style module as it might appear in + :data:`sys.modules` of the target's Python interpreter. + + * Since Ansible 2.9, modules appearing within a package have the original + package hierarchy approximated on the target, enabling relative imports + to function correctly. For example, "ansible.modules.system.setup". + """ + try: + return ansible.executor.module_common._get_ansible_module_fqn(path) + except AttributeError: + pass + except ValueError: + pass + + return 'ansible.modules.' + name + + +def read_file(path): + fd = os.open(path, os.O_RDONLY) + try: + bits = [] + chunk = True + while True: + chunk = os.read(fd, 65536) + if not chunk: + break + bits.append(chunk) + finally: + os.close(fd) + + return mitogen.core.b('').join(bits) + + +def _propagate_deps(invocation, planner, context): + binding = invocation.connection.get_binding() + mitogen.service.call( + call_context=binding.get_service_context(), + service_name='mitogen.service.PushFileService', + method_name='propagate_paths_and_modules', + + context=context, + paths=planner.get_push_files(), + # modules=planner.get_module_deps(), TODO + overridden_sources=invocation._overridden_sources, + # needs to be a list because can't unpickle() a set() + extra_sys_paths=list(invocation._extra_sys_paths), + ) + + +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, source): + for klass in _planners: + if klass.detect(invocation.module_path, source): + LOG.debug( + '%r accepted %r (filename %r)', + klass, invocation.module_name, invocation.module_path, + ) + return klass + LOG.debug('%r rejected %r', klass, invocation.module_name) + raise ansible.errors.AnsibleError(NO_METHOD_MSG + repr(invocation)) + + +def _fix_py35(invocation, module_source): + """ + super edge case with a relative import error in Python 3.5.1-3.5.3 + in Ansible's setup module when using Mitogen + https://github.com/dw/mitogen/issues/672#issuecomment-636408833 + We replace a relative import in the setup module with the actual full file path + This works in vanilla Ansible but not in Mitogen otherwise + """ + if invocation.module_name in {'ansible.builtin.setup', 'ansible.legacy.setup', 'setup'} and \ + invocation.module_path not in invocation._overridden_sources: + # in-memory replacement of setup module's relative import + # would check for just python3.5 and run this then but we don't know the + # target python at this time yet + # NOTE: another ansible 2.10-specific fix: `from ..module_utils` used to be `from ...module_utils` + module_source = module_source.replace( + b"from ..module_utils.basic import AnsibleModule", + b"from ansible.module_utils.basic import AnsibleModule" + ) + invocation._overridden_sources[invocation.module_path] = module_source + + +def _load_collections(invocation): + """ + Special loader that ensures that `ansible_collections` exist as a module path for import + Goes through all collection path possibilities and stores paths to installed collections + Stores them on the current invocation to later be passed to the master service + """ + for collection_path in ansible.collections.list.list_collection_dirs(): + invocation._extra_sys_paths.add(collection_path.decode('utf-8')) + + +def invoke(invocation): + """ + Find a Planner subclass corresponding to `invocation` and use it to invoke + the module. + + :param Invocation invocation: + :returns: + Module return dict. + :raises ansible.errors.AnsibleError: + Unrecognized/unsupported module type. + """ + path = ansible_mitogen.loaders.module_loader.find_plugin( + invocation.module_name, + '', + ) + if path is None: + raise ansible.errors.AnsibleError(NO_MODULE_MSG % ( + invocation.module_name, + )) + + invocation.module_path = mitogen.core.to_text(path) + if invocation.module_path not in _planner_by_path: + if 'ansible_collections' in invocation.module_path: + _load_collections(invocation) + + module_source = invocation.get_module_source() + _fix_py35(invocation, module_source) + _planner_by_path[invocation.module_path] = _get_planner( + invocation, + module_source + ) + + planner = _planner_by_path[invocation.module_path](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.3.9/ansible_mitogen/plugins/__init__.py b/mitogen-0.3.9/ansible_mitogen/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/action/__init__.py b/mitogen-0.3.9/ansible_mitogen/plugins/action/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_fetch.py b/mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_fetch.py new file mode 100644 index 0000000..c1ef190 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_fetch.py @@ -0,0 +1,207 @@ +# (c) 2012-2014, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import base64 +from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleActionSkip +from ansible.module_utils.common.text.converters import to_bytes, to_text +from ansible.module_utils.six import string_types +from ansible.module_utils.parsing.convert_bool import boolean +from ansible.plugins.action import ActionBase +from ansible.utils.display import Display +from ansible.utils.hashing import checksum, checksum_s, md5, secure_hash +from ansible.utils.path import makedirs_safe, is_subpath + +display = Display() + + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + ''' handler for fetch operations ''' + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(tmp, task_vars) + del tmp # tmp no longer has any effect + + try: + if self._play_context.check_mode: + raise AnsibleActionSkip('check mode not (yet) supported for this module') + + source = self._task.args.get('src', None) + original_dest = dest = self._task.args.get('dest', None) + flat = boolean(self._task.args.get('flat'), strict=False) + fail_on_missing = boolean(self._task.args.get('fail_on_missing', True), strict=False) + validate_checksum = boolean(self._task.args.get('validate_checksum', True), strict=False) + + msg = '' + # validate source and dest are strings FIXME: use basic.py and module specs + if not isinstance(source, string_types): + msg = "Invalid type supplied for source option, it must be a string" + + if not isinstance(dest, string_types): + msg = "Invalid type supplied for dest option, it must be a string" + + if source is None or dest is None: + msg = "src and dest are required" + + if msg: + raise AnsibleActionFail(msg) + + source = self._connection._shell.join_path(source) + source = self._remote_expand_user(source) + + remote_stat = {} + remote_checksum = None + if True: + # Get checksum for the remote file even using become. Mitogen doesn't need slurp. + # Follow symlinks because fetch always follows symlinks + try: + remote_stat = self._execute_remote_stat(source, all_vars=task_vars, follow=True) + except AnsibleError as ae: + result['changed'] = False + result['file'] = source + if fail_on_missing: + result['failed'] = True + result['msg'] = to_text(ae) + else: + result['msg'] = "%s, ignored" % to_text(ae, errors='surrogate_or_replace') + + return result + + remote_checksum = remote_stat.get('checksum') + if remote_stat.get('exists'): + if remote_stat.get('isdir'): + result['failed'] = True + result['changed'] = False + result['msg'] = "remote file is a directory, fetch cannot work on directories" + + # Historically, these don't fail because you may want to transfer + # a log file that possibly MAY exist but keep going to fetch other + # log files. Today, this is better achieved by adding + # ignore_errors or failed_when to the task. Control the behaviour + # via fail_when_missing + if not fail_on_missing: + result['msg'] += ", not transferring, ignored" + del result['changed'] + del result['failed'] + + return result + + # use slurp if permissions are lacking or privilege escalation is needed + remote_data = None + if remote_checksum in (None, '1', ''): + slurpres = self._execute_module(module_name='ansible.legacy.slurp', module_args=dict(src=source), task_vars=task_vars) + if slurpres.get('failed'): + if not fail_on_missing: + result['file'] = source + result['changed'] = False + else: + result.update(slurpres) + + if 'not found' in slurpres.get('msg', ''): + result['msg'] = "the remote file does not exist, not transferring, ignored" + elif slurpres.get('msg', '').startswith('source is a directory'): + result['msg'] = "remote file is a directory, fetch cannot work on directories" + + return result + else: + if slurpres['encoding'] == 'base64': + remote_data = base64.b64decode(slurpres['content']) + if remote_data is not None: + remote_checksum = checksum_s(remote_data) + + # calculate the destination name + if os.path.sep not in self._connection._shell.join_path('a', ''): + source = self._connection._shell._unquote(source) + source_local = source.replace('\\', '/') + else: + source_local = source + + # ensure we only use file name, avoid relative paths + if not is_subpath(dest, original_dest): + # TODO: ? dest = os.path.expanduser(dest.replace(('../',''))) + raise AnsibleActionFail("Detected directory traversal, expected to be contained in '%s' but got '%s'" % (original_dest, dest)) + + if flat: + if os.path.isdir(to_bytes(dest, errors='surrogate_or_strict')) and not dest.endswith(os.sep): + raise AnsibleActionFail("dest is an existing directory, use a trailing slash if you want to fetch src into that directory") + if dest.endswith(os.sep): + # if the path ends with "/", we'll use the source filename as the + # destination filename + base = os.path.basename(source_local) + dest = os.path.join(dest, base) + if not dest.startswith("/"): + # if dest does not start with "/", we'll assume a relative path + dest = self._loader.path_dwim(dest) + else: + # files are saved in dest dir, with a subdir for each host, then the filename + if 'inventory_hostname' in task_vars: + target_name = task_vars['inventory_hostname'] + else: + target_name = self._play_context.remote_addr + dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local) + + dest = os.path.normpath(dest) + + # calculate checksum for the local file + local_checksum = checksum(dest) + + if remote_checksum != local_checksum: + # create the containing directories, if needed + makedirs_safe(os.path.dirname(dest)) + + # fetch the file and check for changes + if remote_data is None: + self._connection.fetch_file(source, dest) + else: + try: + f = open(to_bytes(dest, errors='surrogate_or_strict'), 'wb') + f.write(remote_data) + f.close() + except (IOError, OSError) as e: + raise AnsibleActionFail("Failed to fetch the file: %s" % e) + new_checksum = secure_hash(dest) + # For backwards compatibility. We'll return None on FIPS enabled systems + try: + new_md5 = md5(dest) + except ValueError: + new_md5 = None + + if validate_checksum and new_checksum != remote_checksum: + result.update(dict(failed=True, md5sum=new_md5, + msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None, + checksum=new_checksum, remote_checksum=remote_checksum)) + else: + result.update({'changed': True, 'md5sum': new_md5, 'dest': dest, + 'remote_md5sum': None, 'checksum': new_checksum, + 'remote_checksum': remote_checksum}) + else: + # For backwards compatibility. We'll return None on FIPS enabled systems + try: + local_md5 = md5(dest) + except ValueError: + local_md5 = None + result.update(dict(changed=False, md5sum=local_md5, file=source, dest=dest, checksum=local_checksum)) + + finally: + self._remove_tmp_path(self._connection._shell.tmpdir) + + return result diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_get_stack.py b/mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_get_stack.py new file mode 100644 index 0000000..a8634e5 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/action/mitogen_get_stack.py @@ -0,0 +1,58 @@ +# 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. + +""" +Fetch the connection configuration stack that would be used to connect to a +target, without actually connecting to it. +""" + +from __future__ import absolute_import, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +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, + } + + _, stack = self._connection._build_stack() + return { + 'changed': True, + 'result': stack, + '_ansible_verbose_always': True, + # for ansible < 2.8, we'll default to /usr/bin/python like before + 'discovered_interpreter': self._connection._action._discovered_interpreter + } diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/__init__.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/__pycache__/mitogen_local.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/plugins/connection/__pycache__/mitogen_local.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e9ebd47fe097fc7bda3e90caeeb6e5392184eac GIT binary patch literal 2030 zcmah~UuzsU5SO%jckA={PntA#+yqqml7r-~5GZX3Atfm#G&P~24c$`qSa`{b`sXz57jb8Lswy^}OMBaMEV`Hi+*T51z~zPkC_ z*8L74f4Y~m59-JRY}N1BNg`=Z3L4Xb#jFV8fD#Df2I^Vfj9Y$;;^?61rG>ctoG2a= zSlqu$iTgoo)cJJ@mXBz>0ujoqa;CfFE$_jK435|lJz|vnu<|`obmDepIr{SB<+zRO zuE=mV)KB33(bafOEqP4?HQ%CU4K<-`%GPTVugOR*;JE%Jk!`v7n#e_HkFJe2UIp=n zWY0*qbByOA-N3Ght*Xpx14&klOKofANTI`KAW486K#K-*V~z@aJ5q4!Oj;Xd6|m5H{Q3= zy<8MqQry4eKsj^~(Mvd_PM$wa6hdqs`5p>0+iVN1NmG z4VYRhF1XB6!!Z;ZDo`d<+uZB`YKKG6pi-##7MKZuI?<=5;sXrifD0wPtAT>apvNCU z4hAIOh5d?)@fd{W0wt&5lg#XR2YVu~f!~$Fh`WQ$Qo-OB7gFN7xw~{nsO|stgL`uC ztLHIncA1U#GMKpCZRifwZQ1~F+5o582&Kf^B7B`mL0KCcpwcsC*17Vu3vPMhLhuQ` zR_oYB%?=G|hc07lV_U_>ehR6+ipCV%xyL2`0k(=`2PCGF#7xpSkW2Aswo0(qTmi3((<-%uM?kPk1OYGoVn zP%spAZjy8&pWtdw*CspIcnhF+VcI)LPFZ{f9XR%m*eDo}DUN5~ z36)EmyLyJ61o=3Inv;!`yHTZ%OzWKZ*-|BN$E-bB7e zNuopnNn+bcQk1gJo!m*17q!SwEDJ^>es`nl>u&pxPBENEqu0>zc#a?57X#qbJE&Ou$+2N?nlwG^H&pcb5 zJ<#rZl4NRF>J^XOmAGC;ahB@#8B8u7XENra<;y*~Hw*QBS7X&p?5Y0NOXcg9Ehr=tK9`EvF)Kns~sdPckHB-o!G&4 zQc~@0_X=Nt3q?qXujDJ@z*j&5@$4jRwyK2o0-lUBetz%w{`kFT)oP^(#?v3_z28?6 z`pqwv2hh+iyyhpEIO4cML!9Gblt;r_Uc*Slc^&9c)ySLSKgknb%U8}CVU_0TV zL&m0*eZnL;7zyc7imDXSdIqaiRS8axr^KF!1vKd?eSt>EJ!#70N)Yj_74WHKM)oXW zMIlDk5COJLi3%16kO??lRg8dNWK@m&rCh?AOJ#usk{*sS&IRw1J0D~p-3c2rG}FBh z1T4yQe~IE9wbPO45@VN*C1k?%6~0+eBpZ6}Ino8R-}Gaf%oy0rTYoG;<-am6o#V>_ z9-&afq4)5=w|zKF`k=uvk9h67C~t~{$GrXmJ;(V9Z}_I+(s_A83cDJF=%|fdT2#y!s$?~N zNmm5$Ue|%wwE>ew13ZYHp%OjAKjZH)AmX!6K2`dZX-;UN1%oy;FHt&T_T=3*u_{y; zG^a`op@f-l6QqL z+bKGQ;-aT2E_7#J65CpgRA-*<+wW%|W*;xM=$zV_6}$oIMWb5?_<{l(^$I9CgxC0x zY{X~pTt+dw&d&XI>JmzQaZ>71N{5P%EC0Pp={I9m&39HHJ*5dD5Rs*+1#BV0Uw8*@!jzyGH*f?`d`9pS*GLTGD<5~|6?tl5{;r`LR zqrHQD?Q_Q6*bCRvL#>r|ao}(AQu_$_81>2!m*kikL4ocXr8EMru_oMWfjb_kQo6UU z#3#Fqio11nZxOomk|{X36c(R1+Q-HvJG0Zdi(zGM!?gK!jFTwAaTMKN#r`2aV1Fg` I1ixAP2R5=?wEzGB literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_buildah.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_buildah.py new file mode 100644 index 0000000..10ab6b4 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_buildah.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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 = 'buildah' diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_doas.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_doas.py new file mode 100644 index 0000000..963ec59 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_doas.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_docker.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_docker.py new file mode 100644 index 0000000..e0dd212 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_docker.py @@ -0,0 +1,53 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_jail.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_jail.py new file mode 100644 index 0000000..a432e17 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_jail.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_kubectl.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_kubectl.py new file mode 100644 index 0000000..bae4160 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_kubectl.py @@ -0,0 +1,82 @@ +# 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, division, print_function +__metaclass__ = type + +import os.path +import sys + +import ansible.errors + +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 +import ansible_mitogen.loaders + + +_get_result = ansible_mitogen.loaders.connection_loader__get( + 'kubectl', + class_only=True, +) + + +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 not _get_result: + raise ansible.errors.AnsibleConnectionFailure(self.not_supported_msg) + super(Connection, self).__init__(*args, **kwargs) + + def get_extra_args(self): + try: + # Ansible < 2.10, _get_result is the connection class + connection_options = _get_result.connection_options + except AttributeError: + # Ansible >= 2.10, _get_result is a get_with_context_result + connection_options = _get_result.object.connection_options + parameters = [] + for key in connection_options: + task_var_name = 'ansible_%s' % key + task_var = self.get_task_var(task_var_name) + if task_var is not None: + parameters += [connection_options[key], task_var] + + return parameters diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_local.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_local.py new file mode 100644 index 0000000..6ff8673 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_local.py @@ -0,0 +1,88 @@ +# 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, division, print_function +__metaclass__ = type + +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.cls_original_env, + new=os.environ, + ) diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxc.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxc.py new file mode 100644 index 0000000..8850b3d --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxc.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_lxd.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxd.py new file mode 100644 index 0000000..25370ef --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_lxd.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_machinectl.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_machinectl.py new file mode 100644 index 0000000..1f53879 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_machinectl.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_podman.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_podman.py new file mode 100644 index 0000000..e423aac --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_podman.py @@ -0,0 +1,46 @@ +# Copyright 2022, Mitogen contributers +# +# 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, division, print_function +__metaclass__ = type + +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 = 'podman' diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_setns.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_setns.py new file mode 100644 index 0000000..4d70892 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_setns.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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.3.9/ansible_mitogen/plugins/connection/mitogen_ssh.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_ssh.py new file mode 100644 index 0000000..75f2d42 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_ssh.py @@ -0,0 +1,87 @@ +# 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, division, print_function +__metaclass__ = type + +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: + ssh_args: + type: str + vars: + - name: ssh_args + - name: ansible_ssh_args + - name: ansible_mitogen_ssh_args + ssh_common_args: + type: str + vars: + - name: ssh_args + - name: ansible_ssh_common_args + - name: ansible_mitogen_ssh_common_args + ssh_extra_args: + type: str + vars: + - name: ssh_args + - name: ansible_ssh_extra_args + - name: ansible_mitogen_ssh_extra_args +""" + +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 +import ansible_mitogen.loaders + + +class Connection(ansible_mitogen.connection.Connection): + transport = 'ssh' + vanilla_class = ansible_mitogen.loaders.connection_loader__get( + 'ssh', + class_only=True, + ) + + @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.3.9/ansible_mitogen/plugins/connection/mitogen_su.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_su.py new file mode 100644 index 0000000..9395cc5 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_su.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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_su' diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_sudo.py b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_sudo.py new file mode 100644 index 0000000..ef54942 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/connection/mitogen_sudo.py @@ -0,0 +1,46 @@ +# 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, division, print_function +__metaclass__ = type + +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_sudo' diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/strategy/__init__.py b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/strategy/__pycache__/mitogen_linear.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/__pycache__/mitogen_linear.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8a8e2b33c8ec214ad16c735667a50654f93ce73 GIT binary patch literal 843 zcmZ8eO>WdM7`2^r;!ZnLrj)i+9DrFgL1Kl3pj04)5M3~=ys^@;XK>`iAxO>D%)b5l~8D*Q7!xpz%+?Jy?7QvdVF&oRVq}k>Lt`fgr?g%8u zYl8jlMBiYOcHd(p9q z3cZ^9JFhPc$qBr68$cq41!6>C_&;&uSzV>IT&`Xj(P()FTqx&7noG{zkn_APL%hxT zyC&6L=PRnV;I0;a@+2?nWS-28kPD-Qtdg{_>Rij?N|q+cRc)5CIKCI(k010cepO9M z-7HmM6MMy@Kh8UCak;9zn~y#J5P+f(x0UIU=eKC;rUI@N@q7Xg{*(*rRVm#F1jst= zX2V@%Y+R=GTSts_fn8Y_wrK>@Z-X#1s&J7|mDlMfu~6Dif}TE^J>ySbymkX1E5V?H zD~-L7Seq22Td=Yd-#6Uhe}%Z0>Y~5j^d;U^T(Fe23r|xEB>J8k&3doYdsVbU_-8{G jd50Z+*tzz|8*h&Q_U?q35{d_tJskcXhT}Hd4UWhk#n1IA literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen.py b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen.py new file mode 100644 index 0000000..abbe767 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen.py @@ -0,0 +1,63 @@ +# 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, division, print_function +__metaclass__ = type + +import os.path +import sys + +# +# This is not the real Strategy implementation module, it simply exists as a +# proxy to the real module, which is loaded using Python's regular import +# mechanism, to prevent Ansible's PluginLoader from making up a fake name that +# results in ansible_mitogen plugin modules being loaded twice: once by +# PluginLoader with a name like "ansible.plugins.strategy.mitogen", which is +# stuffed into sys.modules even though attempting to import it will trigger an +# ImportError, and once under its canonical name, "ansible_mitogen.strategy". +# +# Therefore we have a proxy module that imports it under the real name, and +# sets up the duff PluginLoader-imported module to just contain objects from +# the real module, so duplicate types don't exist in memory, and things like +# debuggers and isinstance() work predictably. +# + +BASE_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), '../../..') +) + +if BASE_DIR not in sys.path: + sys.path.insert(0, BASE_DIR) + +import ansible_mitogen.strategy +import ansible.plugins.strategy.linear + + +class StrategyModule(ansible_mitogen.strategy.StrategyMixin, + ansible.plugins.strategy.linear.StrategyModule): + pass diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_free.py b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_free.py new file mode 100644 index 0000000..4f4e1f8 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_free.py @@ -0,0 +1,64 @@ +# 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, division, print_function +__metaclass__ = type + +import os.path +import sys + +# +# This is not the real Strategy implementation module, it simply exists as a +# proxy to the real module, which is loaded using Python's regular import +# mechanism, to prevent Ansible's PluginLoader from making up a fake name that +# results in ansible_mitogen plugin modules being loaded twice: once by +# PluginLoader with a name like "ansible.plugins.strategy.mitogen", which is +# stuffed into sys.modules even though attempting to import it will trigger an +# ImportError, and once under its canonical name, "ansible_mitogen.strategy". +# +# Therefore we have a proxy module that imports it under the real name, and +# sets up the duff PluginLoader-imported module to just contain objects from +# the real module, so duplicate types don't exist in memory, and things like +# debuggers and isinstance() work predictably. +# + +BASE_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), '../../..') +) + +if BASE_DIR not in sys.path: + sys.path.insert(0, BASE_DIR) + +import ansible_mitogen.loaders +import ansible_mitogen.strategy + + +Base = ansible_mitogen.loaders.strategy_loader.get('free', class_only=True) + +class StrategyModule(ansible_mitogen.strategy.StrategyMixin, Base): + pass diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py new file mode 100644 index 0000000..c3396c5 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py @@ -0,0 +1,69 @@ +# 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, division, print_function +__metaclass__ = type + +import os.path +import sys + +# +# This is not the real Strategy implementation module, it simply exists as a +# proxy to the real module, which is loaded using Python's regular import +# mechanism, to prevent Ansible's PluginLoader from making up a fake name that +# results in ansible_mitogen plugin modules being loaded twice: once by +# PluginLoader with a name like "ansible.plugins.strategy.mitogen", which is +# stuffed into sys.modules even though attempting to import it will trigger an +# ImportError, and once under its canonical name, "ansible_mitogen.strategy". +# +# Therefore we have a proxy module that imports it under the real name, and +# sets up the duff PluginLoader-imported module to just contain objects from +# the real module, so duplicate types don't exist in memory, and things like +# debuggers and isinstance() work predictably. +# + +BASE_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), '../../..') +) + +if BASE_DIR not in sys.path: + sys.path.insert(0, BASE_DIR) + +import ansible_mitogen.loaders +import ansible_mitogen.strategy + + +Base = ansible_mitogen.loaders.strategy_loader.get('host_pinned', class_only=True) + +if Base is None: + raise ImportError( + 'The host_pinned strategy is only available in Ansible 2.7 or newer.' + ) + +class StrategyModule(ansible_mitogen.strategy.StrategyMixin, Base): + pass diff --git a/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_linear.py b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_linear.py new file mode 100644 index 0000000..b1b03ae --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/plugins/strategy/mitogen_linear.py @@ -0,0 +1,64 @@ +# 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, division, print_function +__metaclass__ = type + +import os.path +import sys + +# +# This is not the real Strategy implementation module, it simply exists as a +# proxy to the real module, which is loaded using Python's regular import +# mechanism, to prevent Ansible's PluginLoader from making up a fake name that +# results in ansible_mitogen plugin modules being loaded twice: once by +# PluginLoader with a name like "ansible.plugins.strategy.mitogen", which is +# stuffed into sys.modules even though attempting to import it will trigger an +# ImportError, and once under its canonical name, "ansible_mitogen.strategy". +# +# Therefore we have a proxy module that imports it under the real name, and +# sets up the duff PluginLoader-imported module to just contain objects from +# the real module, so duplicate types don't exist in memory, and things like +# debuggers and isinstance() work predictably. +# + +BASE_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), '../../..') +) + +if BASE_DIR not in sys.path: + sys.path.insert(0, BASE_DIR) + +import ansible_mitogen.loaders +import ansible_mitogen.strategy + + +Base = ansible_mitogen.loaders.strategy_loader.get('linear', class_only=True) + +class StrategyModule(ansible_mitogen.strategy.StrategyMixin, Base): + pass diff --git a/mitogen-0.3.9/ansible_mitogen/process.py b/mitogen-0.3.9/ansible_mitogen/process.py new file mode 100644 index 0000000..3a41a43 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/process.py @@ -0,0 +1,710 @@ +# 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, division, print_function +__metaclass__ = type + +import atexit +import logging +import multiprocessing +import os +import resource +import socket +import signal +import sys + +try: + import faulthandler +except ImportError: + faulthandler = None + +try: + import setproctitle +except ImportError: + setproctitle = None + +import mitogen +import mitogen.core +import mitogen.debug +import mitogen.fork +import mitogen.master +import mitogen.parent +import mitogen.service +import mitogen.unix +import mitogen.utils + +import ansible +import ansible.constants as C +import ansible.errors +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" +) + +MAX_MESSAGE_SIZE = 4096 * 1048576 + +worker_model_msg = ( + 'Mitogen connection types may only be instantiated when one of the ' + '"mitogen_*" or "operon_*" strategies are active.' +) + +shutting_down_msg = ( + 'The task worker cannot connect. Ansible may be shutting down, or ' + 'the maximum open files limit may have been exceeded. If this occurs ' + 'midway through a run, please retry after increasing the open file ' + 'limit (ulimit -n). Original error: %s' +) + + +#: The worker model as configured by the currently running strategy. This is +#: managed via :func:`get_worker_model` / :func:`set_worker_model` functions by +#: :class:`StrategyMixin`. +_worker_model = None + + +#: A copy of the sole :class:`ClassicWorkerModel` that ever exists during a +#: classic run, as return by :func:`get_classic_worker_model`. +_classic_worker_model = None + + +def set_worker_model(model): + """ + To remove process model-wiring from + :class:`ansible_mitogen.connection.Connection`, it is necessary to track + some idea of the configured execution environment outside the connection + plug-in. + + That is what :func:`set_worker_model` and :func:`get_worker_model` are for. + """ + global _worker_model + assert model is None or _worker_model is None + _worker_model = model + + +def get_worker_model(): + """ + Return the :class:`WorkerModel` currently configured by the running + strategy. + """ + if _worker_model is None: + raise ansible.errors.AnsibleConnectionFailure(worker_model_msg) + return _worker_model + + +def get_classic_worker_model(**kwargs): + """ + Return the single :class:`ClassicWorkerModel` instance, constructing it if + necessary. + """ + global _classic_worker_model + assert _classic_worker_model is None or (not kwargs), \ + "ClassicWorkerModel kwargs supplied but model already constructed" + + if _classic_worker_model is None: + _classic_worker_model = ClassicWorkerModel(**kwargs) + return _classic_worker_model + + +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())) + + +def setup_pool(pool): + """ + Configure a connection multiplexer's :class:`mitogen.service.Pool` with + services accessed by clients and WorkerProcesses. + """ + pool.add(mitogen.service.FileService(router=pool.router)) + pool.add(mitogen.service.PushFileService(router=pool.router)) + pool.add(ansible_mitogen.services.ContextService(router=pool.router)) + pool.add(ansible_mitogen.services.ModuleDepService(pool.router)) + LOG.debug('Service pool configured: size=%d', pool.size) + + +def _setup_responder(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') + + # 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 increase_open_file_limit(): + """ + #549: in order to reduce the possibility of hitting an open files limit, + increase :data:`resource.RLIMIT_NOFILE` from its soft limit to its hard + limit, if they differ. + + It is common that a low soft limit is configured by default, where the hard + limit is much higher. + """ + soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) + if hard == resource.RLIM_INFINITY: + hard_s = '(infinity)' + # cap in case of O(RLIMIT_NOFILE) algorithm in some subprocess. + hard = 524288 + else: + hard_s = str(hard) + + LOG.debug('inherited open file limits: soft=%d hard=%s', soft, hard_s) + if soft >= hard: + LOG.debug('max open files already set to hard limit: %d', hard) + return + + # OS X is limited by kern.maxfilesperproc sysctl, rather than the + # advertised unlimited hard RLIMIT_NOFILE. Just hard-wire known defaults + # for that sysctl, to avoid the mess of querying it. + for value in (hard, 10240): + try: + resource.setrlimit(resource.RLIMIT_NOFILE, (value, hard)) + LOG.debug('raised soft open file limit from %d to %d', soft, value) + break + except ValueError as e: + LOG.debug('could not raise soft open file limit from %d to %d: %s', + soft, value, e) + + +def common_setup(enable_affinity=True, _init_logging=True): + save_pid('controller') + ansible_mitogen.logging.set_process_name('top') + + if _init_logging: + ansible_mitogen.logging.setup() + + if enable_affinity: + ansible_mitogen.affinity.policy.assign_controller() + + mitogen.utils.setup_gil() + if faulthandler is not None: + faulthandler.enable() + + MuxProcess.profiling = getenv_int('MITOGEN_PROFILING') > 0 + if MuxProcess.profiling: + mitogen.core.enable_profiling() + + MuxProcess.cls_original_env = dict(os.environ) + increase_open_file_limit() + + +def get_cpu_count(default=None): + """ + Get the multiplexer CPU count from the MITOGEN_CPU_COUNT environment + variable, returning `default` if one isn't set, or is out of range. + + :param int default: + Default CPU, or :data:`None` to use all available CPUs. + """ + max_cpus = multiprocessing.cpu_count() + if default is None: + default = max_cpus + + cpu_count = getenv_int('MITOGEN_CPU_COUNT', default=default) + if cpu_count < 1 or cpu_count > max_cpus: + cpu_count = default + + return cpu_count + + +class Broker(mitogen.master.Broker): + """ + WorkerProcess maintains fewer file descriptors, therefore does not need + the exuberant syscall expense of EpollPoller, so override it and restore + the poll() poller. + """ + poller_class = mitogen.parent.POLLER_LIGHTWEIGHT + + +class Binding(object): + """ + Represent a bound connection for a particular inventory hostname. When + operating in sharded mode, the actual MuxProcess implementing a connection + varies according to the target machine. Depending on the particular + implementation, this class represents a binding to the correct MuxProcess. + """ + def get_child_service_context(self): + """ + Return the :class:`mitogen.core.Context` to which children should + direct requests for services such as FileService, or :data:`None` for + the local process. + + This can be different from :meth:`get_service_context` where MuxProcess + and WorkerProcess are combined, and it is discovered a task is + delegated after being assigned to its initial worker for the original + un-delegated hostname. In that case, connection management and + expensive services like file transfer must be implemented by the + MuxProcess connected to the target, rather than routed to the + MuxProcess responsible for executing the task. + """ + raise NotImplementedError() + + def get_service_context(self): + """ + Return the :class:`mitogen.core.Context` to which this process should + direct ContextService requests, or :data:`None` for the local process. + """ + raise NotImplementedError() + + def close(self): + """ + Finalize any associated resources. + """ + raise NotImplementedError() + + +class WorkerModel(object): + """ + Interface used by StrategyMixin to manage various Mitogen services, by + default running in one or more connection multiplexer subprocesses spawned + off the top-level Ansible process. + """ + def on_strategy_start(self): + """ + Called prior to strategy start in the top-level process. Responsible + for preparing any worker/connection multiplexer state. + """ + raise NotImplementedError() + + def on_strategy_complete(self): + """ + Called after strategy completion in the top-level process. Must place + Ansible back in a "compatible" state where any other strategy plug-in + may execute. + """ + raise NotImplementedError() + + def get_binding(self, inventory_name): + """ + Return a :class:`Binding` to access Mitogen services for + `inventory_name`. Usually called from worker processes, but may also be + called from top-level process to handle "meta: reset_connection". + """ + raise NotImplementedError() + + +class ClassicBinding(Binding): + """ + Only one connection may be active at a time in a classic worker, so its + binding just provides forwarders back to :class:`ClassicWorkerModel`. + """ + def __init__(self, model): + self.model = model + + def get_service_context(self): + """ + See Binding.get_service_context(). + """ + return self.model.parent + + def get_child_service_context(self): + """ + See Binding.get_child_service_context(). + """ + return self.model.parent + + def close(self): + """ + See Binding.close(). + """ + self.model.on_binding_close() + + +class ClassicWorkerModel(WorkerModel): + #: In the top-level process, this references one end of a socketpair(), + #: whose other end child MuxProcesses block reading from to determine when + #: the master process dies. When the top-level exits abnormally, or + #: normally but where :func:`_on_process_exit` has been called, this socket + #: will be closed, causing all the children to wake. + parent_sock = None + + #: In the mux process, this is the other end of :attr:`cls_parent_sock`. + #: The main thread blocks on a read from it until :attr:`cls_parent_sock` + #: is closed. + child_sock = None + + #: mitogen.master.Router for this worker. + router = None + + #: mitogen.master.Broker for this worker. + broker = None + + #: Name of multiplexer process socket we are currently connected to. + listener_path = None + + #: mitogen.parent.Context representing the parent Context, which is the + #: connection multiplexer process when running in classic mode, or the + #: top-level process when running a new-style mode. + parent = None + + def __init__(self, _init_logging=True): + """ + Arrange for classic model multiplexers to be started. The parent choses + UNIX socket paths each 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. + """ + # #573: The process ID that installed the :mod:`atexit` handler. If + # some unknown Ansible plug-in forks the Ansible top-level process and + # later performs a graceful Python exit, it may try to wait for child + # PIDs it never owned, causing a crash. We want to avoid that. + self._pid = os.getpid() + + common_setup(_init_logging=_init_logging) + + self.parent_sock, self.child_sock = socket.socketpair() + mitogen.core.set_cloexec(self.parent_sock.fileno()) + mitogen.core.set_cloexec(self.child_sock.fileno()) + + self._muxes = [ + MuxProcess(self, index) + for index in range(get_cpu_count(default=1)) + ] + for mux in self._muxes: + mux.start() + + atexit.register(self._on_process_exit) + self.child_sock.close() + self.child_sock = None + + def _listener_for_name(self, name): + """ + Given an inventory hostname, return the UNIX listener that should + communicate with it. This is a simple hash of the inventory name. + """ + mux = self._muxes[abs(hash(name)) % len(self._muxes)] + LOG.debug('will use multiplexer %d (%s) to connect to "%s"', + mux.index, mux.path, name) + return mux.path + + def _reconnect(self, path): + if self.router is not None: + # Router can just be overwritten, but the previous parent + # connection must explicitly be removed from the broker first. + self.router.disconnect(self.parent) + self.parent = None + self.router = None + + try: + self.router, self.parent = mitogen.unix.connect( + path=path, + broker=self.broker, + ) + except mitogen.unix.ConnectError as e: + # This is not AnsibleConnectionFailure since we want to break + # with_items loops. + raise ansible.errors.AnsibleError(shutting_down_msg % (e,)) + + self.router.max_message_size = MAX_MESSAGE_SIZE + self.listener_path = path + + def _on_process_exit(self): + """ + This is an :mod:`atexit` handler installed in the top-level process. + + Shut the write end of `sock`, causing the receive side of the socket in + every :class:`MuxProcess` to return 0-byte reads, and causing their + main threads to wake and initiate shutdown. After shutting the socket + down, wait on each child to finish exiting. + + This is done using :mod:`atexit` since Ansible lacks any better 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. + """ + if self._pid != os.getpid(): + return + + try: + self.parent_sock.shutdown(socket.SHUT_WR) + except socket.error: + # Already closed. This is possible when tests are running. + LOG.debug('_on_process_exit: ignoring duplicate call') + return + + mitogen.core.io_op(self.parent_sock.recv, 1) + self.parent_sock.close() + + for mux in self._muxes: + _, status = os.waitpid(mux.pid, 0) + status = mitogen.fork._convert_exit_status(status) + LOG.debug('multiplexer %d PID %d %s', mux.index, mux.pid, + mitogen.parent.returncode_to_str(status)) + + def _test_reset(self): + """ + Used to clean up in unit tests. + """ + self.on_binding_close() + self._on_process_exit() + set_worker_model(None) + + global _classic_worker_model + _classic_worker_model = None + + def on_strategy_start(self): + """ + See WorkerModel.on_strategy_start(). + """ + + def on_strategy_complete(self): + """ + See WorkerModel.on_strategy_complete(). + """ + + def get_binding(self, inventory_name): + """ + See WorkerModel.get_binding(). + """ + if self.broker is None: + self.broker = Broker() + + path = self._listener_for_name(inventory_name) + if path != self.listener_path: + self._reconnect(path) + + return ClassicBinding(self) + + def on_binding_close(self): + if not self.broker: + return + + self.broker.shutdown() + self.broker.join() + self.router = None + self.broker = None + self.parent = None + self.listener_path = 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 + # related resources. + mitogen.fork.on_fork() + + +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 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. + """ + #: 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. + cls_original_env = None + + def __init__(self, model, index): + #: :class:`ClassicWorkerModel` instance we were created by. + self.model = model + #: MuxProcess CPU index. + self.index = index + #: Individual path of this process. + self.path = mitogen.unix.make_socket_path() + + def start(self): + self.pid = os.fork() + if self.pid: + # Wait for child to boot before continuing. + mitogen.core.io_op(self.model.parent_sock.recv, 1) + return + + ansible_mitogen.logging.set_process_name('mux:' + str(self.index)) + if setproctitle: + setproctitle.setproctitle('mitogen mux:%s (%s)' % ( + self.index, + os.path.basename(self.path), + )) + + self.model.parent_sock.close() + self.model.parent_sock = None + try: + try: + self.worker_main() + except Exception: + LOG.exception('worker_main() crashed') + finally: + sys.exit() + + def worker_main(self): + """ + The main function of 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). + """ + save_pid('mux') + + # #623: MuxProcess ignores SIGINT because it wants to live until every + # Ansible worker process has been cleaned up by + # TaskQueueManager.cleanup(), otherwise harmles yet scary warnings + # about being unable connect to MuxProess could be printed. + signal.signal(signal.SIGINT, signal.SIG_IGN) + ansible_mitogen.logging.set_process_name('mux') + ansible_mitogen.affinity.policy.assign_muxprocess(self.index) + + self._setup_master() + self._setup_services() + + try: + # Let the parent know our listening socket is ready. + mitogen.core.io_op(self.model.child_sock.send, b('1')) + # Block until the socket is closed, which happens on parent exit. + mitogen.core.io_op(self.model.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_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=MAX_MESSAGE_SIZE, + ) + _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.build_stream( + router=self.router, + path=self.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, + size=getenv_int('MITOGEN_POOL_SIZE', default=32), + ) + setup_pool(self.pool) + + def _on_broker_shutdown(self): + """ + Respond to broker shutdown by shutting down the pool. Do not join on it + yet, since that would block the broker thread which then cannot clean + up pending handlers and connections, which is required for the threads + to exit gracefully. + """ + self.pool.stop(join=False) + + def _on_broker_exit(self): + """ + Respond to the broker thread about to exit by finally joining on the + pool. This is safe since pools only block in connection attempts, and + connection attempts fail with CancelledError when broker shutdown + begins. + """ + self.pool.join() diff --git a/mitogen-0.3.9/ansible_mitogen/runner.py b/mitogen-0.3.9/ansible_mitogen/runner.py new file mode 100644 index 0000000..8da1b67 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/runner.py @@ -0,0 +1,1101 @@ +# 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. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import atexit +import json +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_rpartition +from mitogen.core import to_text + +try: + import ctypes +except ImportError: + # Python 2.4 + ctypes = None + +try: + # Python >= 3.4, PEP 451 ModuleSpec API + import importlib.machinery + import importlib.util +except ImportError: + # Python < 3.4, PEP 302 Import Hooks + import imp + +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__) + + +def shlex_split_b(s): + """ + Use shlex.split() to split characters in some single-byte encoding, without + knowing what that encoding is. The input is bytes, the output is a list of + bytes. + """ + assert isinstance(s, mitogen.core.BytesType) + if mitogen.core.PY3: + return [ + t.encode('latin1') + for t in shlex.split(s.decode('latin1'), comments=True) + ] + + return [t for t in shlex.split(s, comments=True)] + + +class TempFileWatcher(object): + """ + Since Ansible 2.7.0, lineinfile leaks file descriptors returned by + :func:`tempfile.mkstemp` (ansible/ansible#57327). Handle this and all + similar cases by recording descriptors produced by mkstemp during module + execution, and cleaning up any leaked descriptors on completion. + """ + def __init__(self): + self._real_mkstemp = tempfile.mkstemp + # (fd, st.st_dev, st.st_ino) + self._fd_dev_inode = [] + tempfile.mkstemp = self._wrap_mkstemp + + def _wrap_mkstemp(self, *args, **kwargs): + fd, path = self._real_mkstemp(*args, **kwargs) + st = os.fstat(fd) + self._fd_dev_inode.append((fd, st.st_dev, st.st_ino)) + return fd, path + + def revert(self): + tempfile.mkstemp = self._real_mkstemp + for tup in self._fd_dev_inode: + self._revert_one(*tup) + + def _revert_one(self, fd, st_dev, st_ino): + try: + st = os.fstat(fd) + except OSError: + # FD no longer exists. + return + + if not (st.st_dev == st_dev and st.st_ino == st_ino): + # FD reused. + return + + LOG.info("a tempfile.mkstemp() FD was leaked during the last task") + os.close(fd) + + +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. + """ + # We know nothing about the character set of /etc/environment or the + # process environment. + environ = getattr(os, 'environb', os.environ) + + 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 == self.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 = open(self.path, 'rb') + 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_b(line) + if (not bits) or bits[0].startswith(b('#')): + continue + + if bits[0] == b('export'): + bits.pop(0) + + key, sep, value = bytes_partition(b(' ').join(bits), b('=')) + 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 self.environ: + LOG.debug('%r: existing key %r=%r exists, not setting %r', + self, key, self.environ[key], value) + else: + LOG.debug('%r: setting key %r to %r', self, key, value) + self._keys.append(key) + self.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 self.environ: + LOG.debug('%r: removing old key %r', self, key) + del self.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_cwd(self): + """ + #591: make a best-effort attempt to return to :attr:`good_temp_dir`. + """ + try: + os.chdir(self.good_temp_dir) + except OSError: + LOG.debug('%r: could not restore CWD to %r', + self, self.good_temp_dir) + + def revert(self): + """ + Revert any changes made to the process after running a module. The base + implementation simply restores the original environment. + """ + self._revert_cwd() + 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): + """ + Return a loader for the module with fullname, if we will load it. + + Implements importlib.abc.MetaPathFinder.find_module(). + Deprecrated in Python 3.4+, replaced by find_spec(). + Raises ImportWarning in Python 3.10+. Removed in Python 3.12. + """ + if fullname in self._by_fullname: + return self + + def find_spec(self, fullname, path, target=None): + """ + Return a `ModuleSpec` for module with `fullname` if we will load it. + Otherwise return `None`. + + Implements importlib.abc.MetaPathFinder.find_spec(). Python 3.4+. + """ + if fullname.endswith('.'): + return None + + try: + module_path, is_package = self._by_fullname[fullname] + except KeyError: + LOG.debug('Skipping %s: not present', fullname) + return None + + LOG.debug('Handling %s', fullname) + origin = 'master:%s' % (module_path,) + return importlib.machinery.ModuleSpec( + fullname, loader=self, origin=origin, is_package=is_package, + ) + + def create_module(self, spec): + """ + Return a module object for the given ModuleSpec. + + Implements PEP-451 importlib.abc.Loader API introduced in Python 3.4. + Unlike Loader.load_module() this shouldn't populate sys.modules or + set module attributes. Both are done by Python. + """ + module = types.ModuleType(spec.name) + # FIXME create_module() shouldn't initialise module attributes + module.__file__ = spec.origin + return module + + def exec_module(self, module): + """ + Execute the module to initialise it. Don't return anything. + + Implements PEP-451 importlib.abc.Loader API, introduced in Python 3.4. + """ + spec = module.__spec__ + path, _ = self._by_fullname[spec.name] + source = ansible_mitogen.target.get_small_file(self._context, path) + code = compile(source, path, 'exec', 0, 1) + exec(code, module.__dict__) + self._loaded.add(spec.name) + + def load_module(self, fullname): + """ + Return the loaded module specified by fullname. + + Implements PEP 302 importlib.abc.Loader.load_module(). + Deprecated in Python 3.4+, replaced by create_module() & exec_module(). + """ + 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, py_module_name, **kwargs): + super(NewStyleRunner, self).__init__(**kwargs) + self.module_map = module_map + self.py_module_name = py_module_name + + 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. + """ + # I think "custom" means "found in custom module_utils search path", + # e.g. playbook relative dir, ~/.ansible/..., Ansible collection. + for fullname, _, _ in self.module_map['custom']: + mitogen.core.import_module(fullname) + + # I think "builtin" means "part of ansible/ansible-base/ansible-core", + # as opposed to Python builtin modules such as sys. + for fullname in self.module_map['builtin']: + try: + mitogen.core.import_module(fullname) + except ImportError as exc: + # #590: Ansible 2.8 module_utils.distro is a package that + # replaces itself in sys.modules with a non-package during + # import. Prior to replacement, it is a real package containing + # a '_distro' submodule which is used on 2.x. Given a 2.x + # controller and 3.x target, the import hook never needs to run + # again before this replacement occurs, and 'distro' is + # replaced with a module from the stdlib. In this case as this + # loop progresses to the next entry and attempts to preload + # 'distro._distro', the import mechanism will fail. So here we + # silently ignore any failure for it. + if fullname == 'ansible.module_utils.distro._distro': + continue + + # ansible.module_utils.compat.selinux raises ImportError if it + # can't load libselinux.so. The importer would usually catch + # this & skip selinux operations. We don't care about selinux, + # we're using import to get a copy of the module. + if (fullname == 'ansible.module_utils.compat.selinux' + and exc.msg == 'unable to load libselinux.so'): + continue + + raise + + 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._temp_watcher = TempFileWatcher() + 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._temp_watcher.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 _get_module_package(self): + """ + Since Ansible 2.9 __package__ must be set in accordance with an + approximation of the original package hierarchy, so that relative + imports function correctly. + """ + pkg, sep, modname = str_rpartition(self.py_module_name, '.') + if not sep: + return None + if mitogen.core.PY3: + return pkg + return pkg.encode() + + def _run(self): + mod = types.ModuleType(self.main_module_name) + mod.__package__ = self._get_module_package() + # 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.3.9/ansible_mitogen/services.py b/mitogen-0.3.9/ansible_mitogen/services.py new file mode 100644 index 0000000..3e9de65 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/services.py @@ -0,0 +1,565 @@ +# 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, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +import logging +import os +import sys +import threading + +import ansible.constants + +import mitogen.core +import mitogen.service +import ansible_mitogen.loaders +import ansible_mitogen.module_finder +import ansible_mitogen.target +import ansible_mitogen.utils.unsafe + + +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 ansible_mitogen.utils.unsafe.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({ + 'stack': list, + }) + def reset(self, stack): + """ + Return a reference, forcing close and discard of the underlying + connection. Used for 'meta: reset_connection' or when some other error + is detected. + + :returns: + :data:`True` if a connection was found to discard, otherwise + :data:`False`. + """ + LOG.debug('%r.reset(%r)', self, stack) + + # this could happen if we have a `shutdown -r` shell command + # and then a `wait_for_connection` right afterwards + # in this case, we have no stack to disconnect from + if not stack: + return False + + l = mitogen.core.Latch() + context = None + with self._lock: + for i, spec in enumerate(stack): + key = key_from_dict(via=context, **spec) + response = self._response_by_key.get(key) + if response is None: + LOG.debug('%r: could not find connection to shut down; ' + 'failed at hop %d', self, i) + return False + + context = response['context'] + + mitogen.core.listen(context, 'disconnect', l.put) + self._shutdown_unlocked(context) + + # The timeout below is to turn a hang into a crash in case there is any + # possible race between 'disconnect' signal subscription, and the child + # abruptly disconnecting. + l.get(timeout=30.0) + return True + + @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('decrementing reference count for %r', 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): + if hasattr(self.router.responder, 'forward_modules'): + 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: %(method)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, 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.3.9/ansible_mitogen/strategy.py b/mitogen-0.3.9/ansible_mitogen/strategy.py new file mode 100644 index 0000000..0a98e31 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/strategy.py @@ -0,0 +1,328 @@ +# 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, division, print_function +__metaclass__ = type + +import os +import signal +import threading + +try: + import setproctitle +except ImportError: + setproctitle = None + +import mitogen.core +import ansible_mitogen.affinity +import ansible_mitogen.loaders +import ansible_mitogen.mixins +import ansible_mitogen.process + +import ansible.executor.process.worker +import ansible.utils.sentinel + + +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: + import awx_display_callback.events + except ImportError: + return + + if hasattr(awx_display_callback.events.EventContext(), '_local'): + # Patched version. + return + + def patch_add_local(self, **kwargs): + tls = vars(self._local) + ctx = tls.setdefault('_ctx', {}) + ctx.update(kwargs) + + awx_display_callback.events.EventContext._local = threading.local() + awx_display_callback.events.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 plugins outside the Ansible tree. + """ + get_kwargs = {'class_only': True} + if name in ('fetch',): + name = 'mitogen_' + name + get_kwargs['collection_list'] = kwargs.pop('collection_list', None) + + klass = ansible_mitogen.loaders.action_loader__get(name, **get_kwargs) + 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) + + +REDIRECTED_CONNECTION_PLUGINS = ( + 'buildah', + 'docker', + 'kubectl', + 'jail', + 'local', + 'lxc', + 'lxd', + 'machinectl', + 'podman', + 'setns', + 'ssh', +) + + +def wrap_connection_loader__get(name, *args, **kwargs): + """ + While a Mitogen strategy is active, rewrite connection_loader.get() calls + for some transports into requests for a compatible Mitogen transport. + """ + if name in REDIRECTED_CONNECTION_PLUGINS: + name = 'mitogen_' + name + + return ansible_mitogen.loaders.connection_loader__get(name, *args, **kwargs) + + +def wrap_worker__run(self): + """ + While a Mitogen strategy is active, trap WorkerProcess.run() calls and use + the opportunity to set the worker's name in the process list and log + output, activate profiling if requested, and bind the worker to a specific + CPU. + """ + if setproctitle: + setproctitle.setproctitle('worker:%s task:%s' % ( + self._host.name, + self._task.action, + )) + + # 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(self) + ) + + +class AnsibleWrappers(object): + """ + Manage add/removal of various Ansible runtime hooks. + """ + 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 _install_wrappers(self): + """ + Install our PluginLoader monkey patches and update global variables + with references to the real functions. + """ + ansible_mitogen.loaders.action_loader.get = wrap_action_loader__get + ansible_mitogen.loaders.connection_loader.get_with_context = 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 = ( + ansible_mitogen.loaders.action_loader__get + ) + ansible_mitogen.loaders.connection_loader.get_with_context = ( + ansible_mitogen.loaders.connection_loader__get + ) + ansible.executor.process.worker.WorkerProcess.run = worker__run + + def install(self): + self._add_plugin_paths() + self._install_wrappers() + + def remove(self): + self._remove_wrappers() + + +class StrategyMixin(object): + """ + This mix-in enhances any built-in strategy by arranging for an appropriate + WorkerModel instance to be constructed as necessary, or for the existing + one to be reused. + + The WorkerModel in turn arranges for a connection multiplexer to be started + somewhere (by default in an external process), and for WorkerProcesses to + grow support for using those top-level services to communicate with 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 connection + mutliplexer subprocess 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 connection multiplexer, 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 connection multiplexer, 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 one of the "mitogen_*" connection plug-ins. That + plug-in implements communication via a UNIX socket connection to the + connection multiplexer process, and uses ContextService running there + to establish a persistent connection to the target. + + 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 _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.action_loader.get( + name=task.action, + class_only=True, + ) + if play_context.connection is not ansible.utils.sentinel.Sentinel: + # 2.8 appears to defer computing this until inside the worker. + # TODO: figure out where it has moved. + ansible_mitogen.loaders.connection_loader.get( + name=play_context.connection, + class_only=True, + ) + + return super(StrategyMixin, self)._queue_task( + host=host, + task=task, + task_vars=task_vars, + play_context=play_context, + ) + + def _get_worker_model(self): + """ + In classic mode a single :class:`WorkerModel` exists, which manages + references and configuration of the associated connection multiplexer + process. + """ + return ansible_mitogen.process.get_classic_worker_model() + + def run(self, iterator, play_context, result=0): + """ + Wrap :meth:`run` to ensure requisite infrastructure and modifications + are configured for the duration of the call. + """ + wrappers = AnsibleWrappers() + self._worker_model = self._get_worker_model() + ansible_mitogen.process.set_worker_model(self._worker_model) + try: + self._worker_model.on_strategy_start() + try: + wrappers.install() + try: + run = super(StrategyMixin, self).run + return mitogen.core._profile_hook('Strategy', + lambda: run(iterator, play_context) + ) + finally: + wrappers.remove() + finally: + self._worker_model.on_strategy_complete() + finally: + ansible_mitogen.process.set_worker_model(None) diff --git a/mitogen-0.3.9/ansible_mitogen/target.py b/mitogen-0.3.9/ansible_mitogen/target.py new file mode 100644 index 0000000..7d907d6 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/target.py @@ -0,0 +1,771 @@ +# 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. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import errno +import grp +import json +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.parent +import mitogen.service +from mitogen.core import b + +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_info < (3,) 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) + + 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. + """ + with open(path, 'rb') as f: + return f.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.3.9/ansible_mitogen/transport_config.py b/mitogen-0.3.9/ansible_mitogen/transport_config.py new file mode 100644 index 0000000..3ab623f --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/transport_config.py @@ -0,0 +1,820 @@ +# 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 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. +""" + +from __future__ import absolute_import, division, print_function +from __future__ import unicode_literals +__metaclass__ = type + +import abc +import os +import ansible.utils.shlex +import ansible.constants as C + +from ansible.module_utils.six import with_metaclass +from ansible.module_utils.parsing.convert_bool import boolean + +# this was added in Ansible >= 2.8.0; fallback to the default interpreter if necessary +try: + from ansible.executor.interpreter_discovery import discover_interpreter +except ImportError: + discover_interpreter = lambda action,interpreter_name,discovery_mode,task_vars: '/usr/bin/python' + +try: + from ansible.utils.unsafe_proxy import AnsibleUnsafeText +except ImportError: + from ansible.vars.unsafe_proxy import AnsibleUnsafeText + +import mitogen.core + + +def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_python): + """ + Triggers ansible python interpreter discovery if requested. + Caches this value the same way Ansible does it. + For connections like `docker`, we want to rediscover the python interpreter because + it could be different than what's ran on the host + """ + # keep trying different interpreters until we don't error + if action._finding_python_interpreter: + return action._possible_python_interpreter + + if s in ['auto', 'auto_legacy', 'auto_silent', 'auto_legacy_silent']: + # python is the only supported interpreter_name as of Ansible 2.8.8 + interpreter_name = 'python' + discovered_interpreter_config = u'discovered_interpreter_%s' % interpreter_name + + if task_vars.get('ansible_facts') is None: + task_vars['ansible_facts'] = {} + + if rediscover_python and task_vars.get('ansible_facts', {}).get(discovered_interpreter_config): + # if we're rediscovering python then chances are we're running something like a docker connection + # this will handle scenarios like running a playbook that does stuff + then dynamically creates a docker container, + # then runs the rest of the playbook inside that container, and then rerunning the playbook again + action._rediscovered_python = True + + # blow away the discovered_interpreter_config cache and rediscover + del task_vars['ansible_facts'][discovered_interpreter_config] + + if discovered_interpreter_config not in task_vars['ansible_facts']: + action._finding_python_interpreter = True + # fake pipelining so discover_interpreter can be happy + action._connection.has_pipelining = True + s = AnsibleUnsafeText(discover_interpreter( + action=action, + interpreter_name=interpreter_name, + discovery_mode=s, + task_vars=task_vars)) + + # cache discovered interpreter + task_vars['ansible_facts'][discovered_interpreter_config] = s + action._connection.has_pipelining = False + else: + s = task_vars['ansible_facts'][discovered_interpreter_config] + + # propagate discovered interpreter as fact + action._discovered_interpreter_key = discovered_interpreter_config + action._discovered_interpreter = s + + action._finding_python_interpreter = False + return s + + +def parse_python_path(s, task_vars, action, rediscover_python): + """ + Given the string set for ansible_python_interpeter, parse it using shell + syntax and return an appropriate argument vector. If the value detected is + one of interpreter discovery then run that first. Caches python interpreter + discovery value in `facts_from_task_vars` like how Ansible handles this. + """ + if not s: + # if python_path doesn't exist, default to `auto` and attempt to discover it + s = 'auto' + + s = run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_python) + # if unable to determine python_path, fallback to '/usr/bin/python' + if not s: + s = '/usr/bin/python' + + 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 host_key_checking(self): + """ + Whether or not to check the keys of 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_buildah_path(self): + """ + The path to the "buildah" program for the 'buildah' transport. + """ + + @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_podman_path(self): + """ + The path to the "podman" program for the 'podman' transport. + """ + + @abc.abstractmethod + def mitogen_ssh_keepalive_interval(self): + """ + The SSH ServerAliveInterval. + """ + + @abc.abstractmethod + def mitogen_ssh_keepalive_count(self): + """ + The SSH ServerAliveCount. + """ + + @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. + """ + + @abc.abstractmethod + def ansible_doas_exe(self): + """ + Value of "ansible_doas_exe" variable. + """ + + +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 + self._task_vars = self._connection._get_task_vars() + # used to run interpreter discovery + self._action = connection._action + + 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): + # become_pass is owned/provided by the active become plugin. However + # PlayContext is intertwined with it. Known complications + # - ansible_become_password is higher priority than ansible_become_pass, + # `play_context.become_pass` doesn't obey this (atleast with Mitgeon). + # - `meta: reset_connection` runs `connection.reset()` but + # `ansible_mitogen.connection.Connection.reset()` recreates the + # connection object, setting `connection.become = None`. + become_plugin = self._connection.become + try: + become_pass = become_plugin.get_option('become_pass', playcontext=self._play_context) + except AttributeError: + become_pass = self._play_context.become_pass + return optional_secret(become_pass) + + def password(self): + return optional_secret(self._play_context.password) + + def port(self): + return self._play_context.port + + def python_path(self, rediscover_python=False): + 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, + task_vars=self._task_vars, + action=self._action, + rediscover_python=rediscover_python) + + def host_key_checking(self): + def candidates(): + yield self._connection.get_task_var('ansible_ssh_host_key_checking') + yield self._connection.get_task_var('ansible_host_key_checking') + yield C.HOST_KEY_CHECKING + val = next((v for v in candidates() if v is not None), True) + return boolean(val) + + def private_key_file(self): + return self._play_context.private_key_file + + def ssh_executable(self): + return C.config.get_config_value("ssh_executable", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + + 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 ( + C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), + C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), + C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + ) + for term in ansible.utils.shlex.shlex_split(s or '') + ] + + def become_exe(self): + # In Ansible 2.8, PlayContext.become_exe always has a default value due + # to the new options mechanism. Previously it was only set if a value + # ("somewhere") had been specified for the task. + # For consistency in the tests, here we make older Ansibles behave like + # newer Ansibles. + exe = self._play_context.become_exe + if exe is None and self._play_context.become_method == 'sudo': + exe = 'sudo' + return 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, + # Ansible <=2.7. + getattr(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_buildah_path(self): + return self._connection.get_task_var('mitogen_buildah_path') + + 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_podman_path(self): + return self._connection.get_task_var('mitogen_podman_path') + + def mitogen_ssh_keepalive_interval(self): + return self._connection.get_task_var('mitogen_ssh_keepalive_interval') + + def mitogen_ssh_keepalive_count(self): + return self._connection.get_task_var('mitogen_ssh_keepalive_count') + + 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() + + def ansible_doas_exe(self): + return ( + self._connection.get_task_var('ansible_doas_exe') or + os.environ.get('ANSIBLE_DOAS_EXE') + ) + + +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, task_vars, become_method, become_user, + play_context, action): + """ + :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 dict task_vars: + Task vars provided by Ansible. + :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. + :param ActionModuleMixin action: + Backref to the ActionModuleMixin required for ansible interpreter discovery + """ + self._inventory_name = inventory_name + self._host_vars = host_vars + self._task_vars = task_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 + self._action = action + + 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_pass') or + self._host_vars.get('ansible_become_password') + ) + + 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, rediscover_python=False): + 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, + task_vars=self._task_vars, + action=self._action, + rediscover_python=rediscover_python) + + def host_key_checking(self): + def candidates(): + yield self._host_vars.get('ansible_ssh_host_key_checking') + yield self._host_vars.get('ansible_host_key_checking') + yield C.HOST_KEY_CHECKING + val = next((v for v in candidates() if v is not None), True) + return boolean(val) + + 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 C.config.get_config_value("ssh_executable", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + + 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 ( + C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), + C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), + C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + ) + 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_buildah_path(self): + return self._host_vars.get('mitogen_buildah_path') + + 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_podman_path(self): + return self._host_vars.get('mitogen_podman_path') + + def mitogen_ssh_keepalive_interval(self): + return self._host_vars.get('mitogen_ssh_keepalive_interval') + + def mitogen_ssh_keepalive_count(self): + return self._host_vars.get('mitogen_ssh_keepalive_count') + + 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 + + def ansible_doas_exe(self): + return ( + self._host_vars.get('ansible_doas_exe') or + os.environ.get('ANSIBLE_DOAS_EXE') + ) diff --git a/mitogen-0.3.9/ansible_mitogen/utils/__init__.py b/mitogen-0.3.9/ansible_mitogen/utils/__init__.py new file mode 100644 index 0000000..a01b261 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/utils/__init__.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re + +import ansible + +__all__ = [ + 'ansible_version', +] + + +def _parse(v_string): + # Adapted from distutils.version.LooseVersion.parse() + component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + for component in component_re.split(v_string): + if not component or component == '.': + continue + try: + yield int(component) + except ValueError: + yield component + + +ansible_version = tuple(_parse(ansible.__version__)) + +del _parse +del re +del ansible diff --git a/mitogen-0.3.9/ansible_mitogen/utils/__pycache__/__init__.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf56eae93f4cf66243d40f139751cf89d90fe3ab GIT binary patch literal 719 zcmYjPOK;Oa5Z)a>lg8mydg9O{5lUQ%6B0rQ(QA;X6u?Qfa=qKgW&P-SU6jg|K!N|! z9{Edq<-}irLz%5o)UjrE=bQ0pzL~9}s83LS=HHEJ_O!wIL76f~g) zBv3esLy2$`4{?xw;@_M>5^!fplaRaI`%aRc=!>2RPPW!`O_RaAH}~fOhXXS5e`8HD za?HK-sHtSD1g!zUNefqL8lhtHsPPNVZcyB)0Qj8O7{oJKCu z-fBeAqof0ienRvMP62y&rst2IF8H(EZg-xJ&kyZoF&P;;8Tkg3FhN!obuRHT+kgM& z-NC-`np)=CILOEJXQ^z(zEYJ!{^1#GH1b@Uf&Hs01yaTuz5m`%qV6M|KhwA+Yxlr+D9%CiyDlT$eEk!xrn>?SqxDBxl8n-%^P0Uzc=9;ldy)r(lQ`Lyb&T9t^ zd>z7&4k+Mf&pw4~_pf@0p;2V|3>TqWB^XoqSctOoRxTRj>QyaF#8@G8n#r_jFek8) zVAG%p7)zx@*j6*1Zfl&*7Q0;}20UBN^1Ndn*yQkKQSnxaS9aMqjcyZ{h7bZc?se!5 J2LM2Q`VXoOx|09^ literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/utils/__pycache__/unsafe.cpython-310.pyc b/mitogen-0.3.9/ansible_mitogen/utils/__pycache__/unsafe.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52bb74afc8fefbf6849776b8d303c3078ada0e66 GIT binary patch literal 2896 zcmaJ@OLG%P5T04B9+u=MvN29#hJXQ81(_-?iOc1R14)1oLt#6qsFJMKJ7a6jYIm6( z8ARDtNes7KQuzbs=)W}AoN~@BDMGquSMmeMO7qyBo}TWnAG6W$aGAjKLe(~BVD<;T z9Di{2Q)tOON(d*M2Bbrqv|}}`POh1wMA*$d(5;}*ESRyFjiu(0c`rB1W~^jm6~^2a zZ4Psr=bw}22ruv=jHA57hhQAzWnO`CTui|4j2IC$F)AjZmA9w1&!)YfZ%&KpgLAyf zho2Lm&1}DSi|o^v6f}BY6yW`X?O8suZ^;<>GRk=|)?XXUm>BbOxG%gehG5?XZt>9< z);Yq*9#Q%0iQN-}-9{JRGW%+L{NO`A!Os{?E~a`kFNrx`6PIAe#Z7Apbo+(!NuC4C zE^}*@)TjPLoa;F~>TV=a&{e|mJ8>kHE^&XyPy8sD_&wmmmd9=Kxg z;5-NICbVP%I!?Luoa~YV@{*t`a8z#P#1>@FLtDiyv(Wj#{zK(^Yme?(itN&T3ca;Y zf43fkribK^O5_J!t;TgtA8EQH3qUa4-}mM_p_=#Ry@-p=DBwcQyJ6yQ1mc<$aWvoY zRkS6-Yu6h$8lUu+oQyi(Req4ncf-Wp6pgs2%ZuFiloxg4rE%2Hf^L>PyWpVZj*1*y zZY&0o=LX4A3H^lN?f=i#E28G?W#Pp@&aMK-qtx>rU^oE~Vv1`9(-D~T zF?~Cp$ZA6~c-CODg?JYB5JkZ9H-loTP7ziQEm?v=VrrdMvb-4h;9#^KeQB((9k(8r zk6Az9P6eT^r$rvwjm!h7BSfOA@3tFtOrr=wfUW?%!%m{4A3LeD%uiU-cgY|tOo-?A=q@-V z4Qq2uotnM%)Fqm#Q|QSmNM_I^28?-=Q!x7z4}e`WILG+WwDE=lm}efe1l9W)W1`rP ziEjuck1gIe8Nkf}wi3N_N&`K#2+1R!#&W5FvJfikrzs*aai4wsb(&mkE@7Sltp(Nw0=an6-5-?h3avR?9p!_jB&mSQLe0Bje_!& zd`Ck1gs9vuQ8tv>S(Q(D45gron-);(s+3WOw#x^VUGfs^5q0MPzcPd8p-^2JqE_r` zB$>;+DDKtS6JNDi(%n$KSR^dkWE)*SP=1J3CW@(uskZPXu*5AOP^77<3J#5 zgMHBj?}$E>9gv@}j@x4%h~5HAK!eP@h1ivD2mQEUgraZnn@~e%bwe(^NV*FAgewcu zxIH=IT_14pEJRbzdR31&H|z@dAG;)WVY77C)3D- zZ-q3k`($|;52*G78ZZ`mxc_FX)J4Z}g8)>+GG%};#n{w6o!{swk;sde}sx_t(F?t&43pag~y~dOSDu_;RXlT6-`# z^u!6$XL!7xTyd4ZBkua)wtI|qU~HDb+TM=*P*)r$BLJ9{%vdDp${q6oWx~Sv?U*mk z_lo%tC@pp(-VMYOUO&lQ=xW5KC98xDdurwE*CiWgC2ALJioYtg|Cuh1SvD=3U+GmX Rk8_BJf2UwpqJ`I0`#+?+)KdTe literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/ansible_mitogen/utils/unsafe.py b/mitogen-0.3.9/ansible_mitogen/utils/unsafe.py new file mode 100644 index 0000000..b2c3d53 --- /dev/null +++ b/mitogen-0.3.9/ansible_mitogen/utils/unsafe.py @@ -0,0 +1,79 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import ansible +import ansible.utils.unsafe_proxy + +import ansible_mitogen.utils + +import mitogen +import mitogen.core +import mitogen.utils + +__all__ = [ + 'cast', +] + +def _cast_to_dict(obj): return {cast(k): cast(v) for k, v in obj.items()} +def _cast_to_list(obj): return [cast(v) for v in obj] +def _cast_unsafe(obj): return obj._strip_unsafe() +def _passthrough(obj): return obj + + +# A dispatch table to cast objects based on their exact type. +# This is an optimisation, reliable fallbacks are required (e.g. isinstance()) +_CAST_DISPATCH = { + bytes: bytes, + dict: _cast_to_dict, + list: _cast_to_list, + tuple: _cast_to_list, + mitogen.core.UnicodeType: mitogen.core.UnicodeType, +} +_CAST_DISPATCH.update({t: _passthrough for t in mitogen.utils.PASSTHROUGH}) + +if hasattr(ansible.utils.unsafe_proxy.AnsibleUnsafeText, '_strip_unsafe'): + _CAST_DISPATCH.update({ + ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: _cast_unsafe, + ansible.utils.unsafe_proxy.AnsibleUnsafeText: _cast_unsafe, + ansible.utils.unsafe_proxy.NativeJinjaUnsafeText: _cast_unsafe, + }) +elif ansible_mitogen.utils.ansible_version[:2] <= (2, 16): + _CAST_DISPATCH.update({ + ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: bytes, + ansible.utils.unsafe_proxy.AnsibleUnsafeText: mitogen.core.UnicodeType, + }) +else: + mitogen_ver = '.'.join(str(v) for v in mitogen.__version__) + raise ImportError("Mitogen %s can't unwrap Ansible %s AnsibleUnsafe objects" + % (mitogen_ver, ansible.__version__)) + + +def cast(obj): + """ + Return obj (or a copy) with subtypes of builtins cast to their supertype. + + This is an enhanced version of :func:`mitogen.utils.cast`. In addition it + handles ``ansible.utils.unsafe_proxy.AnsibleUnsafeText`` and variants. + + There are types handled by :func:`ansible.utils.unsafe_proxy.wrap_var()` + that this function currently does not handle (e.g. `set()`), or preserve + preserve (e.g. `tuple()`). Future enhancements may change this. + + :param obj: + Object to undecorate. + :returns: + Undecorated object. + """ + # Fast path: obj is a known type, dispatch directly + try: + unwrapper = _CAST_DISPATCH[type(obj)] + except KeyError: + pass + else: + return unwrapper(obj) + + # Slow path: obj is some unknown subclass + if isinstance(obj, dict): return _cast_to_dict(obj) + if isinstance(obj, (list, tuple)): return _cast_to_list(obj) + + return mitogen.utils.cast(obj) diff --git a/mitogen-0.3.9/mitogen.egg-info/PKG-INFO b/mitogen-0.3.9/mitogen.egg-info/PKG-INFO new file mode 100644 index 0000000..e324817 --- /dev/null +++ b/mitogen-0.3.9/mitogen.egg-info/PKG-INFO @@ -0,0 +1,39 @@ +Metadata-Version: 2.1 +Name: mitogen +Version: 0.3.9 +Summary: Library for writing distributed self-replicating programs. +Home-page: https://github.com/mitogen-hq/mitogen/ +Author: David Wilson +License: New BSD +Classifier: Environment :: Console +Classifier: Framework :: Ansible +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: System :: Distributed Computing +Classifier: Topic :: System :: Systems Administration +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* +Description-Content-Type: text/markdown +License-File: LICENSE + +# Mitogen + +Please see the documentation. + +![](https://i.imgur.com/eBM6LhJ.gif) + +[![Total alerts](https://img.shields.io/lgtm/alerts/g/mitogen-hq/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mitogen-hq/mitogen/alerts/) + +[![Build Status](https://dev.azure.com/mitogen-hq/mitogen/_apis/build/status/mitogen-hq.mitogen?branchName=master)](https://dev.azure.com/mitogen-hq/mitogen/_build/latest?definitionId=1&branchName=master) diff --git a/mitogen-0.3.9/mitogen.egg-info/SOURCES.txt b/mitogen-0.3.9/mitogen.egg-info/SOURCES.txt new file mode 100644 index 0000000..2d2aed3 --- /dev/null +++ b/mitogen-0.3.9/mitogen.egg-info/SOURCES.txt @@ -0,0 +1,82 @@ +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/plugins/__init__.py +ansible_mitogen/plugins/action/__init__.py +ansible_mitogen/plugins/action/mitogen_fetch.py +ansible_mitogen/plugins/action/mitogen_get_stack.py +ansible_mitogen/plugins/connection/__init__.py +ansible_mitogen/plugins/connection/mitogen_buildah.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_podman.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 +ansible_mitogen/utils/__init__.py +ansible_mitogen/utils/unsafe.py +mitogen/__init__.py +mitogen/buildah.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/podman.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 +tests/testlib.py \ No newline at end of file diff --git a/mitogen-0.3.9/mitogen.egg-info/dependency_links.txt b/mitogen-0.3.9/mitogen.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mitogen-0.3.9/mitogen.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/mitogen-0.3.9/mitogen.egg-info/not-zip-safe b/mitogen-0.3.9/mitogen.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mitogen-0.3.9/mitogen.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/mitogen-0.3.9/mitogen.egg-info/top_level.txt b/mitogen-0.3.9/mitogen.egg-info/top_level.txt new file mode 100644 index 0000000..2360b3f --- /dev/null +++ b/mitogen-0.3.9/mitogen.egg-info/top_level.txt @@ -0,0 +1,2 @@ +ansible_mitogen +mitogen diff --git a/mitogen-0.3.9/mitogen/__init__.py b/mitogen-0.3.9/mitogen/__init__.py new file mode 100644 index 0000000..b0c6679 --- /dev/null +++ b/mitogen-0.3.9/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, 3, 9) + + +#: 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 + mitogen.utils.log_to_file(level=log_level) + return mitogen.core._profile_hook( + 'app.main', + mitogen.utils.run_with_router, + func, + ) + return wrapper diff --git a/mitogen-0.3.9/mitogen/__pycache__/__init__.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe5121d11346adfb443bf6ff3ad51ccca0c50bb0 GIT binary patch literal 2215 zcmZ`)%Wm8@6qQDrr)%fYcLNmM*#kJ57Fl(W1P0P1Fp$_m;zcoCNVLeA;aDP7k{Ul? z?*hb|Zu$lBGT+cY;kGOPLV*Ikq%<>jfog^nU-xzH;gwil?|JZaW`B-6wQIo(ZYmHj8HEVzDkmi+7!qOjge{GQ#?T#Cy zO*-%WQICZ0yngrWkKR5o2rp1JElw+l-+=ex#kp1U{keU)SmLf+{YeQk-R{h?J7?2~rwM zT^3QVA6!Fx-n9+)x(nTb^r7D~_g?QF?7w`z7r%MC|6=#m?%qoi?(V(VPaCkkc&yZ- zsrxYb;C=8RYyj$g=b~}mfUpBxhMw0$@H`WIPC27IrHIf}NUViKg=9IF44f+!9hVsz z!{V`I+B!;@O0gvPFr!FQrJWv?N>*qU_1tW?!nDE?w271|wT+Ia0B|xcd74-#2puyH z$g=A6Fc)N)#Bq)pkK@ECf~7J8?68!WPpEQ^I1O7;l#r-?g0w(@I^%P3WR>MPB@Ant zoq|E8r%unXI31=LR%*CNU{pC5y>)65lnSo2EK|({N0<}vZZ)66LI%obWgRZ$8@#q! zZ8R&TrDmCmWXWS|I<9<7A_Raq761s2SXRnfa?TL}fpGyFd{P5AEU?5ma6+O?OyZ2r zX*P6+ZPH$e$;4)&!08d5P~^5P4iLncO-f18oZ;p1|8vu7)*>FWj3&439tx4!w80kG zSsC6PyO;^qc$gf>k|x`T{#LR%&M4Q`14XqZ$t~MhYq^5x7Vgo~NMcC`ECU@CYD*P2 zGZUc$doNQAyi{--2Rbj{`q>avO@vfZ$UZ>;nLGu?0T{UAsD;j6bqd)PV}y|^Q%g)s za7dYBi;K?@F@59WrwIav6s305b4r(<=_%MEHFVEUa9(6|SlxhO{kAr%5`yZ=v6QZt zB>)IiQQZueRbmmGbf+RsPxMO3PHhf&&iGb;Y1Jxs zOZEM^t^5o5*^Sw(S#?4tPZo|7X>t1>!_}%wCcLK`0TU|T%t^%ld%{|juR2ElF`@U(qYjf7Y zMd3i|e`b1c##Nj+HX927;VJ^T&H7^Q?zjn|R#J19a$5)EC5=t1;$ND2r$QW=JO3#i{e}r`tuXD{`lp8JE-Qk(1DJjC{@xknTo2MgDj>TeBUBDB zR}36`C}|-Ewd}(mqDRqBi`mUDm}wtNfPu=H&BS-m_59H9z%TTB@Z0cPe#5o!pR+G5 zeWJ&ifYhs}Q7tLI0jYijZZJgZsh1)TWv3 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/__pycache__/core.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/core.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f48d3fc5b01bc118f09151f187230c454b4963f9 GIT binary patch literal 127909 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/__pycache__/master.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/master.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbbd28036e539b46656d15ec9c72c611cb5c5092 GIT binary patch literal 44260 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 B$yERV literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/__pycache__/minify.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/minify.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3abfe4b6f6741492c38fa75a4b19ac45fdf34de GIT binary patch literal 2883 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

DRQk-vy%>59;=2xP8wy1vr?OPa<~K#muty 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 Km&fmz82>-PmI*!p literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/__pycache__/select.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/select.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..036ccab786c840250eec8ca60602a64b9764b0de GIT binary patch literal 10624 zcmb_i-E-X5bzfjVxLi>bEz9yp92xPKxG}Yo?WA^DTeYLuiKdOxN@_f&>%oG1FG-MC zV10oV#T|Oat(?cacluJPFCAwluYK%m|A6*sU)z^HcKVif#{K=y1+ds9l{lGT2oS*i zI$!sk-#OUi?AcBY$EAP#clFA(TJ1mhP(2!Wcn@dFi>fIns%<$xswr31zpSaccJ|%< z`h)sb9q$^diFZxiz;hGNEwzN_C3$Ws=T5EL{x8f@yEWjodN_D^4`;f9i^dA7w<Q zciQ~Gn8e)b)~9XXkAtE1{b|Sdhlv_TykGJC&&EMyTfVB97N(HPudiPn#@W@u)j^{4 zP7*0?t_E=$Zb$lx(WB(*FwBx&9bdWDd$ae)#naVPM|zO;Mn}fQOfQlu3ZY$VIP&K4 z#6N>>Gvc@E%H3+2j;vGIdO8 zxV&lNGF|e0l?=diD^12`pr;KLWWkJOd$+{?ZCqZ(nev9jHyZ7Rygk7H7kQ6e{~4DP z40KgfwZ3$=ni~34?JsK&oUJ9btU6!Tw%Y29TEYKiy}Z>?XVoh1&!}^14gXiv6Y4zv zpVeo#R@Ia00`AYLr?%GAMfEi9pHR53QF-b+ z`2VzeQN4u!�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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/__pycache__/service.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..038e4d9687a9027296d948104be1737d99de017a GIT binary patch literal 35617 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 literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/__pycache__/utils.cpython-310.pyc b/mitogen-0.3.9/mitogen/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aae527386a8b6d9a486f5055e568226795fb219e GIT binary patch literal 6501 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 Yg=)2Yw|uSmS`nK>*+2NUT%EoB|A8MHWdHyG literal 0 HcmV?d00001 diff --git a/mitogen-0.3.9/mitogen/buildah.py b/mitogen-0.3.9/mitogen/buildah.py new file mode 100644 index 0000000..7a1e3f8 --- /dev/null +++ b/mitogen-0.3.9/mitogen/buildah.py @@ -0,0 +1,72 @@ +# 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.parent + + +LOG = logging.getLogger(__name__) + + +class Options(mitogen.parent.Options): + container = None + username = None + buildah_path = 'buildah' + + def __init__(self, container=None, buildah_path=None, username=None, + **kwargs): + super(Options, self).__init__(**kwargs) + assert container is not None + self.container = container + if buildah_path: + self.buildah_path = buildah_path + if username: + self.username = username + + +class Connection(mitogen.parent.Connection): + options_class = Options + child_is_immediate_subprocess = False + + # TODO: better way of capturing errors such as "No such container." + create_child_args = { + 'merge_stdio': True + } + + def _get_name(self): + return u'buildah.' + self.options.container + + def get_boot_command(self): + args = [self.options.buildah_path, 'run'] + if self.options.username: + args += ['--user=' + self.options.username] + args += ['--', self.options.container] + return args + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/compat/__init__.py b/mitogen-0.3.9/mitogen/compat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitogen-0.3.9/mitogen/compat/pkgutil.py b/mitogen-0.3.9/mitogen/compat/pkgutil.py new file mode 100644 index 0000000..15eb2af --- /dev/null +++ b/mitogen-0.3.9/mitogen/compat/pkgutil.py @@ -0,0 +1,594 @@ +"""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.exc_info()[1] + 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.3.9/mitogen/compat/tokenize.py b/mitogen-0.3.9/mitogen/compat/tokenize.py new file mode 100644 index 0000000..0473c6a --- /dev/null +++ b/mitogen-0.3.9/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.3.9/mitogen/core.py b/mitogen-0.3.9/mitogen/core.py new file mode 100644 index 0000000..cdfbbcd --- /dev/null +++ b/mitogen-0.3.9/mitogen/core.py @@ -0,0 +1,4196 @@ +# 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 sys +try: + import _frozen_importlib_external +except ImportError: + pass +else: + class MonkeyPatchedPathFinder(_frozen_importlib_external.PathFinder): + """ + Meta path finder for sys.path and package __path__ attributes. + + Patched for https://github.com/python/cpython/issues/115911. + """ + @classmethod + def _path_importer_cache(cls, path): + if path == '': + try: + path = _frozen_importlib_external._os.getcwd() + except (FileNotFoundError, PermissionError): + return None + return super()._path_importer_cache(path) + + if sys.version_info[:2] <= (3, 12): + for i, mpf in enumerate(sys.meta_path): + if mpf is _frozen_importlib_external.PathFinder: + sys.meta_path[i] = MonkeyPatchedPathFinder + del i, mpf + + +import binascii +import collections +import encodings.latin_1 +import encodings.utf_8 +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 syslog +import threading +import time +import traceback +import types +import warnings +import weakref +import zlib + +try: + # Python >= 3.4, PEP 451 ModuleSpec API + import importlib.machinery + import importlib.util +except ImportError: + # Python < 3.4, PEP 302 Import Hooks + 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) + +# str.encode() may take import lock. Deadlock possible if broker calls +# .encode() on behalf of thread currently waiting for module. +LATIN1_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 +STUB_CALL_SERVICE = 111 + +#: 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, _ = encodings.utf_8.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) + + +# Documented in api.rst to work around Sphinx limitation. +now = getattr(time, 'monotonic', time.time) + + +# 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):] + + +def threading__current_thread(): + try: + return threading.current_thread() # Added in Python 2.6+ + except AttributeError: + return threading.currentThread() # Deprecated in Python 3.10+ + + +def threading__thread_name(thread): + try: + return thread.name # Added in Python 2.6+ + except AttributeError: + return thread.getName() # Deprecated in Python 3.10+ + + +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(context_id): + return ( + (context_id == mitogen.context_id) or + (context_id in mitogen.parent_ids) + ) + +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 _has_parent_authority(msg.auth_id) + + +def _signals(obj, signal): + return ( + obj.__dict__ + .setdefault('_signals', {}) + .setdefault(signal, []) + ) + + +def listen(obj, name, func): + """ + Arrange for `func()` to be invoked when signal `name` is fired on `obj`. + """ + _signals(obj, name).append(func) + + +def unlisten(obj, name, func): + """ + Remove `func()` from the list of functions invoked when signal `name` is + fired by `obj`. + + :raises ValueError: + `func()` was not on the list. + """ + _signals(obj, name).remove(func) + + +def fire(obj, name, *args, **kwargs): + """ + Arrange for `func(*args, **kwargs)` to be invoked for every function + registered for signal `name` on `obj`. + """ + for func in _signals(obj, name): + func(*args, **kwargs) + + +def takes_econtext(func): + """ + Decorator that marks a function or class method to automatically receive a + kwarg named `econtext`, referencing the + :class:`mitogen.core.ExternalContext` active in the context in which the + function is being invoked in. The decorator is only meaningful when the + function is invoked via :data:`CALL_FUNCTION `. + + When the function is invoked directly, `econtext` must still be passed to + it explicitly. + """ + func.mitogen_takes_econtext = True + return func + + +def takes_router(func): + """ + Decorator that marks a function or class method to automatically receive a + kwarg named `router`, referencing the :class:`mitogen.core.Router` active + in the context in which the function is being invoked in. The decorator is + only meaningful when the function is invoked via :data:`CALL_FUNCTION + `. + + When the function is invoked directly, `router` must still be passed to it + explicitly. + """ + 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, 'fd %r <= 2' % (fd,) + 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 * now()), + '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, ['']) + + +def pipe(): + """ + Create a UNIX pipe pair using :func:`os.pipe`, wrapping the returned + descriptors in Python file objects in order to manage their lifetime and + ensure they are closed when their last reference is discarded and they have + not been closed explicitly. + """ + rfd, wfd = os.pipe() + return ( + os.fdopen(rfd, 'rb', 0), + os.fdopen(wfd, 'wb', 0) + ) + + +def iter_split(buf, delim, func): + """ + Invoke `func(s)` for each `delim`-delimited chunk in the potentially large + `buf`, avoiding intermediate lists and quadratic string operations. Return + the trailing undelimited portion of `buf`, or any unprocessed portion of + `buf` after `func(s)` returned :data:`False`. + + :returns: + `(trailer, cont)`, where `cont` is :data:`False` if the last call to + `func(s)` returned :data:`False`. + """ + dlen = len(delim) + start = 0 + cont = True + while cont: + nl = buf.find(delim, start) + if nl == -1: + break + cont = not func(buf[start:nl]) is False + start = nl + dlen + return buf[start:], cont + + +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 + + HEADER_FMT = '>hLLLLLL' + HEADER_LEN = struct.calcsize(HEADER_FMT) + HEADER_MAGIC = 0x4d49 # 'MI' + + 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), 'Message data is not Bytes' + + def pack(self): + return ( + struct.pack(self.HEADER_FMT, self.HEADER_MAGIC, self.dst_id, + self.src_id, self.auth_id, self.handle, + self.reply_to or 0, len(self.data)) + + self.data + ) + + 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'], _ = encodings.utf_8.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('dropping reply to message with no return address: %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. + try: + obj = unpickler.load() + except: + LOG.error('raw pickle was: %r', self.data) + raise + 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 mitogen.core.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 or missing router') + 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 function invoked as `notify(receiver)` after a + #: message has been received. The function is invoked on :class:`Broker` + #: thread, therefore it must not block. Used by + #: :class:`mitogen.select.Select` to efficiently implement waiting on + #: multiple event sources. + 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 size(self): + """ + Return the number of items currently buffered. + + As with :class:`Queue.Queue`, `0` may be returned even though a + subsequent call to :meth:`get` will succeed, since a message may be + posted at any moment between :meth:`size` and :meth:`get`. + + As with :class:`Queue.Queue`, `>0` 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:`size` and :meth:`get`. + + :raises LatchError: + The underlying latch has already been marked closed. + """ + return self._latch.size() + + def empty(self): + """ + Return `size() == 0`. + + .. deprecated:: 0.2.8 + Use :meth:`size` instead. + + :raises LatchError: + The latch has already been marked closed. + """ + 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. + + .. deprecated:: 0.2.0 + 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 = [ + 'buildah', + 'compat', + 'debug', + 'doas', + 'docker', + 'kubectl', + 'fakessh', + 'fork', + 'jail', + 'lxc', + 'lxd', + 'master', + 'minify', + 'os_fork', + 'parent', + 'podman', + '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__', + + # On some Python releases (e.g. 3.8, 3.9) the subprocess module tries + # to import of this Windows-only builtin module. + 'msvcrt', + + # Python 2.x module that was renamed to _thread in 3.x. + # This entry avoids a roundtrip on 2.x -> 3.x. + '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._log = logging.getLogger('mitogen.importer') + 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' + + @staticmethod + def _loader_from_module(module, default=None): + """Return the loader for a module object.""" + try: + return module.__spec__.loader + except AttributeError: + pass + try: + return module.__loader__ + except AttributeError: + pass + return default + + 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() + + # For a module inside a package (e.g. pkg_a.mod_b) use the search path + # of that package (e.g. ['/usr/lib/python3.11/site-packages/pkg_a']). + parent, _, modname = str_rpartition(fullname, '.') + if parent: + path = sys.modules[parent].__path__ + else: + path = None + + # For a top-level module search builtin modules, frozen modules, + # system specific locations (e.g. Windows registry, site-packages). + # Otherwise use search path of the parent package. + # Works for both stdlib modules & third-party modules. + # If the search is unsuccessful then raises ImportError. + fp, pathname, description = imp.find_module(modname, path) + if fp: + fp.close() + + def find_module(self, fullname, path=None): + """ + Return a loader (ourself) or None, for the module with fullname. + + Implements importlib.abc.MetaPathFinder.find_module(). + Deprecrated in Python 3.4+, replaced by find_spec(). + Raises ImportWarning in Python 3.10+. + Removed in Python 3.12. + + fullname Fully qualified module name, e.g. "os.path". + path __path__ of parent packge. None for a top level module. + """ + if hasattr(_tls, 'running'): + return None + + _tls.running = True + try: + #_v and self._log.debug('Python requested %r', fullname) + fullname = to_text(fullname) + pkgname, _, suffix = str_rpartition(fullname, '.') + pkg = sys.modules.get(pkgname) + if pkgname and getattr(pkg, '__loader__', None) is not self: + self._log.debug('%s is submodule of a locally loaded package', + fullname) + return None + + if pkgname and suffix not in self._present.get(pkgname, ()): + self._log.debug('%s has no submodule %s', pkgname, suffix) + 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 self._log.debug('%r is available locally', fullname) + except ImportError: + _vv and self._log.debug('we will try to load %r', fullname) + return self + finally: + del _tls.running + + def find_spec(self, fullname, path, target=None): + """ + Return a `ModuleSpec` for module with `fullname` if we will load it. + Otherwise return `None`, allowing other finders to try. + + fullname Fully qualified name of the module (e.g. foo.bar.baz) + path Path entries to search. None for a top-level module. + target Existing module to be reloaded (if any). + + Implements importlib.abc.MetaPathFinder.find_spec() + Python 3.4+. + """ + # Presence of _tls.running indicates we've re-invoked importlib. + # Abort early to prevent infinite recursion. See below. + if hasattr(_tls, 'running'): + return None + + log = self._log.getChild('find_spec') + + if fullname.endswith('.'): + return None + + pkgname, _, modname = fullname.rpartition('.') + if pkgname and modname not in self._present.get(pkgname, ()): + log.debug('Skipping %s. Parent %s has no submodule %s', + fullname, pkgname, modname) + return None + + pkg = sys.modules.get(pkgname) + pkg_loader = self._loader_from_module(pkg) + if pkgname and pkg_loader is not self: + log.debug('Skipping %s. Parent %s was loaded by %r', + fullname, pkgname, pkg_loader) + return None + + # #114: whitelisted prefixes override any system-installed package. + if self.whitelist != ['']: + if any(s and fullname.startswith(s) for s in self.whitelist): + log.debug('Handling %s. It is whitelisted', fullname) + return importlib.machinery.ModuleSpec(fullname, loader=self) + + if fullname == '__main__': + log.debug('Handling %s. A special case', fullname) + return importlib.machinery.ModuleSpec(fullname, loader=self) + + # Re-invoke the import machinery to allow other finders to try. + # Set a guard, so we don't infinitely recurse. See top of this method. + _tls.running = True + try: + spec = importlib.util._find_spec(fullname, path, target) + finally: + del _tls.running + + if spec: + log.debug('Skipping %s. Available as %r', fullname, spec) + return spec + + log.debug('Handling %s. Unavailable locally', fullname) + return importlib.machinery.ModuleSpec(fullname, loader=self) + + 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 self._log.debug('received %s', 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 self._log.debug('existing request for %s in flight', + fullname) + funcs.append(callback) + else: + _v and self._log.debug('sending new %s request to parent', + fullname) + self._callbacks[fullname] = [callback] + self._context.send( + Message(data=b(fullname), handle=GET_MODULE) + ) + finally: + self._lock.release() + + if present: + callback() + + def create_module(self, spec): + """ + Return a module object for the given ModuleSpec. + + Implements PEP-451 importlib.abc.Loader API introduced in Python 3.4. + Unlike Loader.load_module() this shouldn't populate sys.modules or + set module attributes. Both are done by Python. + """ + self._log.debug('Creating module for %r', spec) + + # FIXME Should this be done in find_spec()? Can it? + self._refuse_imports(spec.name) + + # FIXME "create_module() should properly handle the case where it is + # called more than once for the same spec/module." -- PEP-451 + event = threading.Event() + self._request_module(spec.name, callback=event.set) + event.wait() + + # 0:fullname 1:pkg_present 2:path 3:compressed 4:related + _, pkg_present, path, _, _ = self._cache[spec.name] + + if path is None: + raise ImportError(self.absent_msg % (spec.name)) + + spec.origin = self.get_filename(spec.name) + if pkg_present is not None: + # TODO Namespace packages + spec.submodule_search_locations = [] + self._present[spec.name] = pkg_present + + module = types.ModuleType(spec.name) + # FIXME create_module() shouldn't initialise module attributes + module.__file__ = spec.origin + return module + + def exec_module(self, module): + """ + Execute the module to initialise it. Don't return anything. + + Implements PEP-451 importlib.abc.Loader API, introduced in Python 3.4. + """ + name = module.__spec__.name + origin = module.__spec__.origin + self._log.debug('Executing %s from %s', name, origin) + source = self.get_source(name) + try: + # Compile the source into a code object. Don't add any __future__ + # flags and don't inherit any from this module. + # FIXME Should probably be exposed as get_code() + code = compile(source, origin, 'exec', flags=0, dont_inherit=True) + except SyntaxError: + # FIXME Why is this LOG, rather than self._log? + LOG.exception('while importing %r', name) + raise + + exec(code, module.__dict__) + + def load_module(self, fullname): + """ + Return the loaded module specified by fullname. + + Implements importlib.abc.Loader.load_module(). + Deprecated in Python 3.4+, replaced by create_module() & exec_module(). + """ + fullname = to_text(fullname) + _v and self._log.debug('requesting %s', fullname) + self._refuse_imports(fullname) + + event = threading.Event() + self._request_module(fullname, event.set) + event.wait() + + # 0:fullname 1:pkg_present 2:path 3:compressed 4:related + _, pkg_present, path, _, _ = self._cache[fullname] + if path is None: + raise ModuleNotFoundError(self.absent_msg % (fullname,)) + + 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__, _ = encodings.utf_8.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)') + + # #590: if a module replaces itself in sys.modules during import, below + # is necessary. This matches PyImport_ExecCodeModuleEx() + return sys.modules.get(fullname, 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): + """ + A :class:`logging.Handler` subclass that arranges for :data:`FORWARD_LOG` + messages to be sent to a parent context in response to logging messages + generated by the current context. This is installed by default in child + contexts during bootstrap, so that :mod:`logging` events can be viewed and + managed centrally in the master process. + + The handler is initially *corked* after construction, such that it buffers + messages until :meth:`uncork` is called. This allows logging to be + installed prior to communication with the target being available, and + avoids any possible race where early log messages might be dropped. + + :param mitogen.core.Context context: + The context to send log messages towards. At present this is always + the master process. + """ + def __init__(self, context): + logging.Handler.__init__(self) + self.context = context + self.local = threading.local() + self._buffer = [] + # Private synchronization is needed while corked, to ensure no + # concurrent call to _send() exists during uncork(). + self._buffer_lock = threading.Lock() + + 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._buffer_lock.acquire() + try: + self._send = self.context.send + for msg in self._buffer: + self._send(msg) + self._buffer = None + finally: + self._buffer_lock.release() + + def _send(self, msg): + self._buffer_lock.acquire() + try: + if self._buffer is None: + # uncork() may run concurrent to _send() + self._send(msg) + else: + self._buffer.append(msg) + finally: + self._buffer_lock.release() + + def emit(self, rec): + """ + Send a :data:`FORWARD_LOG` message towards the target context. + """ + 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 Stream(object): + """ + A :class:`Stream` is one readable and optionally one writeable file + descriptor (represented by :class:`Side`) aggregated alongside an + associated :class:`Protocol` that knows how to respond to IO readiness + events for those descriptors. + + Streams are registered with :class:`Broker`, and callbacks are invoked on + the broker thread in response to IO activity. When registered using + :meth:`Broker.start_receive` or :meth:`Broker._start_transmit`, the broker + may call any of :meth:`on_receive`, :meth:`on_transmit`, + :meth:`on_shutdown` or :meth:`on_disconnect`. + + It is expected that the :class:`Protocol` associated with a stream will + change over its life. For example during connection setup, the initial + protocol may be :class:`mitogen.parent.BootstrapProtocol` that knows how to + enter SSH and sudo passwords and transmit the :mod:`mitogen.core` source to + the target, before handing off to :class:`MitogenProtocol` when the target + process is initialized. + + Streams connecting to children are in turn aggregated by + :class:`mitogen.parent.Connection`, which contains additional logic for + managing any child process, and a reference to any separate ``stderr`` + :class:`Stream` connected to that process. + """ + #: 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 + + #: A :class:`Protocol` representing the protocol active on the stream. + protocol = None + + #: In parents, the :class:`mitogen.parent.Connection` instance. + conn = None + + #: The stream name. This is used in the :meth:`__repr__` output in any log + #: messages, it may be any descriptive string. + name = u'default' + + def set_protocol(self, protocol): + """ + Bind a :class:`Protocol` to this stream, by updating + :attr:`Protocol.stream` to refer to this stream, and updating this + stream's :attr:`Stream.protocol` to the refer to the protocol. Any + prior protocol's :attr:`Protocol.stream` is set to :data:`None`. + """ + if self.protocol: + self.protocol.stream = None + self.protocol = protocol + self.protocol.stream = self + + def accept(self, rfp, wfp): + """ + Attach a pair of file objects to :attr:`receive_side` and + :attr:`transmit_side`, after wrapping them in :class:`Side` instances. + :class:`Side` will call :func:`set_nonblock` and :func:`set_cloexec` + on the underlying file descriptors during construction. + + The same file object may be used for both sides. The default + :meth:`on_disconnect` is handles the possibility that only one + descriptor may need to be closed. + + :param file rfp: + The file object to receive from. + :param file wfp: + The file object to transmit to. + """ + self.receive_side = Side(self, rfp) + self.transmit_side = Side(self, wfp) + + def __repr__(self): + return "" % (self.name, id(self) & 0xffff,) + + def on_receive(self, broker): + """ + Invoked 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 they are registered using + :meth:`Broker.start_receive`, and the method must invoke + :meth:`on_disconnect` if reading produces an empty string. + + The default implementation reads :attr:`Protocol.read_size` bytes and + passes the resulting bytestring to :meth:`Protocol.on_receive`. If the + bytestring is 0 bytes, invokes :meth:`on_disconnect` instead. + """ + buf = self.receive_side.read(self.protocol.read_size) + if not buf: + LOG.debug('%r: empty read, disconnecting', self.receive_side) + return self.on_disconnect(broker) + + self.protocol.on_receive(broker, buf) + + def on_transmit(self, broker): + """ + Invoked 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 they are ever registerd with + :meth:`Broker._start_transmit`. + + The default implementation invokes :meth:`Protocol.on_transmit`. + """ + self.protocol.on_transmit(broker) + + def on_shutdown(self, broker): + """ + Invoked by :meth:`Broker.shutdown` to allow the stream time to + gracefully shutdown. + + The default implementation emits a ``shutdown`` signal before + invoking :meth:`on_disconnect`. + """ + fire(self, 'shutdown') + self.protocol.on_shutdown(broker) + + def on_disconnect(self, broker): + """ + Invoked by :class:`Broker` to force disconnect the stream during + shutdown, invoked by the default :meth:`on_shutdown` implementation, + and usually invoked by any subclass :meth:`on_receive` implementation + in response to a 0-byte read. + + The base implementation fires a ``disconnect`` event, then closes + :attr:`receive_side` and :attr:`transmit_side` after unregistering the + stream from the broker. + """ + fire(self, 'disconnect') + self.protocol.on_disconnect(broker) + + +class Protocol(object): + """ + Implement the program behaviour associated with activity on a + :class:`Stream`. The protocol in use may vary over a stream's life, for + example to allow :class:`mitogen.parent.BootstrapProtocol` to initialize + the connected child before handing it off to :class:`MitogenProtocol`. A + stream's active protocol is tracked in the :attr:`Stream.protocol` + attribute, and modified via :meth:`Stream.set_protocol`. + + Protocols do not handle IO, they are entirely reliant on the interface + provided by :class:`Stream` and :class:`Side`, allowing the underlying IO + implementation to be replaced without modifying behavioural logic. + """ + stream_class = Stream + + #: The :class:`Stream` this protocol is currently bound to, or + #: :data:`None`. + stream = None + + #: The size of the read buffer used by :class:`Stream` when this is the + #: active protocol for the stream. + read_size = CHUNK_SIZE + + @classmethod + def build_stream(cls, *args, **kwargs): + stream = cls.stream_class() + stream.set_protocol(cls(*args, **kwargs)) + return stream + + def __repr__(self): + return '%s(%s)' % ( + self.__class__.__name__, + self.stream and self.stream.name, + ) + + def on_shutdown(self, broker): + _v and LOG.debug('%r: shutting down', self) + self.stream.on_disconnect(broker) + + def on_disconnect(self, broker): + # Normally both sides an FD, so it is important that tranmit_side is + # deregistered from Poller before closing the receive side, as pollers + # like epoll and kqueue unregister all events on FD close, causing + # subsequent attempt to unregister the transmit side to fail. + LOG.debug('%r: disconnecting', self) + broker.stop_receive(self.stream) + if self.stream.transmit_side: + broker._stop_transmit(self.stream) + + self.stream.receive_side.close() + if self.stream.transmit_side: + self.stream.transmit_side.close() + + +class DelimitedProtocol(Protocol): + """ + Provide a :meth:`Protocol.on_receive` implementation for protocols that are + delimited by a fixed string, like text based protocols. Each message is + passed to :meth:`on_line_received` as it arrives, with incomplete messages + passed to :meth:`on_partial_line_received`. + + When emulating user input it is often necessary to respond to incomplete + lines, such as when a "Password: " prompt is sent. + :meth:`on_partial_line_received` may be called repeatedly with an + increasingly complete message. When a complete message is finally received, + :meth:`on_line_received` will be called once for it before the buffer is + discarded. + + If :func:`on_line_received` returns :data:`False`, remaining data is passed + unprocessed to the stream's current protocol's :meth:`on_receive`. This + allows switching from line-oriented to binary while the input buffer + contains both kinds of data. + """ + #: The delimiter. Defaults to newline. + delimiter = b('\n') + _trailer = b('') + + def on_receive(self, broker, buf): + _vv and IOLOG.debug('%r.on_receive()', self) + stream = self.stream + self._trailer, cont = mitogen.core.iter_split( + buf=self._trailer + buf, + delim=self.delimiter, + func=self.on_line_received, + ) + + if self._trailer: + if cont: + self.on_partial_line_received(self._trailer) + else: + assert stream.protocol is not self, \ + 'stream protocol is no longer %r' % (self,) + stream.protocol.on_receive(broker, self._trailer) + + def on_line_received(self, line): + """ + Receive a line from the stream. + + :param bytes line: + The encoded line, excluding the delimiter. + :returns: + :data:`False` to indicate this invocation modified the stream's + active protocol, and any remaining buffered data should be passed + to the new protocol's :meth:`on_receive` method. + + Any other return value is ignored. + """ + pass + + def on_partial_line_received(self, line): + """ + Receive a trailing unterminated partial line from the stream. + + :param bytes line: + The encoded partial line. + """ + pass + + +class BufferedWriter(object): + """ + Implement buffered output while avoiding quadratic string operations. This + is currently constructed by each protocol, in future it may become fixed + for each stream instead. + """ + def __init__(self, broker, protocol): + self._broker = broker + self._protocol = protocol + self._buf = collections.deque() + self._len = 0 + + def write(self, s): + """ + Transmit `s` immediately, falling back to enqueuing it and marking the + stream writeable if no OS buffer space is available. + """ + if not self._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._protocol.stream.transmit_side.write(s) + if n: + if n == len(s): + return + s = s[n:] + except OSError: + pass + + self._broker._start_transmit(self._protocol.stream) + self._buf.append(s) + self._len += len(s) + + def on_transmit(self, broker): + """ + Respond to stream writeability by retrying previously buffered + :meth:`write` calls. + """ + if self._buf: + buf = self._buf.popleft() + written = self._protocol.stream.transmit_side.write(buf) + if not written: + _v and LOG.debug('disconnected during write to %r', self) + self._protocol.stream.on_disconnect(broker) + return + elif written != len(buf): + self._buf.appendleft(BufferType(buf, written)) + + _vv and IOLOG.debug('transmitted %d bytes to %r', written, self) + self._len -= written + + if not self._buf: + broker._stop_transmit(self._protocol.stream) + + +class Side(object): + """ + Represent one side of a :class:`Stream`. This allows unidirectional (e.g. + pipe) and bidirectional (e.g. socket) streams to operate identically. + + Sides are also responsible for tracking the open/closed state of the + underlying FD, preventing erroneous duplicate calls to :func:`os.close` due + to duplicate :meth:`Stream.on_disconnect` calls, which would otherwise risk + silently succeeding by closing an unrelated descriptor. For this reason, it + is crucial only one file object exists per unique descriptor. + + :param mitogen.core.Stream stream: + The stream this side is associated with. + :param object fp: + The file or socket object managing the underlying file descriptor. Any + object may be used that supports `fileno()` and `close()` methods. + :param bool cloexec: + If :data:`True`, the descriptor has its :data:`fcntl.FD_CLOEXEC` flag + enabled using :func:`fcntl.fcntl`. + :param bool keep_alive: + If :data:`True`, the continued existence of this side will extend the + shutdown grace period until it has been unregistered from the broker. + :param bool blocking: + If :data:`False`, the descriptor has its :data:`os.O_NONBLOCK` flag + enabled using :func:`fcntl.fcntl`. + """ + _fork_refs = weakref.WeakValueDictionary() + closed = False + + def __init__(self, stream, fp, cloexec=True, keep_alive=True, blocking=False): + #: The :class:`Stream` for which this is a read or write side. + self.stream = stream + # File or socket object responsible for the lifetime of its underlying + # file descriptor. + self.fp = fp + #: Integer file descriptor to perform IO on, or :data:`None` if + #: :meth:`close` has been called. This is saved separately from the + #: file object, since :meth:`file.fileno` cannot be called on it after + #: it has been closed. + self.fd = fp.fileno() + #: 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(self.fd) + if not blocking: + set_nonblock(self.fd) + + def __repr__(self): + return '' % ( + self.stream.name or repr(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 :meth:`file.close` on :attr:`fp` if it is not :data:`None`, + then set it to :data:`None`. + """ + _vv and IOLOG.debug('%r.close()', self) + if not self.closed: + self.closed = True + self.fp.close() + + 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 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: disconnected during read: %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: + # Don't touch the handle after close, it may be reused elsewhere. + return None + + written, disconnected = io_op(os.write, self.fd, s) + if disconnected: + LOG.debug('%r: disconnected during write: %s', self, disconnected) + return None + return written + + +class MitogenProtocol(Protocol): + """ + :class:`Protocol` implementing mitogen's :ref:`stream protocol + `. + """ + #: 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 + + #: Invoked as `on_message(stream, msg)` each message received from the + #: peer. + on_message = None + + def __init__(self, router, remote_id, auth_id=None, + local_id=None, parent_ids=None): + self._router = router + self.remote_id = remote_id + #: If not :data:`None`, :class:`Router` stamps this into + #: :attr:`Message.auth_id` of every message received on this stream. + self.auth_id = auth_id + + if parent_ids is None: + parent_ids = mitogen.parent_ids + if local_id is None: + local_id = mitogen.context_id + + self.is_privileged = ( + (remote_id in parent_ids) or + auth_id in ([local_id] + parent_ids) + ) + self.sent_modules = set(['mitogen', 'mitogen.core']) + self._input_buf = collections.deque() + self._input_buf_len = 0 + self._writer = BufferedWriter(router.broker, self) + + #: 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 on_receive(self, broker, buf): + """ + Handle the next complete message on the stream. Raise + :class:`StreamError` on failure. + """ + _vv and IOLOG.debug('%r.on_receive()', self) + 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 + + corrupt_msg = ( + '%s: 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 < Message.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( + Message.HEADER_FMT, + self._input_buf[0][:Message.HEADER_LEN], + ) + + if magic != Message.HEADER_MAGIC: + LOG.error(self.corrupt_msg, self.stream.name, self._input_buf[0][:2048]) + self.stream.on_disconnect(broker) + return False + + if msg_len > self._router.max_message_size: + LOG.error('%r: Maximum message size exceeded (got %d, max %d)', + self, msg_len, self._router.max_message_size) + self.stream.on_disconnect(broker) + return False + + total_len = msg_len + Message.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 - Message.HEADER_LEN + ) + return False + + start = Message.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.stream) + 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._writer._len + + def on_transmit(self, broker): + """ + Transmit buffered messages. + """ + _vv and IOLOG.debug('%r.on_transmit()', self) + self._writer.on_transmit(broker) + + def _send(self, msg): + _vv and IOLOG.debug('%r._send(%r)', self, msg) + self._writer.write(msg.pack()) + + 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): + """ + Disable :class:`Protocol` immediate disconnect behaviour. + """ + _v and LOG.debug('%r: shutting down', self) + + +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 mitogen.core.Router router: + Router to emit messages through. + :param int context_id: + Context ID. + :param str name: + Context name. + """ + name = None + remote_name = None + + def __init__(self, router, context_id, name=None): + self.router = router + self.context_id = context_id + if name: + self.name = to_text(name) + + def __reduce__(self): + return _unpickle_context, (self.context_id, self.name) + + def on_disconnect(self): + _v and LOG.debug('%r: disconnecting', 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. + """ + receiver = Receiver(self.router, persist=persist, respondent=self) + msg.dst_id = self.context_id + msg.reply_to = receiver.handle + + _v and LOG.debug('sending message to %r: %r', self, msg) + self.send(msg) + return receiver + + def call_service_async(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() + _v and LOG.debug('calling service %s.%s of %r, args: %r', + service_name, method_name, self, kwargs) + 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. + + 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. + + This implementation uses :func:`select.select` for wider platform support. + That is considered an implementation detail. Previous versions have used + :func:`select.poll`. Future versions may decide at runtime. + """ + SUPPORTED = True + + #: Increments on every poll(). Used to version _rfds and _wfds. + _generation = 1 + + def __init__(self): + self._rfds = {} + self._wfds = {} + + def __repr__(self): + return '%s' % (type(self).__name__,) + + 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 + + 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. + """ + #: The :class:`Poller` implementation to use. Instances are short lived so + #: prefer :class:`mitogen.parent.PollPoller` if it's available, otherwise + #: :class:`mitogen.core.Poller`. They don't need syscalls to create, + #: configure, or destroy. Replaced during import of :mod:`mitogen.parent`. + poller_class = Poller + + #: If not :data:`None`, a function invoked as `notify(latch)` after a + #: successful call to :meth:`put`. The function is invoked on the + #: :meth:`put` caller's thread, which may be the :class:`Broker` thread, + #: therefore it must not block. Used by :class:`mitogen.select.Select` to + #: efficiently implement waiting on multiple event sources. + 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 size(self): + """ + Return the number of items currently buffered. + + As with :class:`Queue.Queue`, `0` may be returned even though a + subsequent call to :meth:`get` will succeed, since a message may be + posted at any moment between :meth:`size` and :meth:`get`. + + As with :class:`Queue.Queue`, `>0` 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:`size` 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) + finally: + self._lock.release() + + def empty(self): + """ + Return `size() == 0`. + + .. deprecated:: 0.2.8 + Use :meth:`size` instead. + + :raises LatchError: + The latch has already been marked closed. + """ + return self.size() == 0 + + 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() + rsock.setblocking(False) + 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, fd=%d/%d)', + self, timeout, block, rsock.fileno(), wsock.fileno() + ) + + e = None + try: + 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] + + try: + got_cookie = rsock.recv(self.COOKIE_SIZE) + except socket.error: + e2 = sys.exc_info()[1] + if e2.args[0] == errno.EAGAIN: + e = TimeoutError() + else: + e = e2 + + self._cls_idle_socketpairs.append((rsock, wsock)) + if e: + raise e + + 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) + + wsock = None + 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()) + elif self.notify: + self.notify(self) + finally: + self._lock.release() + + if wsock: + self._wake(wsock, cookie) + + 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__thread_name(threading__current_thread()), + ) + + +class Waker(Protocol): + """ + :class:`Protocol` implementing the `UNIX self-pipe trick`_. Used to wake + :class:`Broker` when another thread needs to modify its state, by enqueing + a function call to run on the :class:`Broker` thread. + + .. _UNIX self-pipe trick: https://cr.yp.to/docs/selfpipe.html + """ + read_size = 1 + broker_ident = None + + @classmethod + def build_stream(cls, broker): + stream = super(Waker, cls).build_stream(broker) + stream.accept(*pipe()) + return stream + + def __init__(self, broker): + self._broker = broker + self._deferred = collections.deque() + + def __repr__(self): + return 'Waker(fd=%r/%r)' % ( + self.stream.receive_side and self.stream.receive_side.fd, + self.stream.transmit_side and self.stream.transmit_side.fd, + ) + + @property + def keep_alive(self): + """ + Prevent immediate Broker shutdown while deferred functions remain. + """ + return len(self._deferred) + + def on_receive(self, broker, buf): + """ + 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) + while True: + try: + func, args, kwargs = self._deferred.popleft() + except IndexError: + return + + try: + func(*args, **kwargs) + except Exception: + LOG.exception('defer() crashed: %r(*%r, **%r)', + func, args, kwargs) + 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.stream.transmit_side.write(b(' ')) + except OSError: + e = sys.exc_info()[1] + if e.args[0] not in (errno.EBADF, errno.EWOULDBLOCK): + 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.stream.transmit_side.fd) + self._deferred.append((func, args, kwargs)) + self._wake() + + +class IoLoggerProtocol(DelimitedProtocol): + """ + Attached to one end of a socket pair whose other end overwrites one of the + standard ``stdout`` or ``stderr`` file descriptors in a child context. + Received data is split up into lines, decoded as UTF-8 and logged to the + :mod:`logging` package as either the ``stdout`` or ``stderr`` logger. + + Logging in child contexts is in turn forwarded to the master process using + :class:`LogHandler`. + """ + @classmethod + def build_stream(cls, name, dest_fd): + """ + Even though the file descriptor `dest_fd` will hold the opposite end of + the socket open, we must keep a separate dup() of it (i.e. wsock) in + case some code decides to overwrite `dest_fd` later, which would + prevent break :meth:`on_shutdown` from calling :meth:`shutdown() + ` on it. + """ + rsock, wsock = socket.socketpair() + os.dup2(wsock.fileno(), dest_fd) + stream = super(IoLoggerProtocol, cls).build_stream(name) + stream.name = name + stream.accept(rsock, wsock) + return stream + + def __init__(self, name): + 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[:] + + def on_shutdown(self, broker): + """ + Shut down the write end of the socket, preventing any further writes to + it by this process, or subprocess that inherited it. This allows any + remaining kernel-buffered data to be drained during graceful shutdown + without the buffer continuously refilling due to some out of control + child process. + """ + _v and LOG.debug('%r: shutting down', self) + if not IS_WSL: + # #333: WSL generates invalid readiness indication on shutdown(). + # This modifies the *kernel object* inherited by children, causing + # EPIPE on subsequent writes to any dupped FD in any process. The + # read side can then drain completely of prior buffered data. + self.stream.transmit_side.fp.shutdown(socket.SHUT_WR) + self.stream.transmit_side.close() + + def on_line_received(self, line): + """ + Decode the received line as UTF-8 and pass it to the logging framework. + """ + self._log.info('%s', line.decode('utf-8', 'replace')) + + +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. + """ + #: The :class:`mitogen.core.Context` subclass to use when constructing new + #: :class:`Context` objects in :meth:`myself` and :meth:`context_by_id`. + #: Permits :class:`Router` subclasses to extend the :class:`Context` + #: interface, as done in :class:`mitogen.parent.Router`. + 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 + + duplicate_handle_msg = 'cannot register a handle that 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_exit_msg = 'Broker has exitted' + no_route_msg = 'no route to %r, my ID is %r' + unidirectional_msg = ( + 'routing mode prevents forward of message from context %d to ' + 'context %d via context %d' + ) + + 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. + """ + if msg.is_dead: + return + + target_id_s, _, name = bytes_partition(msg.data, b(':')) + target_id = int(target_id_s, 10) + LOG.error('%r: deleting route to %s (%d)', + self, to_text(name), target_id) + 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() + + def _on_broker_exit(self): + """ + Called prior to broker exit, informs callbacks registered with + :meth:`add_handler` the connection is dead. + """ + _v and LOG.debug('%r: broker has exitted', 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. Since + :class:`Context` is serializable, this is convenient to use in remote + function call parameter lists. + """ + 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): + """ + Return or construct a :class:`Context` given its ID. An internal + mapping of ID to the canonical :class:`Context` representing that ID, + so that :ref:`signals` can be raised. + + This may be called from any thread, lookup and construction are atomic. + + :param int context_id: + The context ID to look up. + :param int via_id: + If the :class:`Context` does not already exist, set its + :attr:`Context.via` to the :class:`Context` matching this ID. + :param bool create: + If the :class:`Context` does not already exist, create it. + :param str name: + If the :class:`Context` does not already exist, set its name. + + :returns: + :class:`Context`, or return :data:`None` if `create` is + :data:`False` and no :class:`Context` previously existed. + """ + 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('%s: registering %r to stream %r', + self, 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. If the parent is disconnected, + or when running in the master context, return :data:`None` instead. + + This can be used from any thread, but its output is only meaningful + from the context of the :class:`Broker` thread, as disconnection or + replacement could happen in parallel on the broker thread at any + moment. + """ + 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 mitogen.core.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 + + 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 _maybe_send_dead(self, unreachable, msg, reason, *args): + """ + Send a dead message to either the original sender or the intended + recipient of `msg`, if the original sender was expecting a reply + (because its `reply_to` was set), otherwise assume the message is a + reply of some sort, and send the dead message to the original + destination. + + :param bool unreachable: + If :data:`True`, the recipient is known to be dead or routing + failed due to a security precaution, so don't attempt to fallback + to sending the dead message to the recipient if the original sender + did not include a reply address. + :param mitogen.core.Message msg: + Message that triggered the dead message. + :param str reason: + Human-readable error reason. + :param tuple args: + Elements to interpolate with `reason`. + """ + 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) + elif not unreachable: + self._async_route( + Message.dead( + dst_id=msg.dst_id, + handle=msg.handle, + reason=reason, + ) + ) + + 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(True, 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(True, msg, 'reply from unexpected context') + return + + if policy and not policy(msg, stream): + self._maybe_send_dead(True, 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(False, msg, self.too_large_msg % ( + self.max_message_size, + )) + return + + parent_stream = self._stream_by_id.get(mitogen.parent_id) + src_stream = self._stream_by_id.get(msg.src_id, parent_stream) + + # When the ingress stream is known, verify the message was received on + # the same as the stream we would expect to receive messages from the + # src_id and auth_id. This is like Reverse Path Filtering in IP, and + # ensures messages from a privileged context cannot be spoofed by a + # child. + if in_stream: + auth_stream = self._stream_by_id.get(msg.auth_id, parent_stream) + if in_stream != auth_stream: + LOG.error('%r: bad auth_id: got %r via %r, not %r: %r', + self, msg.auth_id, in_stream, auth_stream, msg) + return + + if msg.src_id != msg.auth_id and in_stream != src_stream: + LOG.error('%r: bad src_id: got %r via %r, not %r: %r', + self, msg.src_id, in_stream, src_stream, msg) + return + + # If the stream's MitogenProtocol has auth_id set, copy it to the + # message. This allows subtrees to become privileged by stamping a + # parent's context ID. It is used by mitogen.unix to mark client + # streams (like Ansible WorkerProcess) as having the same rights as + # the parent. + if in_stream.protocol.auth_id is not None: + msg.auth_id = in_stream.protocol.auth_id + if in_stream.protocol.on_message is not None: + in_stream.protocol.on_message(in_stream, msg) + + # Record the IDs the source ever communicated with. + in_stream.protocol.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 (not out_stream) and (parent_stream != src_stream or not in_stream): + # No downstream route exists. The message could be from a child or + # ourselves for a parent, in which case we must forward it + # upstream, or it could be from a parent for a dead child, in which + # case its src_id/auth_id would fail verification if returned to + # the parent, so in that case reply with a dead message instead. + out_stream = parent_stream + + if out_stream is None: + self._maybe_send_dead(True, msg, self.no_route_msg, + msg.dst_id, mitogen.context_id) + return + + if in_stream and self.unidirectional and not \ + (in_stream.protocol.is_privileged or + out_stream.protocol.is_privileged): + self._maybe_send_dead(True, msg, self.unidirectional_msg, + in_stream.protocol.remote_id, + out_stream.protocol.remote_id, + mitogen.context_id) + return + + out_stream.protocol._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 NullTimerList(object): + def get_timeout(self): + return None + + +class Broker(object): + """ + Responsible for handling I/O multiplexing in a private thread. + + **Note:** This somewhat limited core version is used by children. The + master subclass is documented below. + """ + poller_class = Poller + _waker = None + _thread = None + + # :func:`mitogen.parent._upgrade_broker` replaces this with + # :class:`mitogen.parent.TimerList` during upgrade. + timers = NullTimerList() + + #: 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.build_stream(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.protocol.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') + 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 not side.closed + 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 not side.closed + 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) > 0 or self.timers.get_timeout() is not None + + 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) + + timer_to = self.timers.get_timeout() + if timeout is None: + timeout = timer_to + elif timer_to is not None and timer_to < timeout: + timeout = timer_to + + #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) + if timer_to is not None: + self.timers.expire() + + 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('%r: force disconnecting %r', self, 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 = now() + self.shutdown_timeout + while self.keep_alive() and now() < deadline: + self._loop_once(max(0, deadline - now())) + + if self.keep_alive(): + LOG.error('%r: pending work still existed %d seconds after ' + 'shutdown began. This may be due to a timer that is yet ' + 'to expire, or a child connection that did not fully ' + 'shut down.', self, self.shutdown_timeout) + + 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.protocol.broker_ident = thread.get_ident() + try: + while self._alive: + self._loop_once() + + fire(self, 'before_shutdown') + fire(self, 'shutdown') + self._broker_shutdown() + except Exception: + e = sys.exc_info()[1] + LOG.exception('broker crashed') + syslog.syslog(syslog.LOG_ERR, 'broker crashed: %s' % (e,)) + syslog.closelog() # prevent test 'fd leak'. + + self._alive = False # Ensure _alive is consistent on crash. + self._exitted = True + self._broker_exit() + + def _broker_main(self): + try: + _profile_hook('mitogen.broker', self._do_broker_main) + finally: + # 'finally' to ensure _on_broker_exit() can always SIGTERM. + fire(self, 'exit') + + def shutdown(self): + """ + Request broker gracefully disconnect streams and stop. Safe to call + from any thread. + """ + _v and LOG.debug('%r: shutting down', 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(%04x)' % (id(self) & 0xffff,) + + +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. + """ + _service_recv = None + + def __repr__(self): + return 'Dispatcher' + + 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, + ) + #: The :data:`CALL_SERVICE` :class:`Receiver` that will eventually be + #: reused by :class:`mitogen.service.Pool`, should it ever be loaded. + #: This is necessary for race-free reception of all service requests + #: delivered regardless of whether the stub or real service pool are + #: loaded. See #547 for related sorrows. + Dispatcher._service_recv = Receiver( + router=econtext.router, + handle=CALL_SERVICE, + policy=has_parent_authority, + ) + self._service_recv.notify = self._on_call_service + listen(econtext.broker, 'shutdown', self._on_broker_shutdown) + + def _on_broker_shutdown(self): + if self._service_recv.notify == self._on_call_service: + self._service_recv.notify = None + 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('%r: dispatching %r', self, 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 _on_call_service(self, recv): + """ + Notifier for the :data:`CALL_SERVICE` receiver. This is called on the + :class:`Broker` thread for any service messages arriving at this + context, for as long as no real service pool implementation is loaded. + + In order to safely bootstrap the service pool implementation a sentinel + message is enqueued on the :data:`CALL_FUNCTION` receiver in order to + wake the main thread, where the importer can run without any + possibility of suffering deadlock due to concurrent uses of the + importer. + + Should the main thread be blocked indefinitely, preventing the import + from ever running, if it is blocked waiting on a service call, then it + means :mod:`mitogen.service` has already been imported and + :func:`mitogen.service.get_or_create_pool` has already run, meaning the + service pool is already active and the duplicate initialization was not + needed anyway. + + #547: This trickery is needed to avoid the alternate option of spinning + a temporary thread to import the service pool, which could deadlock if + a custom import hook executing on the main thread (under the importer + lock) would block waiting for some data that was in turn received by a + service. Main thread import lock can't be released until service is + running, service cannot satisfy request until import lock is released. + """ + self.recv._on_receive(Message(handle=STUB_CALL_SERVICE)) + + def _init_service_pool(self): + import mitogen.service + mitogen.service.get_or_create_pool(router=self.econtext.router) + + def _dispatch_calls(self): + for msg in self.recv: + if msg.handle == STUB_CALL_SERVICE: + if msg.src_id == mitogen.context_id: + self._init_service_pool() + continue + + chain_id, ret = self._dispatch_one(msg) + _v and LOG.debug('%r: %r -> %r', self, 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. + + This class contains the main program implementation for new children. It is + responsible for setting up everything about the process environment, import + hooks, standard IO redirection, logging, configuring a :class:`Router` and + :class:`Broker`, and finally arranging for :class:`Dispatcher` to take over + the main thread after initialization is complete. + + .. 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:: importer + + The :class:`mitogen.core.Importer` instance. + + .. attribute:: stdout_log + + The :class:`IoLogger` connected to :data:`sys.stdout`. + + .. attribute:: stderr_log + + The :class:`IoLogger` connected to :data:`sys.stderr`. + """ + 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) + + def _on_shutdown_msg(self, msg): + if not msg.is_dead: + _v and LOG.debug('shutdown request from context %d', msg.src_id) + 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('parent stream is gone, dying.') + 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(stream.protocol.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.unidirectional = self.config['unidirectional'] + self.router.add_handler( + fn=self._on_shutdown_msg, + handle=SHUTDOWN, + 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) + in_fp = os.fdopen(os.dup(in_fd), 'rb', 0) + os.close(in_fd) + + out_fp = os.fdopen(os.dup(self.config.get('out_fd', 1)), 'wb', 0) + self.stream = MitogenProtocol.build_stream( + self.router, + parent_id, + local_id=self.config['context_id'], + parent_ids=self.config['parent_ids'] + ) + self.stream.accept(in_fp, out_fp) + self.stream.name = 'parent' + self.stream.receive_side.keep_alive = False + + listen(self.stream, 'disconnect', self._on_parent_disconnect) + listen(self.broker, 'exit', self._on_broker_exit) + + 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', 0) + 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 = types.ModuleType('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 stdio temporarily. In case of odd startup, + assume we may be allocated a standard handle. + """ + for stdfd, mode in ((0, os.O_RDONLY), (1, os.O_RDWR), (2, os.O_RDWR)): + fd = os.open('/dev/null', mode) + if fd != stdfd: + os.dup2(fd, stdfd) + os.close(fd) + + def _preserve_tty_fp(self): + """ + #481: when stderr is a TTY due to being started via tty_create_child() + or 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_fp = os.fdopen(os.dup(2), 'r+b', 0) + set_cloexec(self.reserve_tty_fp.fileno()) + except OSError: + pass + + def _setup_stdio(self): + self._preserve_tty_fp() + # 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.loggers = [] + for name, fd in (('stdout', 1), ('stderr', 2)): + log = IoLoggerProtocol.build_stream(name, fd) + self.broker.start_receive(log) + self.loggers.append(log) + + # 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() + + _v and LOG.debug('Python version is %s', sys.version) + _v and LOG.debug('Parent is context %r (%s); my ID is %r', + self.parent.context_id, self.parent.name, + 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()) + + sys.executable = os.environ.pop('ARGV0', sys.executable) + _v and LOG.debug('Recovered sys.executable: %r', sys.executable) + + if self.config.get('send_ec2', True): + self.stream.transmit_side.write(b('MITO002\n')) + self.broker._py24_25_compat() + self.log_handler.uncork() + 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() diff --git a/mitogen-0.3.9/mitogen/debug.py b/mitogen-0.3.9/mitogen/debug.py new file mode 100644 index 0000000..dbab550 --- /dev/null +++ b/mitogen-0.3.9/mitogen/debug.py @@ -0,0 +1,236 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +Basic signal handler for dumping thread stacks. +""" + +import difflib +import logging +import os +import gc +import signal +import sys +import threading +import time +import traceback + +import mitogen.core +import mitogen.parent + + +LOG = logging.getLogger(__name__) +_last = None + + +def enable_evil_interrupts(): + signal.signal(signal.SIGALRM, (lambda a, b: None)) + signal.setitimer(signal.ITIMER_REAL, 0.01, 0.01) + + +def disable_evil_interrupts(): + signal.setitimer(signal.ITIMER_REAL, 0, 0) + + +def _hex(n): + return '%08x' % n + + +def get_subclasses(klass): + """ + Rather than statically import every interesting subclass, forcing it all to + be transferred and potentially disrupting the debugged environment, + enumerate only those loaded in memory. Also returns the original class. + """ + stack = [klass] + seen = set() + while stack: + klass = stack.pop() + seen.add(klass) + stack.extend(klass.__subclasses__()) + return seen + + +def get_routers(): + return dict( + (_hex(id(router)), router) + for klass in get_subclasses(mitogen.core.Router) + for router in gc.get_referrers(klass) + if isinstance(router, mitogen.core.Router) + ) + + +def get_router_info(): + return { + 'routers': dict( + (id_, { + 'id': id_, + 'streams': len(set(router._stream_by_id.values())), + 'contexts': len(set(router._context_by_id.values())), + 'handles': len(router._handle_map), + }) + for id_, router in get_routers().items() + ) + } + + +def get_stream_info(router_id): + router = get_routers().get(router_id) + return { + 'streams': dict( + (_hex(id(stream)), ({ + 'name': stream.name, + 'remote_id': stream.remote_id, + 'sent_module_count': len(getattr(stream, 'sent_modules', [])), + 'routes': sorted(getattr(stream, 'routes', [])), + 'type': type(stream).__module__, + })) + for via_id, stream in router._stream_by_id.items() + ) + } + + +def format_stacks(): + name_by_id = dict( + (t.ident, t.name) + for t in threading.enumerate() + ) + + l = ['', ''] + for threadId, stack in sys._current_frames().items(): + l += ["# PID %d ThreadID: (%s) %s; %r" % ( + os.getpid(), + name_by_id.get(threadId, ''), + threadId, + stack, + )] + #stack = stack.f_back.f_back + + for filename, lineno, name, line in traceback.extract_stack(stack): + l += [ + 'File: "%s", line %d, in %s' % ( + filename, + lineno, + name + ) + ] + if line: + l += [' ' + line.strip()] + l += [''] + + l += ['', ''] + return '\n'.join(l) + + +def get_snapshot(): + global _last + + s = format_stacks() + snap = s + if _last: + snap += '\n' + diff = list(difflib.unified_diff( + a=_last.splitlines(), + b=s.splitlines(), + fromfile='then', + tofile='now' + )) + + if diff: + snap += '\n'.join(diff) + '\n' + else: + snap += '(no change since last time)\n' + _last = s + return snap + + +def _handler(*_): + fp = open('/dev/tty', 'w', 1) + fp.write(get_snapshot()) + fp.close() + + +def install_handler(): + signal.signal(signal.SIGUSR2, _handler) + + +def _logging_main(secs): + while True: + time.sleep(secs) + LOG.info('PERIODIC THREAD DUMP\n\n%s', get_snapshot()) + + +def dump_to_logger(secs=5): + th = threading.Thread( + target=_logging_main, + kwargs={'secs': secs}, + name='mitogen.debug.dump_to_logger', + ) + th.setDaemon(True) + th.start() + + +class ContextDebugger(object): + @classmethod + @mitogen.core.takes_econtext + def _configure_context(cls, econtext): + mitogen.parent.upgrade_router(econtext) + econtext.debugger = cls(econtext.router) + + def __init__(self, router): + self.router = router + self.router.add_handler( + func=self._on_debug_msg, + handle=mitogen.core.DEBUG, + persist=True, + policy=mitogen.core.has_parent_authority, + ) + mitogen.core.listen(router, 'register', self._on_stream_register) + LOG.debug('Context debugging configured.') + + def _on_stream_register(self, context, stream): + LOG.debug('_on_stream_register: sending configure() to %r', stream) + context.call_async(ContextDebugger._configure_context) + + def _on_debug_msg(self, msg): + if msg != mitogen.core._DEAD: + threading.Thread( + target=self._handle_debug_msg, + name='ContextDebuggerHandler', + args=(msg,) + ).start() + + def _handle_debug_msg(self, msg): + try: + method, args, kwargs = msg.unpickle() + msg.reply(getattr(self, method)(*args, **kwargs)) + except Exception: + e = sys.exc_info()[1] + msg.reply(mitogen.core.CallError(e)) diff --git a/mitogen-0.3.9/mitogen/doas.py b/mitogen-0.3.9/mitogen/doas.py new file mode 100644 index 0000000..5b212b9 --- /dev/null +++ b/mitogen-0.3.9/mitogen/doas.py @@ -0,0 +1,142 @@ +# 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 re + +import mitogen.core +import mitogen.parent + + +LOG = logging.getLogger(__name__) + +password_incorrect_msg = 'doas password is incorrect' +password_required_msg = 'doas password is required' + + +class PasswordError(mitogen.core.StreamError): + pass + + +class Options(mitogen.parent.Options): + username = u'root' + password = None + doas_path = 'doas' + password_prompt = u'Password:' + incorrect_prompts = ( + u'doas: authentication failed', # slicer69/doas + u'doas: Authorization failed', # openbsd/src + ) + + def __init__(self, username=None, password=None, doas_path=None, + password_prompt=None, incorrect_prompts=None, **kwargs): + super(Options, self).__init__(**kwargs) + if username is not None: + self.username = mitogen.core.to_text(username) + if password is not None: + self.password = mitogen.core.to_text(password) + if doas_path is not None: + self.doas_path = doas_path + if password_prompt is not None: + self.password_prompt = mitogen.core.to_text(password_prompt) + if incorrect_prompts is not None: + self.incorrect_prompts = [ + mitogen.core.to_text(p) + for p in incorrect_prompts + ] + + +class BootstrapProtocol(mitogen.parent.RegexProtocol): + password_sent = False + + def setup_patterns(self, conn): + prompt_pattern = re.compile( + re.escape(conn.options.password_prompt).encode('utf-8'), + re.I + ) + incorrect_prompt_pattern = re.compile( + u'|'.join( + re.escape(s) + for s in conn.options.incorrect_prompts + ).encode('utf-8'), + re.I + ) + + self.PATTERNS = [ + (incorrect_prompt_pattern, type(self)._on_incorrect_password), + ] + self.PARTIAL_PATTERNS = [ + (prompt_pattern, type(self)._on_password_prompt), + ] + + def _on_incorrect_password(self, line, match): + if self.password_sent: + self.stream.conn._fail_connection( + PasswordError(password_incorrect_msg) + ) + + def _on_password_prompt(self, line, match): + if self.stream.conn.options.password is None: + self.stream.conn._fail_connection( + PasswordError(password_required_msg) + ) + return + + if self.password_sent: + self.stream.conn._fail_connection( + PasswordError(password_incorrect_msg) + ) + return + + LOG.debug('sending password') + self.stream.transmit_side.write( + (self.stream.conn.options.password + '\n').encode('utf-8') + ) + self.password_sent = True + + +class Connection(mitogen.parent.Connection): + options_class = Options + diag_protocol_class = BootstrapProtocol + + create_child = staticmethod(mitogen.parent.hybrid_tty_create_child) + child_is_immediate_subprocess = False + + def _get_name(self): + return u'doas.' + self.options.username + + def stderr_stream_factory(self): + stream = super(Connection, self).stderr_stream_factory() + stream.protocol.setup_patterns(self) + return stream + + def get_boot_command(self): + bits = [self.options.doas_path, '-u', self.options.username, '--'] + return bits + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/docker.py b/mitogen-0.3.9/mitogen/docker.py new file mode 100644 index 0000000..48848c8 --- /dev/null +++ b/mitogen-0.3.9/mitogen/docker.py @@ -0,0 +1,83 @@ +# 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 Options(mitogen.parent.Options): + container = None + image = None + username = None + docker_path = u'docker' + + def __init__(self, container=None, image=None, docker_path=None, + username=None, **kwargs): + super(Options, self).__init__(**kwargs) + assert container or image + if container: + self.container = mitogen.core.to_text(container) + if image: + self.image = mitogen.core.to_text(image) + if docker_path: + self.docker_path = mitogen.core.to_text(docker_path) + if username: + self.username = mitogen.core.to_text(username) + + +class Connection(mitogen.parent.Connection): + options_class = Options + child_is_immediate_subprocess = False + + # TODO: better way of capturing errors such as "No such container." + create_child_args = { + 'merge_stdio': True + } + + def _get_name(self): + return u'docker.' + (self.options.container or self.options.image) + + def get_boot_command(self): + args = ['--interactive'] + if self.options.username: + args += ['--user=' + self.options.username] + + bits = [self.options.docker_path] + if self.options.container: + bits += ['exec'] + args + [self.options.container] + elif self.options.image: + bits += ['run'] + args + ['--rm', self.options.image] + + return bits + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/fakessh.py b/mitogen-0.3.9/mitogen/fakessh.py new file mode 100644 index 0000000..2d66024 --- /dev/null +++ b/mitogen-0.3.9/mitogen/fakessh.py @@ -0,0 +1,455 @@ +# 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.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.Protocol): + _output_buf = '' + _closed = False + + def __init__(self, broker): + self._broker = broker + + 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.fp.fileno() is not None: + self._broker._start_transmit(self) + + def on_shutdown(self, stream, broker): + self.close() + + def on_transmit(self, stream, 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, stream, broker): + s = stream.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.fp.fileno(), + self.transmit_side.fp.fileno(), + ) + + +class Process(object): + """ + Manages the lifetime and pipe connections of the SSH command running in the + slave. + """ + def __init__(self, router, stdin, stdout, proc=None): + self.router = router + self.stdin = stdin + self.stdout = stdout + self.proc = proc + self.control_handle = router.add_handler(self._on_control) + self.stdin_handle = router.add_handler(self._on_stdin) + self.pump = IoPump.build_stream(router.broker) + self.pump.accept(stdin, stdout) + 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, self.stdout) + + 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, msg) + self.pump.protocol.close() + return + + data = msg.unpickle() + IOLOG.debug('%r._on_stdin() -> len %d', self, len(data)) + self.pump.protocol.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, proc.stdout, 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, + stdin=os.fdopen(1, 'w+b', 0), + stdout=os.fdopen(0, 'r+b', 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, sock1) + 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(econtext, 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.3.9/mitogen/fork.py b/mitogen-0.3.9/mitogen/fork.py new file mode 100644 index 0000000..f0c2d7e --- /dev/null +++ b/mitogen-0.3.9/mitogen/fork.py @@ -0,0 +1,250 @@ +# 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 errno +import logging +import os +import random +import sys +import threading +import traceback + +import mitogen.core +import mitogen.parent +from mitogen.core import b + + +LOG = logging.getLogger(__name__) + +# 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 :gh:issue:`150` + for a full discussion. + """ + logging._lock = threading.RLock() + + # The root logger does not appear in the loggerDict. + logging.Logger.manager.loggerDict = {} + logging.getLogger().handlers = [] + + +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) + + +def _convert_exit_status(status): + """ + Convert a :func:`os.waitpid`-style exit status to a :mod:`subprocess` style + exit status. + """ + if os.WIFEXITED(status): + return os.WEXITSTATUS(status) + elif os.WIFSIGNALED(status): + return -os.WTERMSIG(status) + elif os.WIFSTOPPED(status): + return -os.WSTOPSIG(status) + + +class Process(mitogen.parent.Process): + def poll(self): + 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 + + if not pid: + return + return _convert_exit_status(status) + + +class Options(mitogen.parent.Options): + #: Reference to the importer, if any, recovered from the parent. + importer = None + + #: User-supplied function for cleaning up child process state. + on_fork = None + + def __init__(self, old_router, max_message_size, on_fork=None, debug=False, + profiling=False, unidirectional=False, on_start=None, + name=None): + if not FORK_SUPPORTED: + raise Error(self.python_version_msg) + + # fork method only supports a tiny subset of options. + super(Options, self).__init__( + max_message_size=max_message_size, debug=debug, + profiling=profiling, unidirectional=unidirectional, name=name, + ) + 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 + + +class Connection(mitogen.parent.Connection): + options_class = Options + child_is_immediate_subprocess = True + + 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." + ) + + name_prefix = u'fork' + + def start_child(self): + parentfp, childfp = mitogen.parent.create_socketpair() + pid = os.fork() + if pid: + childfp.close() + return Process(pid, stdin=parentfp, stdout=parentfp) + 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 get_econtext_config(self): + config = super(Connection, self).get_econtext_config() + config['core_src_fd'] = None + config['importer'] = self.options.importer + config['send_ec2'] = False + config['setup_package'] = False + if self.options.on_start: + config['on_start'] = self.options.on_start + return config + + def _child_main(self, childfp): + on_fork() + if self.options.on_fork: + self.options.on_fork() + mitogen.core.set_block(childfp.fileno()) + + childfp.send(b('MITO002\n')) + + # 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() + + mitogen.core.IOLOG.setLevel(logging.INFO) + + try: + try: + mitogen.core.ExternalContext(self.get_econtext_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) diff --git a/mitogen-0.3.9/mitogen/jail.py b/mitogen-0.3.9/mitogen/jail.py new file mode 100644 index 0000000..4da7eb0 --- /dev/null +++ b/mitogen-0.3.9/mitogen/jail.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. + +# !mitogen: minify_safe + +import mitogen.core +import mitogen.parent + + +class Options(mitogen.parent.Options): + container = None + username = None + jexec_path = u'/usr/sbin/jexec' + + def __init__(self, container, jexec_path=None, username=None, **kwargs): + super(Options, self).__init__(**kwargs) + self.container = mitogen.core.to_text(container) + if username: + self.username = mitogen.core.to_text(username) + if jexec_path: + self.jexec_path = jexec_path + + +class Connection(mitogen.parent.Connection): + options_class = Options + + child_is_immediate_subprocess = False + create_child_args = { + 'merge_stdio': True + } + + def _get_name(self): + return u'jail.' + self.options.container + + def get_boot_command(self): + bits = [self.options.jexec_path] + if self.options.username: + bits += ['-U', self.options.username] + bits += [self.options.container] + return bits + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/kubectl.py b/mitogen-0.3.9/mitogen/kubectl.py new file mode 100644 index 0000000..5d3994a --- /dev/null +++ b/mitogen-0.3.9/mitogen/kubectl.py @@ -0,0 +1,66 @@ +# Copyright 2018, Yannig Perre +# +# 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.parent + + +class Options(mitogen.parent.Options): + pod = None + kubectl_path = 'kubectl' + kubectl_args = None + + def __init__(self, pod, kubectl_path=None, kubectl_args=None, **kwargs): + super(Options, self).__init__(**kwargs) + assert pod + self.pod = pod + if kubectl_path: + self.kubectl_path = kubectl_path + self.kubectl_args = kubectl_args or [] + + +class Connection(mitogen.parent.Connection): + options_class = Options + child_is_immediate_subprocess = True + + # TODO: better way of capturing errors such as "No such container." + create_child_args = { + 'merge_stdio': True + } + + def _get_name(self): + return u'kubectl.%s%s' % (self.options.pod, self.options.kubectl_args) + + def get_boot_command(self): + bits = [ + self.options.kubectl_path + ] + self.options.kubectl_args + [ + 'exec', '-it', self.options.pod + ] + return bits + ["--"] + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/lxc.py b/mitogen-0.3.9/mitogen/lxc.py new file mode 100644 index 0000000..21dfef5 --- /dev/null +++ b/mitogen-0.3.9/mitogen/lxc.py @@ -0,0 +1,73 @@ +# 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.parent + + +class Options(mitogen.parent.Options): + container = None + lxc_attach_path = 'lxc-attach' + + def __init__(self, container, lxc_attach_path=None, **kwargs): + super(Options, self).__init__(**kwargs) + self.container = container + if lxc_attach_path: + self.lxc_attach_path = lxc_attach_path + + +class Connection(mitogen.parent.Connection): + options_class = Options + + child_is_immediate_subprocess = False + create_child_args = { + # If lxc-attach finds any of stdin, stdout, stderr connected to a TTY, + # to prevent input injection it creates a proxy pty, forcing all IO to + # be buffered in <4KiB chunks. So ensure stderr is also routed to the + # socketpair. + 'merge_stdio': True + } + + eof_error_hint = ( + 'Note: many versions of LXC do not report program execution failure ' + 'meaningfully. Please check the host logs (/var/log) for more ' + 'information.' + ) + + def _get_name(self): + return u'lxc.' + self.options.container + + def get_boot_command(self): + bits = [ + self.options.lxc_attach_path, + '--clear-env', + '--name', self.options.container, + '--', + ] + return bits + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/lxd.py b/mitogen-0.3.9/mitogen/lxd.py new file mode 100644 index 0000000..09034ab --- /dev/null +++ b/mitogen-0.3.9/mitogen/lxd.py @@ -0,0 +1,75 @@ +# 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.parent + + +class Options(mitogen.parent.Options): + container = None + lxc_path = 'lxc' + python_path = 'python' + + def __init__(self, container, lxc_path=None, **kwargs): + super(Options, self).__init__(**kwargs) + self.container = container + if lxc_path: + self.lxc_path = lxc_path + + +class Connection(mitogen.parent.Connection): + options_class = Options + + child_is_immediate_subprocess = False + create_child_args = { + # If lxc finds any of stdin, stdout, stderr connected to a TTY, to + # prevent input injection it creates a proxy pty, forcing all IO to be + # buffered in <4KiB chunks. So ensure stderr is also routed to the + # socketpair. + 'merge_stdio': True + } + + eof_error_hint = ( + 'Note: many versions of LXC do not report program execution failure ' + 'meaningfully. Please check the host logs (/var/log) for more ' + 'information.' + ) + + def _get_name(self): + return u'lxd.' + self.options.container + + def get_boot_command(self): + bits = [ + self.options.lxc_path, + 'exec', + '--mode=noninteractive', + self.options.container, + '--', + ] + return bits + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/master.py b/mitogen-0.3.9/mitogen/master.py new file mode 100644 index 0000000..b1e0a1d --- /dev/null +++ b/mitogen-0.3.9/mitogen/master.py @@ -0,0 +1,1572 @@ +# 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 functionality required by master processes, such as +starting new contexts via SSH. Its size is also restricted, since it must +be sent to any context that will be used to establish additional child +contexts. +""" + +import dis +import errno +import inspect +import itertools +import logging +import os +import pkgutil +import re +import string +import sys +import threading +import types +import zlib + +try: + # Python >= 3.4, PEP 451 ModuleSpec API + import importlib.machinery + import importlib.util + from _imp import is_builtin as _is_builtin +except ImportError: + # Python < 3.4, PEP 302 Import Hooks + import imp + from imp import is_builtin as _is_builtin + +try: + import sysconfig +except ImportError: + sysconfig = None + +if not hasattr(pkgutil, 'find_loader'): + # find_loader() was new in >=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') + + +# there are some cases where modules are loaded in memory only, such as +# ansible collections, and the module "filename" doesn't actually exist +SPECIAL_FILE_PATHS = { + "__synthetic__", + "" +} + + +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, None) for a in attr_candidates) + version = 'python%s.%s' % sys.version_info[0:2] + s = set(os.path.realpath(os.path.join(p, 'lib', version)) + for p in prefixes if p is not None) + + # When running 'unit2 tests/module_finder_test.py' in a Py2 venv on Ubuntu + # 18.10, above is insufficient to catch the real directory. + if sysconfig is not None: + s.add(sysconfig.get_config_var('DESTLIB')) + return s + + +def is_stdlib_name(modname): + """ + Return :data:`True` if `modname` appears to come from the standard library. + """ + # `(_imp|imp).is_builtin()` isn't a documented part of Python's stdlib. + # Returns 1 if modname names a module that is "builtin" to the the Python + # interpreter (e.g. '_sre'). Otherwise 0 (e.g. 're', 'netifaces'). + # + # """ + # Main is a little special - imp.is_builtin("__main__") will return False, + # but BuiltinImporter is still the most appropriate initial setting for + # its __loader__ attribute. + # """ -- comment in CPython pylifecycle.c:add_main_module() + if _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, fullname): + """ + 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. + :param str fullname: + Name of the package we're trying to get child modules for + + :return: + List of submodule name suffixes. + """ + mod_path = os.path.dirname(path) + if mod_path != '': + return [to_text(name) for _, name, _ in pkgutil.iter_modules([mod_path])] + else: + # we loaded some weird package in memory, so we'll see if it has a custom loader we can use + loader = pkgutil.find_loader(fullname) + return [to_text(name) for name, _ in loader.iter_modules(None)] if loader else [] + + +def _looks_like_script(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. + """ + try: + fp = open(path, 'rb') + except IOError: + e = sys.exc_info()[1] + if e.args[0] == errno.EISDIR: + return False + raise + + try: + sample = fp.read(512).decode('latin-1') + return not set(sample).difference(string.printable) + finally: + fp.close() + + +def _py_filename(path): + """ + Returns a tuple of a Python path (if the file looks Pythonic) and whether or not + the Python path is special. Special file paths/modules might only exist in memory + """ + if not path: + return None, False + + if path[-4:] in ('.pyc', '.pyo'): + path = path.rstrip('co') + + if path.endswith('.py'): + return path, False + + if os.path.exists(path) and _looks_like_script(path): + return path, False + + basepath = os.path.basename(path) + if basepath in SPECIAL_FILE_PATHS: + return path, True + + # return None, False means that the filename passed to _py_filename does not appear + # to be python, and code later will handle when this function returns None + # see https://github.com/dw/mitogen/pull/715#discussion_r532380528 for how this + # decision was made to handle non-python files in this manner + return None, False + + +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 + + context = self._router.context_by_id(msg.src_id) + if context is None: + LOG.error('%s: dropping log from unknown context %d', + self, msg.src_id) + return + + name, level_s, s = msg.data.decode('utf-8', 'replace').split('\x00', 2) + + logger_name = '%s.[%s]' % (name, context.name) + logger = self._cache.get(logger_name) + if logger is None: + self._cache[logger_name] = logger = logging.getLogger(logger_name) + + # See logging.Handler.makeRecord() + record = logging.LogRecord( + name=logger.name, + level=int(level_s), + pathname='(unknown file)', + lineno=0, + msg=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 FinderMethod(object): + """ + Interface to a method for locating a Python module or package given its + name according to the running Python interpreter. You'd think this was a + simple task, right? Naive young fellow, welcome to the real world. + """ + def __init__(self): + self.log = LOG.getChild(self.__class__.__name__) + + def __repr__(self): + return '%s()' % (type(self).__name__,) + + def find(self, fullname): + """ + Accept a canonical module name as would be found in :data:`sys.modules` + and return a `(path, source, is_pkg)` tuple, where: + + * `path`: Unicode string containing path to source file. + * `source`: Bytestring containing source file's content. + * `is_pkg`: :data:`True` if `fullname` is a package. + + :returns: + :data:`None` if not found, or tuple as described above. + """ + raise NotImplementedError() + + +class DefectivePython3xMainMethod(FinderMethod): + """ + 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 :mod:`__main__` module. Therefore + we must handle :mod:`__main__` specially. + """ + def find(self, fullname): + """ + Find :mod:`__main__` using its :data:`__file__` attribute. + """ + if fullname != '__main__': + return None + + mod = sys.modules.get(fullname) + if not mod: + return None + + path = getattr(mod, '__file__', None) + if not (path is not None and os.path.exists(path) and _looks_like_script(path)): + return None + + fp = open(path, 'rb') + try: + source = fp.read() + finally: + fp.close() + + return path, source, False + + +class PkgutilMethod(FinderMethod): + """ + Attempt to fetch source code via pkgutil. In an ideal world, this would + be the only required implementation of get_module(). + """ + def find(self, fullname): + """ + Find `fullname` using :func:`pkgutil.find_loader`. + """ + try: + # If fullname refers to a submodule that's not already imported + # then the containing package is imported. + # 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: find_loader(%r) failed: %s', self, fullname, e) + return None + + if not loader: + LOG.debug('%r: find_loader(%r) returned %r, aborting', + self, fullname, loader) + return + + try: + path = loader.get_filename(fullname) + except (AttributeError, ImportError, ValueError): + # - 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: %r.get_file_name(%r) failed: %r', self, loader, fullname, e) + return + + path, is_special = _py_filename(path) + + try: + source = loader.get_source(fullname) + except AttributeError: + # Per PEP-302, get_source() is optional, + e = sys.exc_info()[1] + LOG.debug('%r: %r.get_source() failed: %r', self, loader, fullname, e) + return + + try: + is_pkg = loader.is_package(fullname) + except AttributeError: + # Per PEP-302, is_package() is optional, + e = sys.exc_info()[1] + LOG.debug('%r: %r.is_package(%r) failed: %r', self, loader, fullname, e) + return + + # workaround for special python modules that might only exist in memory + if is_special and is_pkg and not source: + source = '\n' + + if path is None or source is None: + LOG.debug('%r: path=%r, source=%r, aborting', self, path, source) + 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 + + +class SysModulesMethod(FinderMethod): + """ + Attempt to fetch source code via :data:`sys.modules`. This was originally + specifically to support :mod:`__main__`, but it may catch a few more cases. + """ + def find(self, fullname): + """ + Find `fullname` using its :data:`__file__` attribute. + """ + try: + module = sys.modules[fullname] + except KeyError: + LOG.debug('%r: sys.modules[%r] absent, aborting', self, fullname) + return + + if not isinstance(module, types.ModuleType): + LOG.debug('%r: sys.modules[%r] is %r, aborting', + self, fullname, module) + return + + try: + resolved_name = module.__name__ + except AttributeError: + LOG.debug('%r: %r has no __name__, aborting', self, module) + return + + if resolved_name != fullname: + LOG.debug('%r: %r.__name__ is %r, aborting', + self, module, resolved_name) + return + + try: + path = module.__file__ + except AttributeError: + LOG.debug('%r: %r has no __file__, aborting', self, module) + return + + path, _ = _py_filename(path) + if not path: + LOG.debug('%r: %r.__file__ is %r, aborting', self, module, path) + return + + LOG.debug('%r: sys.modules[%r]: found %s', self, fullname, path) + 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 + + +class ParentImpEnumerationMethod(FinderMethod): + """ + Attempt to fetch source code by examining the module's (hopefully less + insane) parent package, and if no insane parents exist, simply use + :mod:`sys.path` to search for it from scratch on the filesystem using the + normal Python lookup mechanism. + + This is required for older versions of :mod:`ansible.compat.six`, + :mod:`plumbum.colors`, Ansible 2.8 :mod:`ansible.module_utils.distro` and + its submodule :mod:`ansible.module_utils.distro._distro`. + + When some package dynamically replaces itself in :data:`sys.modules`, but + only conditionally according to some program logic, it is possible that + children may attempt to load modules and subpackages from it that can no + longer be resolved by examining a (corrupted) parent. + + For cases like :mod:`ansible.module_utils.distro`, this must handle cases + where a package transmuted itself into a totally unrelated module during + import and vice versa, where :data:`sys.modules` is replaced with junk that + makes it impossible to discover the loaded module using the in-memory + module object or any parent package's :data:`__path__`, since they have all + been overwritten. Some men just want to watch the world burn. + """ + + @staticmethod + def _iter_parents(fullname): + """ + >>> list(ParentEnumerationMethod._iter_parents('a')) + [('', 'a')] + >>> list(ParentEnumerationMethod._iter_parents('a.b.c')) + [('a.b', 'c'), ('a', 'b'), ('', 'a')] + """ + while fullname: + fullname, _, modname = str_rpartition(fullname, u'.') + yield fullname, modname + + def _find_sane_parent(self, fullname): + """ + Iteratively search :data:`sys.modules` for the least indirect parent of + `fullname` that's from the same package and has a :data:`__path__` + attribute. + + :return: + `(parent_name, path, modpath)` tuple, where: + + * `modname`: canonical name of the found package, or the empty + string if none is found. + * `search_path`: :data:`__path__` attribute of the least + indirect parent found, or :data:`None` if no indirect parent + was found. + * `modpath`: list of module name components leading from `path` + to the target module. + """ + modpath = [] + for pkgname, modname in self._iter_parents(fullname): + modpath.insert(0, modname) + if not pkgname: + return [], None, modpath + + try: + pkg = sys.modules[pkgname] + except KeyError: + LOG.debug('%r: sys.modules[%r] absent, skipping', self, pkgname) + continue + + try: + resolved_pkgname = pkg.__name__ + except AttributeError: + LOG.debug('%r: %r has no __name__, skipping', self, pkg) + continue + + if resolved_pkgname != pkgname: + LOG.debug('%r: %r.__name__ is %r, skipping', + self, pkg, resolved_pkgname) + continue + + try: + path = pkg.__path__ + except AttributeError: + LOG.debug('%r: %r has no __path__, skipping', self, pkg) + continue + + if not path: + LOG.debug('%r: %r.__path__ is %r, skipping', self, pkg, path) + continue + + return pkgname.split('.'), path, modpath + + def _found_package(self, fullname, path): + path = os.path.join(path, '__init__.py') + LOG.debug('%r: %r is PKG_DIRECTORY: %r', self, fullname, path) + return self._found_module( + fullname=fullname, + path=path, + fp=open(path, 'rb'), + is_pkg=True, + ) + + def _found_module(self, fullname, path, fp, is_pkg=False): + try: + path, _ = _py_filename(path) + if not path: + 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, is_pkg + + def _find_one_component(self, modname, search_path): + try: + #fp, path, (suffix, _, kind) = imp.find_module(modname, search_path) + # FIXME The imp module was removed in Python 3.12. + return imp.find_module(modname, search_path) + except ImportError: + e = sys.exc_info()[1] + LOG.debug('%r: imp.find_module(%r, %r) -> %s', + self, modname, [search_path], e) + return None + + def find(self, fullname): + """ + See implementation for a description of how this works. + """ + if sys.version_info >= (3, 4): + return None + + #if fullname not in sys.modules: + # Don't attempt this unless a module really exists in sys.modules, + # else we could return junk. + #return + + fullname = to_text(fullname) + modname, search_path, modpath = self._find_sane_parent(fullname) + while True: + tup = self._find_one_component(modpath.pop(0), search_path) + if tup is None: + return None + + fp, path, (suffix, _, kind) = tup + if modpath: + # Still more components to descent. Result must be a package + if fp: + fp.close() + if kind != imp.PKG_DIRECTORY: + LOG.debug('%r: %r appears to be child of non-package %r', + self, fullname, path) + return None + search_path = [path] + elif kind == imp.PKG_DIRECTORY: + return self._found_package(fullname, path) + else: + return self._found_module(fullname, path, fp) + + +class ParentSpecEnumerationMethod(ParentImpEnumerationMethod): + def _find_parent_spec(self, fullname): + #history = [] + debug = self.log.debug + children = [] + for parent_name, child_name in self._iter_parents(fullname): + children.insert(0, child_name) + if not parent_name: + debug('abandoning %r, reached top-level', fullname) + return None, children + + try: + parent = sys.modules[parent_name] + except KeyError: + debug('skipping %r, not in sys.modules', parent_name) + continue + + try: + spec = parent.__spec__ + except AttributeError: + debug('skipping %r: %r.__spec__ is absent', + parent_name, parent) + continue + + if not spec: + debug('skipping %r: %r.__spec__=%r', + parent_name, parent, spec) + continue + + if spec.name != parent_name: + debug('skipping %r: %r.__spec__.name=%r does not match', + parent_name, parent, spec.name) + continue + + if not spec.submodule_search_locations: + debug('skipping %r: %r.__spec__.submodule_search_locations=%r', + parent_name, parent, spec.submodule_search_locations) + continue + + return spec, children + + raise ValueError('%s._find_parent_spec(%r) unexpectedly reached bottom' + % (self.__class__.__name__, fullname)) + + def find(self, fullname): + # Returns absolute path, ParentImpEnumerationMethod returns relative + # >>> spec_pem.find('six_brokenpkg._six')[::2] + # ('/Users/alex/src/mitogen/tests/data/importer/six_brokenpkg/_six.py', False) + + if sys.version_info < (3, 4): + return None + + fullname = to_text(fullname) + spec, children = self._find_parent_spec(fullname) + for child_name in children: + if spec: + name = '%s.%s' % (spec.name, child_name) + submodule_search_locations = spec.submodule_search_locations + else: + name = child_name + submodule_search_locations = None + spec = importlib.util._find_spec(name, submodule_search_locations) + if spec is None: + self.log.debug('%r spec unavailable from %s', fullname, spec) + return None + + is_package = spec.submodule_search_locations is not None + if name != fullname: + if not is_package: + self.log.debug('%r appears to be child of non-package %r', + fullname, spec) + return None + continue + + if not spec.has_location: + self.log.debug('%r.origin cannot be read as a file', spec) + return None + + if os.path.splitext(spec.origin)[1] != '.py': + self.log.debug('%r.origin does not contain Python source code', + spec) + return None + + # FIXME This should use loader.get_source() + with open(spec.origin, 'rb') as f: + source = f.read() + + return spec.origin, source, is_package + + raise ValueError('%s.find(%r) unexpectedly reached bottom' + % (self.__class__.__name__, fullname)) + + +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 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 = [ + DefectivePython3xMainMethod(), + PkgutilMethod(), + SysModulesMethod(), + ParentSpecEnumerationMethod(), + ParentImpEnumerationMethod(), + ] + + 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.find(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._log = logging.getLogger('mitogen.responder') + 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' + + 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 + + self._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. + self._log.debug('refusing to serve stdlib module %r', 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. + self._log.debug('could not find source for %r', 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 = mitogen.core.now() + source = mitogen.minify.minimize_source(source).encode('utf-8') + self.minify_secs += mitogen.core.now() - t0 + + if is_pkg: + pkg_present = get_child_modules(path, fullname) + self._log.debug('%s is a package at %s with submodules %r', + fullname, path, 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.protocol.sent_modules: + tup = self._build_tuple(fullname) + msg = mitogen.core.Message.pickled( + tup, + dst_id=stream.protocol.remote_id, + handle=mitogen.core.LOAD_MODULE, + ) + self._log.debug('sending %s (%.2f KiB) to %s', + fullname, len(msg.data) / 1024.0, stream.name) + self._router._async_route(msg) + stream.protocol.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.protocol.send( + mitogen.core.Message.pickled( + self._make_negative_response(fullname), + dst_id=stream.protocol.remote_id, + handle=mitogen.core.LOAD_MODULE, + ) + ) + + def _send_module_and_related(self, stream, fullname): + if fullname in stream.protocol.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.protocol.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() + self._log.debug('%s requested module %s', stream.name, fullname) + self.get_module_count += 1 + if fullname in stream.protocol.sent_modules: + LOG.warning('_on_get_module(): dup request for %r from %r', + fullname, stream) + + t0 = mitogen.core.now() + try: + self._send_module_and_related(stream, fullname) + finally: + self.get_module_secs += mitogen.core.now() - t0 + + def _send_forward_module(self, stream, context, fullname): + if stream.protocol.remote_id != context.context_id: + stream.protocol._send( + mitogen.core.Message( + data=b('%s\x00%s' % (context.context_id, fullname)), + handle=mitogen.core.FORWARD_MODULE, + dst_id=stream.protocol.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=mitogen.core.threading__current_thread(), + on_join=self.shutdown, + ) + super(Broker, self).__init__() + self.timers = mitogen.parent.TimerList() + + 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): + # making stream_by_id python3-safe by converting stream_by_id values iter to list + for stream in list(self._stream_by_id.values()): + self.disconnect_stream(stream) + + +class IdAllocator(object): + """ + Allocate IDs for new contexts constructed locally, and blocks of IDs for + children to allocate their own IDs using + :class:`mitogen.parent.ChildIdAllocator` without risk of conflict, and + without necessitating network round-trips for each new context. + + This class responds to :data:`mitogen.core.ALLOCATE_ID` messages received + from children by replying with fresh block ID allocations. + + The master's :class:`IdAllocator` instance can be accessed via + :attr:`mitogen.master.Router.id_allocator`. + """ + #: Block allocations are made in groups of 1000 by default. + BLOCK_SIZE = 1000 + + 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,) + + def allocate(self): + """ + Allocate a context ID by directly incrementing an internal counter. + + :returns: + The new context ID. + """ + self.lock.acquire() + try: + id_ = self.next_id + self.next_id += 1 + return id_ + finally: + self.lock.release() + + def allocate_block(self): + """ + Allocate a block of IDs for use in a child context. + + This function is safe to call from any thread. + + :returns: + Tuple of the form `(id, end_id)` where `id` is the first usable ID + and `end_id` is the last usable ID. + """ + 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.3.9/mitogen/minify.py b/mitogen-0.3.9/mitogen/minify.py new file mode 100644 index 0000000..09fdc4e --- /dev/null +++ b/mitogen-0.3.9/mitogen/minify.py @@ -0,0 +1,143 @@ +# 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.3.9/mitogen/os_fork.py b/mitogen-0.3.9/mitogen/os_fork.py new file mode 100644 index 0000000..9c649d0 --- /dev/null +++ b/mitogen-0.3.9/mitogen/os_fork.py @@ -0,0 +1,186 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +Support for operating in a mixed threading/forking environment. +""" + +import os +import socket +import sys +import weakref + +import mitogen.core + + +# List of weakrefs. On Python 2.4, mitogen.core registers its Broker on this +# list and mitogen.service registers its Pool too. +_brokers = weakref.WeakKeyDictionary() +_pools = weakref.WeakKeyDictionary() + + +def _notice_broker_or_pool(obj): + """ + Used by :mod:`mitogen.core` and :mod:`mitogen.service` to automatically + register every broker and pool on Python 2.4/2.5. + """ + if isinstance(obj, mitogen.core.Broker): + _brokers[obj] = True + else: + _pools[obj] = True + + +def wrap_os__fork(): + corker = Corker( + brokers=list(_brokers), + pools=list(_pools), + ) + try: + corker.cork() + return os__fork() + finally: + corker.uncork() + + +# If Python 2.4/2.5 where threading state is not fixed up, subprocess.Popen() +# may still deadlock due to the broker thread. In this case, pause os.fork() so +# that all active threads are paused during fork. +if sys.version_info < (2, 6): + os__fork = os.fork + os.fork = wrap_os__fork + + +class Corker(object): + """ + Arrange for :class:`mitogen.core.Broker` and optionally + :class:`mitogen.service.Pool` to be temporarily "corked" while fork + operations may occur. + + In a mixed threading/forking environment, it is critical no threads are + active at the moment of fork, as they could hold mutexes whose state is + unrecoverably snapshotted in the locked state in the fork child, causing + deadlocks at random future moments. + + To ensure a target thread has all locks dropped, it is made to write a + large string to a socket with a small buffer that has :data:`os.O_NONBLOCK` + disabled. CPython will drop the GIL and enter the ``write()`` system call, + where it will block until the socket buffer is drained, or the write side + is closed. + + :class:`mitogen.core.Poller` is used to ensure the thread really has + blocked outside any Python locks, by checking if the socket buffer has + started to fill. + + Since this necessarily involves posting a message to every existent thread + and verifying acknowledgement, it will never be a fast operation. + + This does not yet handle the case of corking being initiated from within a + thread that is also a cork target. + + :param brokers: + Sequence of :class:`mitogen.core.Broker` instances to cork. + :param pools: + Sequence of :class:`mitogen.core.Pool` instances to cork. + """ + def __init__(self, brokers=(), pools=()): + self.brokers = brokers + self.pools = pools + + def _do_cork(self, s, wsock): + try: + try: + while True: + # at least EINTR is possible. Do our best to keep handling + # outside the GIL in this case using sendall(). + wsock.sendall(s) + except socket.error: + pass + finally: + wsock.close() + + def _cork_one(self, s, obj): + """ + Construct a socketpair, saving one side of it, and passing the other to + `obj` to be written to by one of its threads. + """ + rsock, wsock = mitogen.parent.create_socketpair(size=4096) + mitogen.core.set_cloexec(rsock.fileno()) + mitogen.core.set_cloexec(wsock.fileno()) + mitogen.core.set_block(wsock) # gevent + self._rsocks.append(rsock) + obj.defer(self._do_cork, s, wsock) + + def _verify_one(self, rsock): + """ + Pause until the socket `rsock` indicates readability, due to + :meth:`_do_cork` triggering a blocking write on another thread. + """ + poller = mitogen.core.Poller() + poller.start_receive(rsock.fileno()) + try: + while True: + for fd in poller.poll(): + return + finally: + poller.close() + + def cork(self): + """ + Arrange for any associated brokers and pools to be paused with no locks + held. This will not return until each thread acknowledges it has ceased + execution. + """ + current = mitogen.core.threading__current_thread() + s = mitogen.core.b('CORK') * ((128 // 4) * 1024) + self._rsocks = [] + + # Pools must be paused first, as existing work may require the + # participation of a broker in order to complete. + for pool in self.pools: + if not pool.closed: + for th in pool._threads: + if th != current: + self._cork_one(s, pool) + + for broker in self.brokers: + if broker._alive: + if broker._thread != current: + self._cork_one(s, broker) + + # Pause until we can detect every thread has entered write(). + for rsock in self._rsocks: + self._verify_one(rsock) + + def uncork(self): + """ + Arrange for paused threads to resume operation. + """ + for rsock in self._rsocks: + rsock.close() diff --git a/mitogen-0.3.9/mitogen/parent.py b/mitogen-0.3.9/mitogen/parent.py new file mode 100644 index 0000000..4b96dcf --- /dev/null +++ b/mitogen-0.3.9/mitogen/parent.py @@ -0,0 +1,2771 @@ +# Copyright 2019, David Wilson +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# !mitogen: minify_safe + +""" +This module defines functionality common to master and parent processes. It is +sent to any child context that is due to become a parent, due to recursive +connection. +""" + +import binascii +import errno +import fcntl +import getpass +import heapq +import inspect +import logging +import os +import re +import signal +import socket +import struct +import subprocess +import sys +import termios +import textwrap +import threading +import zlib + +# Absolute imports for <2.5. +select = __import__('select') + +try: + import thread +except ImportError: + import threading as thread + +import mitogen.core +from mitogen.core import b +from mitogen.core import bytes_partition +from mitogen.core import IOLOG + + +LOG = logging.getLogger(__name__) + +# #410: we must avoid the use of socketpairs if SELinux is enabled. +try: + fp = open('/sys/fs/selinux/enforce', 'rb') + try: + SELINUX_ENABLED = bool(int(fp.read())) + finally: + fp.close() +except IOError: + SELINUX_ENABLED = False + + +try: + next +except NameError: + # Python 2.4/2.5 + from mitogen.core import next + + +itervalues = getattr(dict, 'itervalues', dict.values) + +if mitogen.core.PY3: + xrange = range + closure_attr = '__closure__' + IM_SELF_ATTR = '__self__' +else: + closure_attr = 'func_closure' + IM_SELF_ATTR = 'im_self' + + +try: + SC_OPEN_MAX = os.sysconf('SC_OPEN_MAX') +except ValueError: + SC_OPEN_MAX = 1024 + +BROKER_SHUTDOWN_MSG = ( + 'Connection cancelled because the associated Broker began to shut down.' +) + +OPENPTY_MSG = ( + "Failed to create a PTY: %s. It is likely the maximum number of PTYs has " + "been reached. Consider increasing the 'kern.tty.ptmx_max' sysctl on OS " + "X, the 'kernel.pty.max' sysctl on Linux, or modifying your configuration " + "to avoid PTY use." +) + +SYS_EXECUTABLE_MSG = ( + "The Python sys.executable variable is unset, indicating Python was " + "unable to determine its original program name. Unless explicitly " + "configured otherwise, child contexts will be started using " + "'/usr/bin/python'" +) +_sys_executable_warning_logged = False + + +def _ioctl_cast(n): + """ + Linux ioctl() request parameter is unsigned, whereas on BSD/Darwin it is + signed. Until 2.5 Python exclusively implemented the BSD behaviour, + preventing use of large unsigned int requests like the TTY layer uses + below. So on 2.4, we cast our unsigned to look like signed for Python. + """ + if sys.version_info < (2, 5): + n, = struct.unpack('i', struct.pack('I', n)) + return n + + +# If not :data:`None`, called prior to exec() of any new child process. Used by +# :func:`mitogen.utils.reset_affinity` to allow the child to be freely +# scheduled. +_preexec_hook = None + +# Get PTY number; asm-generic/ioctls.h +LINUX_TIOCGPTN = _ioctl_cast(2147767344) + +# Lock/unlock PTY; asm-generic/ioctls.h +LINUX_TIOCSPTLCK = _ioctl_cast(1074025521) + +IS_LINUX = os.uname()[0] == 'Linux' + +SIGNAL_BY_NUM = dict( + (getattr(signal, name), name) + for name in sorted(vars(signal), reverse=True) + if name.startswith('SIG') and not name.startswith('SIG_') +) + +_core_source_lock = threading.Lock() +_core_source_partial = None + + +def get_log_level(): + return (LOG.getEffectiveLevel() or logging.INFO) + + +def get_sys_executable(): + """ + Return :data:`sys.executable` if it is set, otherwise return + ``"/usr/bin/python"`` and log a warning. + """ + if sys.executable: + return sys.executable + + global _sys_executable_warning_logged + if not _sys_executable_warning_logged: + LOG.warn(SYS_EXECUTABLE_MSG) + _sys_executable_warning_logged = True + + return '/usr/bin/python' + + +def _get_core_source(): + """ + In non-masters, simply fetch the cached mitogen.core source code via the + import mechanism. In masters, this function is replaced with a version that + performs minification directly. + """ + return inspect.getsource(mitogen.core) + + +def get_core_source_partial(): + """ + _get_core_source() is expensive, even with @lru_cache in minify.py, threads + can enter it simultaneously causing severe slowdowns. + """ + global _core_source_partial + + if _core_source_partial is None: + _core_source_lock.acquire() + try: + if _core_source_partial is None: + _core_source_partial = PartialZlib( + _get_core_source().encode('utf-8') + ) + finally: + _core_source_lock.release() + + return _core_source_partial + + +def get_default_remote_name(): + """ + Return the default name appearing in argv[0] of remote machines. + """ + s = u'%s@%s:%d' + s %= (getpass.getuser(), socket.gethostname(), os.getpid()) + # In mixed UNIX/Windows environments, the username may contain slashes. + return s.translate({ + ord(u'\\'): ord(u'_'), + ord(u'/'): ord(u'_') + }) + + +def is_immediate_child(msg, stream): + """ + Handler policy that requires messages to arrive only from immediately + connected children. + """ + return msg.src_id == stream.protocol.remote_id + + +def flags(names): + """ + Return the result of ORing a set of (space separated) :py:mod:`termios` + module constants together. + """ + return sum(getattr(termios, name, 0) + for name in names.split()) + + +def cfmakeraw(tflags): + """ + Given a list returned by :py:func:`termios.tcgetattr`, return a list + modified in a manner similar to the `cfmakeraw()` C library function, but + additionally disabling local echo. + """ + # BSD: github.com/freebsd/freebsd/blob/master/lib/libc/gen/termios.c#L162 + # Linux: github.com/lattera/glibc/blob/master/termios/cfmakeraw.c#L20 + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = tflags + iflag &= ~flags('IMAXBEL IXOFF INPCK BRKINT PARMRK ' + 'ISTRIP INLCR ICRNL IXON IGNPAR') + iflag &= ~flags('IGNBRK BRKINT PARMRK') + oflag &= ~flags('OPOST') + lflag &= ~flags('ECHO ECHOE ECHOK ECHONL ICANON ISIG ' + 'IEXTEN NOFLSH TOSTOP PENDIN') + cflag &= ~flags('CSIZE PARENB') + cflag |= flags('CS8 CREAD') + return [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] + + +def disable_echo(fd): + old = termios.tcgetattr(fd) + new = cfmakeraw(old) + flags = getattr(termios, 'TCSASOFT', 0) + if not mitogen.core.IS_WSL: + # issue #319: Windows Subsystem for Linux as of July 2018 throws EINVAL + # if TCSAFLUSH is specified. + flags |= termios.TCSAFLUSH + termios.tcsetattr(fd, flags, new) + + +def create_socketpair(size=None): + """ + Create a :func:`socket.socketpair` for use as a child's UNIX stdio + channels. As socketpairs are bidirectional, they are economical on file + descriptor usage as one descriptor can be used for ``stdin`` and + ``stdout``. As they are sockets their buffers are tunable, allowing large + buffers to improve file transfer throughput and reduce IO loop iterations. + """ + if size is None: + size = mitogen.core.CHUNK_SIZE + + parentfp, childfp = socket.socketpair() + for fp in parentfp, childfp: + fp.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, size) + + return parentfp, childfp + + +def create_best_pipe(escalates_privilege=False): + """ + By default we prefer to communicate with children over a UNIX socket, as a + single file descriptor can represent bidirectional communication, and a + cross-platform API exists to align buffer sizes with the needs of the + library. + + SELinux prevents us setting up a privileged process to inherit an AF_UNIX + socket, a facility explicitly designed as a better replacement for pipes, + because at some point in the mid 90s it might have been commonly possible + for AF_INET sockets to end up undesirably connected to a privileged + process, so let's make up arbitrary rules breaking all sockets instead. + + If SELinux is detected, fall back to using pipes. + + :param bool escalates_privilege: + If :data:`True`, the target program may escalate privileges, causing + SELinux to disconnect AF_UNIX sockets, so avoid those. + :returns: + `(parent_rfp, child_wfp, child_rfp, parent_wfp)` + """ + if (not escalates_privilege) or (not SELINUX_ENABLED): + parentfp, childfp = create_socketpair() + return parentfp, childfp, childfp, parentfp + + parent_rfp, child_wfp = mitogen.core.pipe() + try: + child_rfp, parent_wfp = mitogen.core.pipe() + return parent_rfp, child_wfp, child_rfp, parent_wfp + except: + parent_rfp.close() + child_wfp.close() + raise + + +def popen(**kwargs): + """ + Wrap :class:`subprocess.Popen` to ensure any global :data:`_preexec_hook` + is invoked in the child. + """ + real_preexec_fn = kwargs.pop('preexec_fn', None) + def preexec_fn(): + if _preexec_hook: + _preexec_hook() + if real_preexec_fn: + real_preexec_fn() + return subprocess.Popen(preexec_fn=preexec_fn, **kwargs) + + +def create_child(args, merge_stdio=False, stderr_pipe=False, + escalates_privilege=False, preexec_fn=None): + """ + Create a child process whose stdin/stdout is connected to a socket. + + :param list args: + Program argument vector. + :param bool merge_stdio: + If :data:`True`, arrange for `stderr` to be connected to the `stdout` + socketpair, rather than inherited from the parent process. This may be + necessary to ensure that no TTY is connected to any stdio handle, for + instance when using LXC. + :param bool stderr_pipe: + If :data:`True` and `merge_stdio` is :data:`False`, arrange for + `stderr` to be connected to a separate pipe, to allow any ongoing debug + logs generated by e.g. SSH to be output as the session progresses, + without interfering with `stdout`. + :param bool escalates_privilege: + If :data:`True`, the target program may escalate privileges, causing + SELinux to disconnect AF_UNIX sockets, so avoid those. + :param function preexec_fn: + If not :data:`None`, a function to run within the post-fork child + before executing the target program. + :returns: + :class:`Process` instance. + """ + parent_rfp, child_wfp, child_rfp, parent_wfp = create_best_pipe( + escalates_privilege=escalates_privilege + ) + + stderr = None + stderr_r = None + if merge_stdio: + stderr = child_wfp + elif stderr_pipe: + stderr_r, stderr = mitogen.core.pipe() + mitogen.core.set_cloexec(stderr_r.fileno()) + + try: + proc = popen( + args=args, + stdin=child_rfp, + stdout=child_wfp, + stderr=stderr, + close_fds=True, + preexec_fn=preexec_fn, + ) + except: + child_rfp.close() + child_wfp.close() + parent_rfp.close() + parent_wfp.close() + if stderr_pipe: + stderr.close() + stderr_r.close() + raise + + child_rfp.close() + child_wfp.close() + if stderr_pipe: + stderr.close() + + return PopenProcess( + proc=proc, + stdin=parent_wfp, + stdout=parent_rfp, + stderr=stderr_r, + ) + + +def _acquire_controlling_tty(): + os.setsid() + if sys.platform in ('linux', 'linux2'): + # On Linux, the controlling tty becomes the first tty opened by a + # process lacking any prior tty. + os.close(os.open(os.ttyname(2), os.O_RDWR)) + if hasattr(termios, 'TIOCSCTTY') and not mitogen.core.IS_WSL: + # #550: prehistoric WSL does not like TIOCSCTTY. + # On BSD an explicit ioctl is required. For some inexplicable reason, + # Python 2.6 on Travis also requires it. + fcntl.ioctl(2, termios.TIOCSCTTY) + + +def _linux_broken_devpts_openpty(): + """ + #462: On broken Linux hosts with mismatched configuration (e.g. old + /etc/fstab template installed), /dev/pts may be mounted without the gid= + mount option, causing new slave devices to be created with the group ID of + the calling process. This upsets glibc, whose openpty() is required by + specification to produce a slave owned by a special group ID (which is + always the 'tty' group). + + Glibc attempts to use "pt_chown" to fix ownership. If that fails, it + chown()s the PTY directly, which fails due to non-root, causing openpty() + to fail with EPERM ("Operation not permitted"). Since we don't need the + magical TTY group to run sudo and su, open the PTY ourselves in this case. + """ + master_fd = None + try: + # Opening /dev/ptmx causes a PTY pair to be allocated, and the + # corresponding slave /dev/pts/* device to be created, owned by UID/GID + # matching this process. + master_fd = os.open('/dev/ptmx', os.O_RDWR) + # Clear the lock bit from the PTY. This a prehistoric feature from a + # time when slave device files were persistent. + fcntl.ioctl(master_fd, LINUX_TIOCSPTLCK, struct.pack('i', 0)) + # Since v4.13 TIOCGPTPEER exists to open the slave in one step, but we + # must support older kernels. Ask for the PTY number. + pty_num_s = fcntl.ioctl(master_fd, LINUX_TIOCGPTN, + struct.pack('i', 0)) + pty_num, = struct.unpack('i', pty_num_s) + pty_name = '/dev/pts/%d' % (pty_num,) + # Now open it with O_NOCTTY to ensure it doesn't change our controlling + # TTY. Otherwise when we close the FD we get killed by the kernel, and + # the child we spawn that should really attach to it will get EPERM + # during _acquire_controlling_tty(). + slave_fd = os.open(pty_name, os.O_RDWR|os.O_NOCTTY) + return master_fd, slave_fd + except OSError: + if master_fd is not None: + os.close(master_fd) + e = sys.exc_info()[1] + raise mitogen.core.StreamError(OPENPTY_MSG, e) + + +def openpty(): + """ + Call :func:`os.openpty`, raising a descriptive error if the call fails. + + :raises mitogen.core.StreamError: + Creating a PTY failed. + :returns: + `(master_fp, slave_fp)` file-like objects. + """ + try: + master_fd, slave_fd = os.openpty() + except OSError: + e = sys.exc_info()[1] + if not (IS_LINUX and e.args[0] == errno.EPERM): + raise mitogen.core.StreamError(OPENPTY_MSG, e) + master_fd, slave_fd = _linux_broken_devpts_openpty() + + master_fp = os.fdopen(master_fd, 'r+b', 0) + slave_fp = os.fdopen(slave_fd, 'r+b', 0) + disable_echo(master_fd) + disable_echo(slave_fd) + mitogen.core.set_block(slave_fd) + return master_fp, slave_fp + + +def tty_create_child(args): + """ + Return a file descriptor connected to the master end of a pseudo-terminal, + whose slave end is connected to stdin/stdout/stderr of a new child process. + The child is created such that the pseudo-terminal becomes its controlling + TTY, ensuring access to /dev/tty returns a new file descriptor open on the + slave end. + + :param list args: + Program argument vector. + :returns: + :class:`Process` instance. + """ + master_fp, slave_fp = openpty() + try: + proc = popen( + args=args, + stdin=slave_fp, + stdout=slave_fp, + stderr=slave_fp, + preexec_fn=_acquire_controlling_tty, + close_fds=True, + ) + except: + master_fp.close() + slave_fp.close() + raise + + slave_fp.close() + return PopenProcess( + proc=proc, + stdin=master_fp, + stdout=master_fp, + ) + + +def hybrid_tty_create_child(args, escalates_privilege=False): + """ + Like :func:`tty_create_child`, except attach stdin/stdout to a socketpair + like :func:`create_child`, but leave stderr and the controlling TTY + attached to a TTY. + + This permits high throughput communication with programs that are reached + via some program that requires a TTY for password input, like many + configurations of sudo. The UNIX TTY layer tends to have tiny (no more than + 14KiB) buffers, forcing many IO loop iterations when transferring bulk + data, causing significant performance loss. + + :param bool escalates_privilege: + If :data:`True`, the target program may escalate privileges, causing + SELinux to disconnect AF_UNIX sockets, so avoid those. + :param list args: + Program argument vector. + :returns: + :class:`Process` instance. + """ + master_fp, slave_fp = openpty() + try: + parent_rfp, child_wfp, child_rfp, parent_wfp = create_best_pipe( + escalates_privilege=escalates_privilege, + ) + try: + mitogen.core.set_block(child_rfp) + mitogen.core.set_block(child_wfp) + proc = popen( + args=args, + stdin=child_rfp, + stdout=child_wfp, + stderr=slave_fp, + preexec_fn=_acquire_controlling_tty, + close_fds=True, + ) + except: + parent_rfp.close() + child_wfp.close() + parent_wfp.close() + child_rfp.close() + raise + except: + master_fp.close() + slave_fp.close() + raise + + slave_fp.close() + child_rfp.close() + child_wfp.close() + return PopenProcess( + proc=proc, + stdin=parent_wfp, + stdout=parent_rfp, + stderr=master_fp, + ) + + +class Timer(object): + """ + Represents a future event. + """ + #: Set to :data:`False` if :meth:`cancel` has been called, or immediately + #: prior to being executed by :meth:`TimerList.expire`. + active = True + + def __init__(self, when, func): + self.when = when + self.func = func + + def __repr__(self): + return 'Timer(%r, %r)' % (self.when, self.func) + + def __eq__(self, other): + return self.when == other.when + + def __lt__(self, other): + return self.when < other.when + + def __le__(self, other): + return self.when <= other.when + + def cancel(self): + """ + Cancel this event. If it has not yet executed, it will not execute + during any subsequent :meth:`TimerList.expire` call. + """ + self.active = False + + +class TimerList(object): + """ + Efficiently manage a list of cancellable future events relative to wall + clock time. An instance of this class is installed as + :attr:`mitogen.master.Broker.timers` by default, and as + :attr:`mitogen.core.Broker.timers` in children after a call to + :func:`mitogen.parent.upgrade_router`. + + You can use :class:`TimerList` to cause the broker to wake at arbitrary + future moments, useful for implementing timeouts and polling in an + asynchronous context. + + :class:`TimerList` methods can only be called from asynchronous context, + for example via :meth:`mitogen.core.Broker.defer`. + + The broker automatically adjusts its sleep delay according to the installed + timer list, and arranges for timers to expire via automatic calls to + :meth:`expire`. The main user interface to :class:`TimerList` is + :meth:`schedule`. + """ + _now = mitogen.core.now + + def __init__(self): + self._lst = [] + + def get_timeout(self): + """ + Return the floating point seconds until the next event is due. + + :returns: + Floating point delay, or 0.0, or :data:`None` if no events are + scheduled. + """ + while self._lst and not self._lst[0].active: + heapq.heappop(self._lst) + if self._lst: + return max(0, self._lst[0].when - self._now()) + + def schedule(self, when, func): + """ + Schedule a future event. + + :param float when: + UNIX time in seconds when event should occur. + :param callable func: + Callable to invoke on expiry. + :returns: + A :class:`Timer` instance, exposing :meth:`Timer.cancel`, which may + be used to cancel the future invocation. + """ + timer = Timer(when, func) + heapq.heappush(self._lst, timer) + return timer + + def expire(self): + """ + Invoke callbacks for any events in the past. + """ + now = self._now() + while self._lst and self._lst[0].when <= now: + timer = heapq.heappop(self._lst) + if timer.active: + timer.active = False + timer.func() + + +class PartialZlib(object): + """ + Because the mitogen.core source has a line appended to it during bootstrap, + it must be recompressed for each connection. This is not a problem for a + small number of connections, but it amounts to 30 seconds CPU time by the + time 500 targets are in use. + + For that reason, build a compressor containing mitogen.core and flush as + much of it as possible into an initial buffer. Then to append the custom + line, clone the compressor and compress just that line. + + A full compression costs ~6ms on a modern machine, this method costs ~35 + usec. + """ + def __init__(self, s): + self.s = s + if sys.version_info > (2, 5): + self._compressor = zlib.compressobj(9) + self._out = self._compressor.compress(s) + self._out += self._compressor.flush(zlib.Z_SYNC_FLUSH) + else: + self._compressor = None + + def append(self, s): + """ + Append the bytestring `s` to the compressor state and return the + final compressed output. + """ + if self._compressor is None: + return zlib.compress(self.s + s, 9) + else: + compressor = self._compressor.copy() + out = self._out + out += compressor.compress(s) + return out + compressor.flush() + + +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->Protocol->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) + try: + 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 + finally: + root.setLevel(old_level) + + broker.timers = TimerList() + LOG.debug('upgraded %r with %r (new: %d readers, %d writers; ' + 'old: %d readers, %d writers)', old, new, + len(new._rfds), len(new._wfds), len(old._rfds), len(old._wfds)) + + +@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 get_connection_class(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.Connection + + +@mitogen.core.takes_econtext +def _proxy_connect(name, method_name, kwargs, econtext): + """ + Implements the target portion of Router._proxy_connect() by upgrading the + local process 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=get_connection_class(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 returncode_to_str(n): + """ + Parse and format a :func:`os.waitpid` exit status. + """ + if n < 0: + return 'exited due to signal %d (%s)' % (-n, SIGNAL_BY_NUM.get(-n)) + return 'exited with return code %d' % (n,) + + +class EofError(mitogen.core.StreamError): + """ + Raised by :class:`Connection` when an empty read is detected from the + remote process before bootstrap completes. + """ + # inherits from StreamError to maintain compatibility. + pass + + +class CancelledError(mitogen.core.StreamError): + """ + Raised by :class:`Connection` when :meth:`mitogen.core.Broker.shutdown` is + called before bootstrap completes. + """ + 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 :linux:man2:`poll` interface. Not available on + some Python/OS X combinations. Otherwise the preferred poller for small + FD counts; or if many pollers are created, used once, then closed. + There there is no setup/teardown/configuration system call overhead. + """ + SUPPORTED = hasattr(select, 'poll') + _readmask = SUPPORTED and select.POLLIN | select.POLLHUP + + def __init__(self): + super(PollPoller, self).__init__() + self._pollobj = select.poll() + + # TODO: no proof we dont need writemask too + 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 :freebsd:man2:`kqueue` interface. + """ + SUPPORTED = hasattr(select, 'kqueue') + + 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 :linux:man7:`epoll` interface. + """ + SUPPORTED = hasattr(select, 'epoll') + _inmask = SUPPORTED and select.EPOLLIN | select.EPOLLHUP + + 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) + + 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 + + +POLLERS = (EpollPoller, KqueuePoller, PollPoller, mitogen.core.Poller) +PREFERRED_POLLER = next(cls for cls in POLLERS if cls.SUPPORTED) + + +# For processes that start many threads or connections, it's possible Latch +# will also get high-numbered FDs, and so select() becomes useless there too. +POLLER_LIGHTWEIGHT = PollPoller.SUPPORTED and PollPoller or PREFERRED_POLLER +mitogen.core.Latch.poller_class = POLLER_LIGHTWEIGHT + + +class LineLoggingProtocolMixin(object): + def __init__(self, **kwargs): + super(LineLoggingProtocolMixin, self).__init__(**kwargs) + self.logged_lines = [] + self.logged_partial = None + + def on_line_received(self, line): + self.logged_partial = None + self.logged_lines.append((mitogen.core.now(), line)) + self.logged_lines[:] = self.logged_lines[-100:] + return super(LineLoggingProtocolMixin, self).on_line_received(line) + + def on_partial_line_received(self, line): + self.logged_partial = line + return super(LineLoggingProtocolMixin, self).on_partial_line_received(line) + + def on_disconnect(self, broker): + if self.logged_partial: + self.logged_lines.append((mitogen.core.now(), self.logged_partial)) + self.logged_partial = None + super(LineLoggingProtocolMixin, self).on_disconnect(broker) + + +def get_history(streams): + history = [] + for stream in streams: + if stream: + history.extend(getattr(stream.protocol, 'logged_lines', [])) + history.sort() + + s = b('\n').join(h[1] for h in history) + return mitogen.core.to_text(s) + + +class RegexProtocol(LineLoggingProtocolMixin, mitogen.core.DelimitedProtocol): + """ + Implement a delimited protocol where messages matching a set of regular + expressions are dispatched to individual handler methods. Input is + dispatches using :attr:`PATTERNS` and :attr:`PARTIAL_PATTERNS`, before + falling back to :meth:`on_unrecognized_line_received` and + :meth:`on_unrecognized_partial_line_received`. + """ + #: A sequence of 2-tuples of the form `(compiled pattern, method)` for + #: patterns that should be matched against complete (delimited) messages, + #: i.e. full lines. + PATTERNS = [] + + #: Like :attr:`PATTERNS`, but patterns that are matched against incomplete + #: lines. + PARTIAL_PATTERNS = [] + + def on_line_received(self, line): + super(RegexProtocol, self).on_line_received(line) + for pattern, func in self.PATTERNS: + match = pattern.search(line) + if match is not None: + return func(self, line, match) + + return self.on_unrecognized_line_received(line) + + def on_unrecognized_line_received(self, line): + LOG.debug('%s: (unrecognized): %s', + self.stream.name, line.decode('utf-8', 'replace')) + + def on_partial_line_received(self, line): + super(RegexProtocol, self).on_partial_line_received(line) + LOG.debug('%s: (partial): %s', + self.stream.name, line.decode('utf-8', 'replace')) + for pattern, func in self.PARTIAL_PATTERNS: + match = pattern.search(line) + if match is not None: + return func(self, line, match) + + return self.on_unrecognized_partial_line_received(line) + + def on_unrecognized_partial_line_received(self, line): + LOG.debug('%s: (unrecognized partial): %s', + self.stream.name, line.decode('utf-8', 'replace')) + + +class BootstrapProtocol(RegexProtocol): + """ + Respond to stdout of a child during bootstrap. Wait for :attr:`EC0_MARKER` + to be written by the first stage to indicate it can receive the bootstrap, + then await :attr:`EC1_MARKER` to indicate success, and + :class:`MitogenProtocol` can be enabled. + """ + #: 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 = b('MITO000') + EC1_MARKER = b('MITO001') + EC2_MARKER = b('MITO002') + + def __init__(self, broker): + super(BootstrapProtocol, self).__init__() + self._writer = mitogen.core.BufferedWriter(broker, self) + + def on_transmit(self, broker): + self._writer.on_transmit(broker) + + def _on_ec0_received(self, line, match): + LOG.debug('%r: first stage started succcessfully', self) + self._writer.write(self.stream.conn.get_preamble()) + + def _on_ec1_received(self, line, match): + LOG.debug('%r: first stage received mitogen.core source', self) + + def _on_ec2_received(self, line, match): + LOG.debug('%r: new child booted successfully', self) + self.stream.conn._complete_connection() + return False + + def on_unrecognized_line_received(self, line): + LOG.debug('%s: stdout: %s', self.stream.name, + line.decode('utf-8', 'replace')) + + PATTERNS = [ + (re.compile(EC0_MARKER), _on_ec0_received), + (re.compile(EC1_MARKER), _on_ec1_received), + (re.compile(EC2_MARKER), _on_ec2_received), + ] + + +class LogProtocol(LineLoggingProtocolMixin, mitogen.core.DelimitedProtocol): + """ + For "hybrid TTY/socketpair" mode, after connection setup a spare TTY master + FD exists 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 sends SIGHUP to + processes whose controlling TTY is the slave whose master side was closed. + LogProtocol takes over this FD and creates log messages for anything + written to it. + """ + def on_line_received(self, line): + """ + Read a line, decode it as UTF-8, and log it. + """ + super(LogProtocol, self).on_line_received(line) + LOG.info(u'%s: %s', self.stream.name, line.decode('utf-8', 'replace')) + + +class MitogenProtocol(mitogen.core.MitogenProtocol): + """ + Extend core.MitogenProtocol to cause SHUTDOWN to be sent to the child + during graceful shutdown. + """ + def on_shutdown(self, broker): + """ + Respond to the broker's request for the stream to shut down by sending + SHUTDOWN to the child. + """ + LOG.debug('%r: requesting child shutdown', self) + self._send( + mitogen.core.Message( + src_id=mitogen.context_id, + dst_id=self.remote_id, + handle=mitogen.core.SHUTDOWN, + ) + ) + + +class Options(object): + name = None + + #: The path to the remote Python interpreter. + python_path = get_sys_executable() + + #: Maximum time to wait for a connection attempt. + connect_timeout = 30.0 + + #: True to cause context to write verbose /tmp/mitogen..log. + debug = False + + #: True to cause context to write /tmp/mitogen.stats...log. + profiling = False + + #: True if unidirectional routing is enabled in the new child. + unidirectional = False + + #: Passed via Router wrapper methods, must eventually be passed to + #: ExternalContext.main(). + max_message_size = None + + #: Remote name. + remote_name = None + + #: Derived from :py:attr:`connect_timeout`; absolute floating point + #: UNIX timestamp after which the connection attempt should be abandoned. + connect_deadline = None + + def __init__(self, max_message_size, name=None, remote_name=None, + python_path=None, debug=False, connect_timeout=None, + profiling=False, unidirectional=False, old_router=None): + self.name = name + 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') + if remote_name: + self.remote_name = mitogen.core.to_text(remote_name) + self.debug = debug + self.profiling = profiling + self.unidirectional = unidirectional + self.max_message_size = max_message_size + self.connect_deadline = mitogen.core.now() + self.connect_timeout + + +class Connection(object): + """ + Manage the lifetime of a set of :class:`Streams ` connecting to a + remote Python interpreter, including bootstrap, disconnection, and external + tool integration. + + Base for streams capable of starting children. + """ + options_class = Options + + #: The protocol attached to stdio of the child. + stream_protocol_class = BootstrapProtocol + + #: The protocol attached to stderr of the child. + diag_protocol_class = LogProtocol + + #: :class:`Process` + proc = None + + #: :class:`mitogen.core.Stream` with sides connected to stdin/stdout. + stdio_stream = None + + #: If `proc.stderr` is set, referencing either a plain pipe or the + #: controlling TTY, this references the corresponding + #: :class:`LogProtocol`'s stream, allowing it to be disconnected when this + #: stream is disconnected. + stderr_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' + + #: :class:`Timer` that runs :meth:`_on_timer_expired` when connection + #: timeout occurs. + _timer = None + + #: When disconnection completes, instance of :class:`Reaper` used to wait + #: on the exit status of the subprocess. + _reaper = None + + #: On failure, the exception object that should be propagated back to the + #: user. + exception = None + + #: Extra text appended to :class:`EofError` if that exception is raised on + #: a failed connection attempt. May be used in subclasses to hint at common + #: problems with a particular connection method. + eof_error_hint = None + + def __init__(self, options, router): + #: :class:`Options` + self.options = options + self._router = router + + def __repr__(self): + return 'Connection(%r)' % (self.stdio_stream,) + + # 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. + # The script preamble_size.py measures this. + # * '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. + # + # macOS tweaks for Python 2.7 must be kept in sync with the the Ansible + # module test_echo_module, used by the integration tests. + # * macOS <= 10.14 (Darwin <= 18) install an unreliable Python version + # switcher as /usr/bin/python, which introspects argv0. To workaround + # it we redirect attempts to call /usr/bin/python with an explicit + # call to /usr/bin/python2.7. macOS 10.15 (Darwin 19) removed it. + # * macOS 11.x (Darwin 20, Big Sur) and macOS 12.x (Darwin 21, Montery) + # do something slightly different. The Python executable is patched to + # perform an extra execvp(). I don't fully understand the details, but + # setting PYTHON_LAUNCHED_FROM_WRAPPER=1 avoids it. + # * macOS 12.3+ (Darwin 21.4+, Monterey) doesn't ship Python. + # https://developer.apple.com/documentation/macos-release-notes/macos-12_3-release-notes#Python + # + # 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 os.uname()[0]=='Darwin'and os.uname()[2][:2]<'19'and sys.executable=='/usr/bin/python':sys.executable='/usr/bin/python2.7' + if os.uname()[0]=='Darwin'and os.uname()[2][:2]in'2021'and sys.version[:3]=='2.7':os.environ['PYTHON_LAUNCHED_FROM_WRAPPER']='1' + os.environ['ARGV0']=sys.executable + os.execl(sys.executable,sys.executable+'(mitogen:CONTEXT_NAME)') + os.write(1,'MITO000\n'.encode()) + C=zlib.decompress(os.fdopen(0,'rb').read(PREAMBLE_COMPRESSED_LEN)) + 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.options.python_path, list): + return self.options.python_path + return [self.options.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(' ', ' ') + source = source.replace('CONTEXT_NAME', self.options.remote_name) + preamble_compressed = self.get_preamble() + source = source.replace('PREAMBLE_COMPRESSED_LEN', + str(len(preamble_compressed))) + compressed = zlib.compress(source.encode(), 9) + encoded = binascii.b2a_base64(compressed).replace(b('\n'), b('')) + + # Just enough to decode, decompress, and exec the first stage. + # Priorities: wider compatibility, faster startup, shorter length. + # `import os` here, instead of stage 1, to save a few bytes. + # `sys.path=...` for https://github.com/python/cpython/issues/115911. + return self.get_python_argv() + [ + '-c', + 'import sys;sys.path=[p for p in sys.path if p];import binascii,os,zlib;' + 'exec(zlib.decompress(binascii.a2b_base64("%s")))' % (encoded.decode(),), + ] + + def get_econtext_config(self): + assert self.options.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.context.context_id, + 'debug': self.options.debug, + 'profiling': self.options.profiling, + 'unidirectional': self.options.unidirectional, + 'log_level': get_log_level(), + 'whitelist': self._router.get_module_whitelist(), + 'blacklist': self._router.get_module_blacklist(), + 'max_message_size': self.options.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 _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.proc.pid) + + def start_child(self): + args = self.get_boot_command() + LOG.debug('command line for %r: %s', self, Argv(args)) + try: + return self.create_child(args=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) + + def _adorn_eof_error(self, e): + """ + Subclasses may 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 _complete_connection(self): + self._timer.cancel() + if not self.exception: + mitogen.core.unlisten(self._router.broker, 'shutdown', + self._on_broker_shutdown) + self._router.register(self.context, self.stdio_stream) + self.stdio_stream.set_protocol( + MitogenProtocol( + router=self._router, + remote_id=self.context.context_id, + ) + ) + self._router.route_monitor.notice_stream(self.stdio_stream) + self.latch.put() + + def _fail_connection(self, exc): + """ + Fail the connection attempt. + """ + LOG.debug('failing connection %s due to %r', + self.stdio_stream and self.stdio_stream.name, exc) + if self.exception is None: + self._adorn_eof_error(exc) + self.exception = exc + mitogen.core.unlisten(self._router.broker, 'shutdown', + self._on_broker_shutdown) + for stream in self.stdio_stream, self.stderr_stream: + if stream and not stream.receive_side.closed: + stream.on_disconnect(self._router.broker) + self._complete_connection() + + eof_error_msg = 'EOF on stream; last 100 lines received:\n' + + def on_stdio_disconnect(self): + """ + Handle stdio stream disconnection by failing the Connection if the + stderr stream has already been closed. Otherwise, wait for it to close + (or timeout), to allow buffered diagnostic logs to be consumed. + + It is normal that when a subprocess aborts, stdio has nothing buffered + when it is closed, thus signalling readability, causing an empty read + (interpreted as indicating disconnection) on the next loop iteration, + even if its stderr pipe has lots of diagnostic logs still buffered in + the kernel. Therefore we must wait for both pipes to indicate they are + empty before triggering connection failure. + """ + stderr = self.stderr_stream + if stderr is None or stderr.receive_side.closed: + self._on_streams_disconnected() + + def on_stderr_disconnect(self): + """ + Inverse of :func:`on_stdio_disconnect`. + """ + if self.stdio_stream.receive_side.closed: + self._on_streams_disconnected() + + def _on_streams_disconnected(self): + """ + When disconnection has been detected for both streams, cancel the + connection timer, mark the connection failed, and reap the child + process. Do nothing if the timer has already been cancelled, indicating + some existing failure has already been noticed. + """ + if self._timer.active: + self._timer.cancel() + self._fail_connection(EofError( + self.eof_error_msg + get_history( + [self.stdio_stream, self.stderr_stream] + ) + )) + + if self._reaper: + return + + self._reaper = Reaper( + broker=self._router.broker, + proc=self.proc, + kill=not ( + (self.detached and self.child_is_immediate_subprocess) or + # Avoid killing so child has chance to write cProfile data + self._router.profiling + ), + # Don't delay shutdown waiting for a detached child, since the + # detached child may expect to live indefinitely after its parent + # exited. + wait_on_shutdown=(not self.detached), + ) + self._reaper.reap() + + def _on_broker_shutdown(self): + """ + Respond to broker.shutdown() being called by failing the connection + attempt. + """ + self._fail_connection(CancelledError(BROKER_SHUTDOWN_MSG)) + + def stream_factory(self): + return self.stream_protocol_class.build_stream( + broker=self._router.broker, + ) + + def stderr_stream_factory(self): + return self.diag_protocol_class.build_stream() + + def _setup_stdio_stream(self): + stream = self.stream_factory() + stream.conn = self + stream.name = self.options.name or self._get_name() + stream.accept(self.proc.stdout, self.proc.stdin) + + mitogen.core.listen(stream, 'disconnect', self.on_stdio_disconnect) + self._router.broker.start_receive(stream) + return stream + + def _setup_stderr_stream(self): + stream = self.stderr_stream_factory() + stream.conn = self + stream.name = self.options.name or self._get_name() + stream.accept(self.proc.stderr, self.proc.stderr) + + mitogen.core.listen(stream, 'disconnect', self.on_stderr_disconnect) + self._router.broker.start_receive(stream) + return stream + + def _on_timer_expired(self): + self._fail_connection( + mitogen.core.TimeoutError( + 'Failed to setup connection after %.2f seconds', + self.options.connect_timeout, + ) + ) + + def _async_connect(self): + LOG.debug('creating connection to context %d using %s', + self.context.context_id, self.__class__.__module__) + mitogen.core.listen(self._router.broker, 'shutdown', + self._on_broker_shutdown) + self._timer = self._router.broker.timers.schedule( + when=self.options.connect_deadline, + func=self._on_timer_expired, + ) + + try: + self.proc = self.start_child() + except Exception: + LOG.debug('failed to start child', exc_info=True) + self._fail_connection(sys.exc_info()[1]) + return + + LOG.debug('child for %r started: pid:%r stdin:%r stdout:%r stderr:%r', + self, self.proc.pid, + self.proc.stdin.fileno(), + self.proc.stdout.fileno(), + self.proc.stderr and self.proc.stderr.fileno()) + + self.stdio_stream = self._setup_stdio_stream() + if self.context.name is None: + self.context.name = self.stdio_stream.name + self.proc.name = self.stdio_stream.name + if self.proc.stderr: + self.stderr_stream = self._setup_stderr_stream() + + def connect(self, context): + self.context = context + self.latch = mitogen.core.Latch() + self._router.broker.defer(self._async_connect) + self.latch.get() + if self.exception: + raise self.exception + + +class ChildIdAllocator(object): + """ + Allocate new context IDs from a block of unique context IDs allocated by + the master process. + """ + def __init__(self, router): + self.router = router + self.lock = threading.Lock() + self.it = iter(xrange(0)) + + def allocate(self): + """ + Allocate an ID, requesting a fresh block from the master if the + existing block is exhausted. + + :returns: + The new context ID. + + .. warning:: + + This method is not safe to call from the :class:`Broker` thread, as + it may block on IO of its own. + """ + self.lock.acquire() + try: + for id_ in self.it: + return id_ + + master = self.router.context_by_id(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 * mitogen.core.now()), + ) + + 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('starting no-reply function call to %r: %r', + self.context.name or self.context.context_id, + 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('starting function call to %s: %r', + self.context.name or self.context.context_id, + 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 mitogen.master.Router router: + Router to install handlers on. + :param mitogen.core.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 + self._log = logging.getLogger('mitogen.route_monitor') + #: 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.protocol.send( + mitogen.core.Message( + handle=handle, + data=data.encode('utf-8'), + dst_id=stream.protocol.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.protocol.egress_ids and ( + (self.parent is None) or + (self.parent.context_id != stream.protocol.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 + :data:`mitogen.core.DEL_ROUTE` upstream when that child disconnects. + """ + self._routes_by_stream[stream] = set([stream.protocol.remote_id]) + self._propagate_up(mitogen.core.ADD_ROUTE, stream.protocol.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 + + self._log.debug('stream %s is gone; propagating DEL_ROUTE for %r', + stream.name, 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.src_id) + current = self.router.stream_by_id(target_id) + if current and current.protocol.remote_id != mitogen.parent_id: + self._log.error('Cannot add duplicate route to %r via %r, ' + 'already have existing route via %r', + target_id, stream, current) + return + + self._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.src_id) + if registered_stream != stream: + self._log.error('received DEL_ROUTE for %d from %r, expected %r', + target_id, stream, registered_stream) + return + + context = self.router.context_by_id(target_id, create=False) + if context: + self._log.debug('firing local disconnect signal for %r', context) + mitogen.core.fire(context, 'disconnect') + + self._log.debug('deleting route to %d via %r', target_id, stream) + routes = self._routes_by_stream.get(stream) + if routes: + routes.discard(target_id) + + self.router.del_route(target_id) + if stream.protocol.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('upgrading %r with capabilities to start new children', 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.protocol.remote_id != msg.src_id or stream.conn.detached: + LOG.warning('bad DETACHING received on %r: %r', stream, msg) + return + LOG.debug('%r: marking as detached', stream) + stream.conn.detached = True + msg.reply(None) + + def get_streams(self): + """ + Return an atomic snapshot of all streams in existence at time of call. + This is safe to call from any thread. + """ + self._write_lock.acquire() + try: + return itervalues(self._stream_by_id) + finally: + self._write_lock.release() + + def disconnect(self, context): + """ + Disconnect a context and forget its stream, assuming the context is + directly connected. + """ + stream = self.stream_by_id(context) + if stream is None or stream.protocol.remote_id != context.context_id: + return + + l = mitogen.core.Latch() + mitogen.core.listen(stream, 'disconnect', l.put) + def disconnect(): + LOG.debug('Starting disconnect of %r', stream) + stream.on_disconnect(self.broker) + self.broker.defer(disconnect) + l.get() + + def add_route(self, target_id, stream): + """ + Arrange for messages whose `dst_id` is `target_id` to be forwarded on a + directly connected :class:`Stream`. Safe to call from any thread. + + This is called automatically by :class:`RouteMonitor` 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. + + :param int target_id: + Target context ID to add a route for. + :param mitogen.core.Stream stream: + Stream over which messages to the target should be routed. + """ + LOG.debug('%r: adding route to context %r via %r', + self, target_id, stream) + assert isinstance(target_id, int) + assert isinstance(stream, mitogen.core.Stream) + + self._write_lock.acquire() + try: + self._stream_by_id[target_id] = stream + finally: + self._write_lock.release() + + def del_route(self, target_id): + """ + Delete any route that exists for `target_id`. It is not an error to + delete a route that does not currently exist. Safe to call from any + thread. + + This is called automatically by :class:`RouteMonitor` in response to + :data:`mitogen.core.DEL_ROUTE` messages, but remains public while the + design has not yet settled, and situations may arise where routing is + not fully automatic. + + :param int target_id: + Target context ID to delete route for. + """ + LOG.debug('%r: deleting route to %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, **kwargs): + context_id = self.allocate_id() + context = self.context_class(self, context_id) + context.name = kwargs.get('name') + + kwargs['old_router'] = self + kwargs['max_message_size'] = self.max_message_size + conn = klass(klass.options_class(**kwargs), self) + try: + conn.connect(context=context) + except mitogen.core.TimeoutError: + raise mitogen.core.StreamError(self.connection_timeout_msg) + + return context + + def connect(self, method_name, name=None, **kwargs): + if name: + name = mitogen.core.to_text(name) + + klass = get_connection_class(method_name) + kwargs.setdefault(u'debug', self.debug) + kwargs.setdefault(u'profiling', self.profiling) + kwargs.setdefault(u'unidirectional', self.unidirectional) + kwargs.setdefault(u'name', name) + + via = kwargs.pop(u'via', None) + if via is not None: + return self.proxy_connect(via, method_name, + **mitogen.core.Kwargs(kwargs)) + return self._connect(klass, **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 buildah(self, **kwargs): + return self.connect(u'buildah', **kwargs) + + 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) + + def podman(self, **kwargs): + return self.connect(u'podman', **kwargs) + + +class Reaper(object): + """ + Asynchronous logic for reaping :class:`Process` objects. This is necessary + to prevent uncontrolled buildup of zombie processes in long-lived parents + that will eventually reach an OS limit, preventing creation of new threads + and processes, and to log the exit status of the child in the case of an + error. + + To avoid modifying process-global state such as with + :func:`signal.set_wakeup_fd` or installing a :data:`signal.SIGCHLD` handler + that might interfere with the user's ability to use those facilities, + Reaper polls for exit with backoff using timers installed on an associated + :class:`Broker`. + + :param mitogen.core.Broker broker: + The :class:`Broker` on which to install timers + :param mitogen.parent.Process proc: + The process to reap. + :param bool kill: + If :data:`True`, send ``SIGTERM`` and ``SIGKILL`` to the process. + :param bool wait_on_shutdown: + If :data:`True`, delay :class:`Broker` shutdown if child has not yet + exited. If :data:`False` simply forget the child. + """ + #: :class:`Timer` that invokes :meth:`reap` after some polling delay. + _timer = None + + def __init__(self, broker, proc, kill, wait_on_shutdown): + self.broker = broker + self.proc = proc + self.kill = kill + self.wait_on_shutdown = wait_on_shutdown + self._tries = 0 + + def _signal_child(self, signum): + # For processes like sudo we cannot actually send sudo a signal, + # because it is setuid, so this is best-effort only. + LOG.debug('%r: sending %s', self.proc, SIGNAL_BY_NUM[signum]) + try: + os.kill(self.proc.pid, signum) + except OSError: + e = sys.exc_info()[1] + if e.args[0] != errno.EPERM: + raise + + def _calc_delay(self, count): + """ + Calculate a poll delay given `count` attempts have already been made. + These constants have no principle, they just produce rapid but still + relatively conservative retries. + """ + delay = 0.05 + for _ in xrange(count): + delay *= 1.72 + return delay + + def _on_broker_shutdown(self): + """ + Respond to :class:`Broker` shutdown by cancelling the reap timer if + :attr:`Router.await_children_at_shutdown` is disabled. Otherwise + shutdown is delayed for up to :attr:`Broker.shutdown_timeout` for + subprocesses may have no intention of exiting any time soon. + """ + if not self.wait_on_shutdown: + self._timer.cancel() + + def _install_timer(self, delay): + new = self._timer is None + self._timer = self.broker.timers.schedule( + when=mitogen.core.now() + delay, + func=self.reap, + ) + if new: + mitogen.core.listen(self.broker, 'shutdown', + self._on_broker_shutdown) + + def _remove_timer(self): + if self._timer and self._timer.active: + self._timer.cancel() + mitogen.core.unlisten(self.broker, 'shutdown', + self._on_broker_shutdown) + + def reap(self): + """ + Reap the child process during disconnection. + """ + status = self.proc.poll() + if status is not None: + LOG.debug('%r: %s', self.proc, returncode_to_str(status)) + mitogen.core.fire(self.proc, 'exit') + self._remove_timer() + return + + self._tries += 1 + if self._tries > 20: + LOG.warning('%r: child will not exit, giving up', self) + self._remove_timer() + return + + delay = self._calc_delay(self._tries - 1) + LOG.debug('%r still running after IO disconnect, recheck in %.03fs', + self.proc, delay) + self._install_timer(delay) + + if not self.kill: + pass + elif self._tries == 2: + self._signal_child(signal.SIGTERM) + elif self._tries == 6: # roughly 4 seconds + self._signal_child(signal.SIGKILL) + + +class Process(object): + """ + Process objects provide a uniform interface to the :mod:`subprocess` and + :mod:`mitogen.fork`. This class is extended by :class:`PopenProcess` and + :class:`mitogen.fork.Process`. + + :param int pid: + The process ID. + :param file stdin: + File object attached to standard input. + :param file stdout: + File object attached to standard output. + :param file stderr: + File object attached to standard error, or :data:`None`. + """ + #: Name of the process used in logs. Set to the stream/context name by + #: :class:`Connection`. + name = None + + def __init__(self, pid, stdin, stdout, stderr=None): + #: The process ID. + self.pid = pid + #: File object attached to standard input. + self.stdin = stdin + #: File object attached to standard output. + self.stdout = stdout + #: File object attached to standard error. + self.stderr = stderr + + def __repr__(self): + return '%s %s pid %d' % ( + type(self).__name__, + self.name, + self.pid, + ) + + def poll(self): + """ + Fetch the child process exit status, or :data:`None` if it is still + running. This should be overridden by subclasses. + + :returns: + Exit status in the style of the :attr:`subprocess.Popen.returncode` + attribute, i.e. with signals represented by a negative integer. + """ + raise NotImplementedError() + + +class PopenProcess(Process): + """ + :class:`Process` subclass wrapping a :class:`subprocess.Popen` object. + + :param subprocess.Popen proc: + The subprocess. + """ + def __init__(self, proc, stdin, stdout, stderr=None): + super(PopenProcess, self).__init__(proc.pid, stdin, stdout, stderr) + #: The subprocess. + self.proc = proc + + def poll(self): + return self.proc.poll() + + +class ModuleForwarder(object): + """ + Respond to :data:`mitogen.core.GET_MODULE` requests in a child 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' + + 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.protocol.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.protocol.sent_modules: + return + + LOG.debug('%r._on_forward_module() sending %r to %r via %r', + self, fullname, context_id, stream.protocol.remote_id) + self._send_module_and_related(stream, fullname) + if stream.protocol.remote_id != context_id: + stream.protocol._send( + mitogen.core.Message( + data=msg.data, + handle=mitogen.core.FORWARD_MODULE, + dst_id=stream.protocol.remote_id, + ) + ) + + def _on_get_module(self, msg): + if msg.is_dead: + return + + fullname = msg.data.decode('utf-8') + LOG.debug('%r: %s requested by context %d', self, fullname, msg.src_id) + callback = lambda: self._on_cache_callback(msg, fullname) + self.importer._request_module(fullname, callback) + + def _on_cache_callback(self, msg, fullname): + stream = self.router.stream_by_id(msg.src_id) + LOG.debug('%r: sending %s to %r', self, fullname, stream) + 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: %s not in cache (for %s)', + self, related, fullname) + + self._send_one_module(stream, tup) + + def _send_one_module(self, stream, tup): + if tup[0] not in stream.protocol.sent_modules: + stream.protocol.sent_modules.add(tup[0]) + self.router._async_route( + mitogen.core.Message.pickled( + tup, + dst_id=stream.protocol.remote_id, + handle=mitogen.core.LOAD_MODULE, + ) + ) diff --git a/mitogen-0.3.9/mitogen/podman.py b/mitogen-0.3.9/mitogen/podman.py new file mode 100644 index 0000000..acc46a3 --- /dev/null +++ b/mitogen-0.3.9/mitogen/podman.py @@ -0,0 +1,73 @@ +# Copyright 2019, David Wilson +# Copyright 2021, Mitogen contributors +# +# 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.parent + + +LOG = logging.getLogger(__name__) + + +class Options(mitogen.parent.Options): + container = None + username = None + podman_path = 'podman' + + def __init__(self, container=None, podman_path=None, username=None, + **kwargs): + super(Options, self).__init__(**kwargs) + assert container is not None + self.container = container + if podman_path: + self.podman_path = podman_path + if username: + self.username = username + + +class Connection(mitogen.parent.Connection): + options_class = Options + child_is_immediate_subprocess = False + + # TODO: better way of capturing errors such as "No such container." + create_child_args = { + 'merge_stdio': True + } + + def _get_name(self): + return u'podman.' + self.options.container + + def get_boot_command(self): + args = [self.options.podman_path, 'exec'] + if self.options.username: + args += ['--user=' + self.options.username] + args += ["--interactive", "--", self.options.container] + return args + super(Connection, self).get_boot_command() diff --git a/mitogen-0.3.9/mitogen/profiler.py b/mitogen-0.3.9/mitogen/profiler.py new file mode 100644 index 0000000..512a593 --- /dev/null +++ b/mitogen-0.3.9/mitogen/profiler.py @@ -0,0 +1,164 @@ +# 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 shutil +import subprocess +import sys +import tempfile +import time + + +def try_merge(stats, path): + try: + stats.add(path) + return True + except Exception as e: + print('%s failed. Will retry. %s' % (path, e)) + return False + + +def merge_stats(outpath, inpaths): + first, rest = inpaths[0], inpaths[1:] + for x in range(1): + 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) + + pstats.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__.lstrip()) + 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.3.9/mitogen/select.py b/mitogen-0.3.9/mitogen/select.py new file mode 100644 index 0000000..2d87574 --- /dev/null +++ b/mitogen-0.3.9/mitogen/select.py @@ -0,0 +1,348 @@ +# 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