diff --git a/ansible_mitogen/target.py b/ansible_mitogen/target.py index f9cef43f..30666231 100644 --- a/ansible_mitogen/target.py +++ b/ansible_mitogen/target.py @@ -31,14 +31,8 @@ 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 -from __future__ import unicode_literals - import errno -import functools import grp -import json -import logging import operator import os import pwd @@ -51,10 +45,32 @@ import tempfile import traceback import types +# Absolute imports for <2.5. +logging = __import__('logging') + import mitogen.core import mitogen.fork import mitogen.parent import mitogen.service +from mitogen.core import b + +try: + import json +except ImportError: + import simplejson as json + +try: + reduce +except ImportError: + # Python 2.4 + 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 @@ -70,14 +86,14 @@ import ansible_mitogen.runner LOG = logging.getLogger(__name__) MAKE_TEMP_FAILED_MSG = ( - "Unable to find a useable temporary directory. This likely means no\n" - "system-supplied TMP directory can be written to, or all directories\n" - "were mounted on 'noexec' filesystems.\n" - "\n" - "The following paths were tried:\n" - " %(namelist)s\n" - "\n" - "Please check '-vvv' output for a log of individual path errors." + 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" %(namelist)s\n" + u"\n" + u"Please check '-vvv' output for a log of individual path errors." ) @@ -99,7 +115,7 @@ def subprocess__Popen__close_fds(self, but): a version that is O(fds) rather than O(_SC_OPEN_MAX). """ try: - names = os.listdir('/proc/self/fd') + 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) @@ -118,9 +134,9 @@ def subprocess__Popen__close_fds(self, but): if ( - sys.platform.startswith('linux') and - sys.version < '3.0' and - hasattr(subprocess.Popen, '_close_fds') and + sys.platform.startswith(u'linux') and + sys.version < u'3.0' and + hasattr(subprocess.Popen, u'_close_fds') and not mitogen.is_master ): subprocess.Popen._original_close_fds = subprocess.Popen._close_fds @@ -142,7 +158,7 @@ def get_small_file(context, path): Bytestring file data. """ pool = mitogen.service.get_or_create_pool(router=context.router) - service = pool.get_service('mitogen.service.PushFileService') + service = pool.get_service(u'mitogen.service.PushFileService') return service.get(path) @@ -184,9 +200,10 @@ def transfer_file(context, in_path, out_path, sync=False, set_owner=False): if not ok: raise IOError('transfer of %r was interrupted.' % (in_path,)) - os.fchmod(fp.fileno(), metadata['mode']) + set_file_mode(tmp_path, metadata['mode'], fd=fp.fileno()) if set_owner: - set_fd_owner(fp.fileno(), metadata['owner'], metadata['group']) + set_file_owner(tmp_path, metadata['owner'], metadata['group'], + fd=fp.fileno()) finally: fp.close() @@ -209,7 +226,8 @@ def prune_tree(path): try: os.unlink(path) return - except OSError as e: + 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) @@ -219,7 +237,8 @@ def prune_tree(path): # 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 as e: + except OSError: + e = sys.exc_info()[1] LOG.warning('prune_tree(%r): %s', path, e) try: @@ -227,7 +246,8 @@ def prune_tree(path): if name not in ('.', '..'): prune_tree(os.path.join(path, name)) os.rmdir(path) - except OSError as e: + except OSError: + e = sys.exc_info()[1] LOG.error('prune_tree(%r): %s', path, e) @@ -248,7 +268,8 @@ def is_good_temp_dir(path): if not os.path.exists(path): try: os.makedirs(path, mode=int('0700', 8)) - except OSError as e: + 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 @@ -258,14 +279,16 @@ def is_good_temp_dir(path): prefix='ansible_mitogen_is_good_temp_dir', dir=path, ) - except (OSError, IOError) as e: + 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 as e: + except OSError: + e = sys.exc_info()[1] LOG.debug('temp dir %r unusable: chmod failed: %s', path, e) return False @@ -273,7 +296,8 @@ def is_good_temp_dir(path): # 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 as e: + except OSError: + e = sys.exc_info()[1] LOG.debug('temp dir %r unusable: %s', path, e) return False finally: @@ -351,9 +375,9 @@ def init_child(econtext, log_level, candidate_temp_dirs): good_temp_dir = find_good_temp_dir(candidate_temp_dirs) return { - 'fork_context': _fork_parent, - 'home_dir': mitogen.core.to_text(os.path.expanduser('~')), - 'good_temp_dir': good_temp_dir, + u'fork_context': _fork_parent, + u'home_dir': mitogen.core.to_text(os.path.expanduser('~')), + u'good_temp_dir': good_temp_dir, } @@ -379,7 +403,7 @@ def run_module(kwargs): """ runner_name = kwargs.pop('runner_name') klass = getattr(ansible_mitogen.runner, runner_name) - impl = klass(**kwargs) + impl = klass(**mitogen.core.Kwargs(kwargs)) return impl.run() @@ -412,8 +436,11 @@ class AsyncRunner(object): dct.setdefault('ansible_job_id', self.job_id) dct.setdefault('data', '') - with open(self.path + '.tmp', 'w') as fp: + 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): @@ -565,8 +592,8 @@ def exec_args(args, in_data='', chdir=None, shell=None, emulate_tty=False): stdout, stderr = proc.communicate(in_data) if emulate_tty: - stdout = stdout.replace(b'\n', b'\r\n') - return proc.returncode, stdout, stderr or b'' + 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): @@ -598,7 +625,7 @@ def read_path(path): return open(path, 'rb').read() -def set_fd_owner(fd, owner, group=None): +def set_file_owner(path, owner, group=None, fd=None): if owner: uid = pwd.getpwnam(owner).pw_uid else: @@ -609,7 +636,11 @@ def set_fd_owner(fd, owner, group=None): else: gid = os.getegid() - os.fchown(fd, (uid, gid)) + 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, @@ -627,9 +658,9 @@ def write_path(path, s, owner=None, group=None, mode=None, try: try: if mode: - os.fchmod(fp.fileno(), mode) + set_file_mode(tmp_path, mode, fd=fp.fileno()) if owner or group: - set_fd_owner(fp.fileno(), owner, group) + set_file_owner(tmp_path, owner, group, fd=fp.fileno()) fp.write(s) finally: fp.close() @@ -676,7 +707,7 @@ def apply_mode_spec(spec, mode): mask = CHMOD_MASKS[ch] bits = CHMOD_BITS[ch] cur_perm_bits = mode & mask - new_perm_bits = functools.reduce(operator.or_, (bits[p] for p in perms), 0) + new_perm_bits = reduce(operator.or_, (bits[p] for p in perms), 0) mode &= ~mask if op == '=': mode |= new_perm_bits @@ -687,15 +718,19 @@ def apply_mode_spec(spec, mode): return mode -def set_file_mode(path, spec): +def set_file_mode(path, spec, fd=None): """ Update the permissions of a file using the same syntax as chmod(1). """ - mode = os.stat(path).st_mode - - if spec.isdigit(): + if isinstance(spec, (int, 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) - os.chmod(path, new_mode) + if fd is not None and hasattr(os, 'fchmod'): + os.fchmod(fd, new_mode) + else: + os.chmod(path, new_mode)