diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index b2396d6661d..21eca99b0c0 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -2273,58 +2273,67 @@ class AnsibleModule(object): native_dest_dir = b_dest_dir native_suffix = os.path.basename(b_dest) native_prefix = b('.ansible_tmp') + error_msg = None + tmp_dest_name = None try: tmp_dest_fd, tmp_dest_name = tempfile.mkstemp(prefix=native_prefix, dir=native_dest_dir, suffix=native_suffix) except (OSError, IOError): e = get_exception() - self.fail_json(msg='The destination directory (%s) is not writable by the current user. Error was: %s' % (os.path.dirname(dest), e)) + error_msg = 'The destination directory (%s) is not writable by the current user. Error was: %s' % (os.path.dirname(dest), e) except TypeError: # We expect that this is happening because python3.4.x and # below can't handle byte strings in mkstemp(). Traceback # would end in something like: # file = _os.path.join(dir, pre + name + suf) # TypeError: can't concat bytes to str - self.fail_json(msg='Failed creating temp file for atomic move. This usually happens when using Python3 less than Python3.5. ' - 'Please use Python2.x or Python3.5 or greater.', exception=traceback.format_exc()) + error_msg = ('Failed creating temp file for atomic move. This usually happens when using Python3 less than Python3.5. ' + 'Please use Python2.x or Python3.5 or greater.') + finally: + if error_msg: + if unsafe_writes: + self._unsafe_writes(b_src, b_dest) + else: + self.fail_json(msg=error_msg, exception=traceback.format_exc()) - b_tmp_dest_name = to_bytes(tmp_dest_name, errors='surrogate_or_strict') + if tmp_dest_name: + b_tmp_dest_name = to_bytes(tmp_dest_name, errors='surrogate_or_strict') - try: try: - # close tmp file handle before file operations to prevent text file busy errors on vboxfs synced folders (windows host) - os.close(tmp_dest_fd) - # leaves tmp file behind when sudo and not root - try: - shutil.move(b_src, b_tmp_dest_name) - except OSError: - # cleanup will happen by 'rm' of tempdir - # copy2 will preserve some metadata - shutil.copy2(b_src, b_tmp_dest_name) - - if self.selinux_enabled(): - self.set_context_if_different( - b_tmp_dest_name, context, False) try: - tmp_stat = os.stat(b_tmp_dest_name) - if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid): - os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid) - except OSError: - e = get_exception() - if e.errno != errno.EPERM: - raise - try: - os.rename(b_tmp_dest_name, b_dest) + # close tmp file handle before file operations to prevent text file busy errors on vboxfs synced folders (windows host) + os.close(tmp_dest_fd) + # leaves tmp file behind when sudo and not root + try: + shutil.move(b_src, b_tmp_dest_name) + except OSError: + # cleanup will happen by 'rm' of tempdir + # copy2 will preserve some metadata + shutil.copy2(b_src, b_tmp_dest_name) + + if self.selinux_enabled(): + self.set_context_if_different( + b_tmp_dest_name, context, False) + try: + tmp_stat = os.stat(b_tmp_dest_name) + if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid): + os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid) + except OSError: + e = get_exception() + if e.errno != errno.EPERM: + raise + try: + os.rename(b_tmp_dest_name, b_dest) + except (shutil.Error, OSError, IOError): + e = get_exception() + if unsafe_writes and e.errno == errno.EBUSY: + self._unsafe_writes(b_tmp_dest_name, b_dest) + else: + self.fail_json(msg='Unable to rename file: %s to %s: %s' % (src, dest, e), exception=traceback.format_exc()) except (shutil.Error, OSError, IOError): e = get_exception() - if unsafe_writes and e.errno == errno.EBUSY: - self._unsafe_writes(b_tmp_dest_name, b_dest) - else: - self.fail_json(msg='Unable to rename file: %s to %s: %s' % (src, dest, e), exception=traceback.format_exc()) - except (shutil.Error, OSError, IOError): - e = get_exception() - self.fail_json(msg='Failed to replace file: %s to %s: %s' % (src, dest, e), exception=traceback.format_exc()) - finally: - self.cleanup(b_tmp_dest_name) + self.fail_json(msg='Failed to replace file: %s to %s: %s' % (src, dest, e), exception=traceback.format_exc()) + finally: + self.cleanup(b_tmp_dest_name) if creating: # make sure the file has the correct permissions diff --git a/lib/ansible/modules/files/copy.py b/lib/ansible/modules/files/copy.py index 33c37afed79..586aa71a618 100644 --- a/lib/ansible/modules/files/copy.py +++ b/lib/ansible/modules/files/copy.py @@ -360,7 +360,8 @@ def main(): if "permission denied" in to_native(e).lower(): module.fail_json(msg="Destination directory %s is not accessible" % (os.path.dirname(dest))) module.fail_json(msg="Destination directory %s does not exist" % (os.path.dirname(dest))) - if not os.access(os.path.dirname(b_dest), os.W_OK): + + if not os.access(os.path.dirname(b_dest), os.W_OK) and not module.params['unsafe_writes']: module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest))) backup_file = None