From f649318707c081f9eb9c8b79cdb11af493b83461 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 27 Feb 2018 15:15:41 +0545 Subject: [PATCH] ansible: _remote_chmod() / _fixup_perms2() can be called sometimes. It's used at least by the copy module, even though the result is still mostly a no-op. _remote_chmod() doesn't accept octal mode, it accepts symbolic mode. So implement a symbolic parser in helpers.py. --- ansible_mitogen/connection/mitogen.py | 12 +++--- ansible_mitogen/helpers.py | 59 +++++++++++++++++++++++++++ ansible_mitogen/mixins.py | 7 +++- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/ansible_mitogen/connection/mitogen.py b/ansible_mitogen/connection/mitogen.py index 9ef139e4..77de6714 100644 --- a/ansible_mitogen/connection/mitogen.py +++ b/ansible_mitogen/connection/mitogen.py @@ -197,8 +197,8 @@ class Connection(ansible.plugins.connection.ConnectionBase): :returns: (return code, stdout bytes, stderr bytes) """ - return self.py_call(ansible_mitogen.helpers.exec_command, - cast(cmd), cast(in_data)) + return self.call(ansible_mitogen.helpers.exec_command, + cast(cmd), cast(in_data)) def fetch_file(self, in_path, out_path): """ @@ -210,8 +210,8 @@ class Connection(ansible.plugins.connection.ConnectionBase): :param str out_path: Local filesystem path to write. """ - output = self.py_call(ansible_mitogen.helpers.read_path, - cast(in_path)) + output = self.call(ansible_mitogen.helpers.read_path, + cast(in_path)) ansible_mitogen.helpers.write_path(out_path, output) def put_file(self, in_path, out_path): @@ -224,5 +224,5 @@ class Connection(ansible.plugins.connection.ConnectionBase): :param str out_path: Remote filesystem path to write. """ - self.py_call(ansible_mitogen.helpers.write_path, cast(out_path), - ansible_mitogen.helpers.read_path(in_path)) + self.call(ansible_mitogen.helpers.write_path, cast(out_path), + ansible_mitogen.helpers.read_path(in_path)) diff --git a/ansible_mitogen/helpers.py b/ansible_mitogen/helpers.py index 6b256b0c..526c74d1 100644 --- a/ansible_mitogen/helpers.py +++ b/ansible_mitogen/helpers.py @@ -26,8 +26,11 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import json +import operator import os import pwd +import re +import stat import subprocess import time @@ -165,3 +168,59 @@ def write_path(path, s): Writes bytes `s` to a filesystem `path`. """ open(path, 'wb').write(s) + + + +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 or_(it): + return reduce(operator.or_, it, 0) + + +def apply_mode_spec(spec, mode): + for clause in spec.split(','): + match = CHMOD_CLAUSE_PAT.match(clause) + who, op, perms = match.groups() + mask = CHMOD_MASKS[who] + bits = CHMOD_BITS[who] + for ch in who or 'a': + cur_perm_bits = mode & mask + new_perm_bits = or_(bits[p] for p in perms) + mode &= ~mask + if op == '=': + mode |= new_perm_bits + elif op == '+': + mode |= new_perm_bits | cur_per_bits + else: + mode |= cur_perm_bits & ~new_perm_bits + return mode + + +def set_file_mode(path, spec): + """ + Update the permissions of a file using the same syntax as chmod(1). + """ + mode = os.stat(path).st_mode + + if spec.is_digit(): + new_mode = int(spec, 8) + else: + new_mode = apply_mode_spec(spec, mode) + + os.chmod(path, new_mode) diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index c3970a20..ca0af751 100644 --- a/ansible_mitogen/mixins.py +++ b/ansible_mitogen/mixins.py @@ -117,11 +117,14 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase): def _fixup_perms2(self, remote_paths, remote_user=None, execute=True): # replaces 83 lines - assert False, "_fixup_perms2() should never be called." + if not execute: + return self._remote_chmod(remote_paths, mode='u+x') + # Do nothing unless request was to set the execute bit. + return self.COMMAND_RESULT.copy() def _remote_chmod(self, paths, mode, sudoable=False): return self.fake_shell(lambda: mitogen.master.Select.all( - self._connection.call_async(os.chmod, path, mode) + self._connection.call_async(helpers.set_file_mode, path, mode) for path in paths ))