1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> 5# Copyright: (c) 2017, Ansible Project 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import absolute_import, division, print_function 9__metaclass__ = type 10 11 12DOCUMENTATION = r''' 13--- 14module: copy 15version_added: historical 16short_description: Copy files to remote locations 17description: 18 - The C(copy) module copies a file from the local or remote machine to a location on the remote machine. 19 - Use the M(ansible.builtin.fetch) module to copy files from remote locations to the local box. 20 - If you need variable interpolation in copied files, use the M(ansible.builtin.template) module. 21 Using a variable in the C(content) field will result in unpredictable output. 22 - For Windows targets, use the M(ansible.windows.win_copy) module instead. 23options: 24 src: 25 description: 26 - Local path to a file to copy to the remote server. 27 - This can be absolute or relative. 28 - If path is a directory, it is copied recursively. In this case, if path ends 29 with "/", only inside contents of that directory are copied to destination. 30 Otherwise, if it does not end with "/", the directory itself with all contents 31 is copied. This behavior is similar to the C(rsync) command line tool. 32 type: path 33 content: 34 description: 35 - When used instead of C(src), sets the contents of a file directly to the specified value. 36 - Works only when C(dest) is a file. Creates the file if it does not exist. 37 - For advanced formatting or if C(content) contains a variable, use the 38 M(ansible.builtin.template) module. 39 type: str 40 version_added: '1.1' 41 dest: 42 description: 43 - Remote absolute path where the file should be copied to. 44 - If C(src) is a directory, this must be a directory too. 45 - If C(dest) is a non-existent path and if either C(dest) ends with "/" or C(src) is a directory, C(dest) is created. 46 - If I(dest) is a relative path, the starting directory is determined by the remote host. 47 - If C(src) and C(dest) are files, the parent directory of C(dest) is not created and the task fails if it does not already exist. 48 type: path 49 required: yes 50 backup: 51 description: 52 - Create a backup file including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly. 53 type: bool 54 default: no 55 version_added: '0.7' 56 force: 57 description: 58 - Influence whether the remote file must always be replaced. 59 - If C(yes), the remote file will be replaced when contents are different than the source. 60 - If C(no), the file will only be transferred if the destination does not exist. 61 - Alias C(thirsty) has been deprecated and will be removed in 2.13. 62 type: bool 63 default: yes 64 aliases: [ thirsty ] 65 version_added: '1.1' 66 mode: 67 description: 68 - The permissions of the destination file or directory. 69 - For those used to C(/usr/bin/chmod) remember that modes are actually octal numbers. 70 You must either add a leading zero so that Ansible's YAML parser knows it is an octal number 71 (like C(0644) or C(01777))or quote it (like C('644') or C('1777')) so Ansible receives a string 72 and can do its own conversion from string into number. Giving Ansible a number without following 73 one of these rules will end up with a decimal number which will have unexpected results. 74 - As of Ansible 1.8, the mode may be specified as a symbolic mode (for example, C(u+rwx) or C(u=rw,g=r,o=r)). 75 - As of Ansible 2.3, the mode may also be the special string C(preserve). 76 - C(preserve) means that the file will be given the same permissions as the source file. 77 - When doing a recursive copy, see also C(directory_mode). 78 type: path 79 directory_mode: 80 description: 81 - When doing a recursive copy set the mode for the directories. 82 - If this is not set we will use the system defaults. 83 - The mode is only set on directories which are newly created, and will not affect those that already existed. 84 type: raw 85 version_added: '1.5' 86 remote_src: 87 description: 88 - Influence whether C(src) needs to be transferred or already is present remotely. 89 - If C(no), it will search for C(src) at originating/master machine. 90 - If C(yes) it will go to the remote/target machine for the C(src). 91 - C(remote_src) supports recursive copying as of version 2.8. 92 - C(remote_src) only works with C(mode=preserve) as of version 2.6. 93 type: bool 94 default: no 95 version_added: '2.0' 96 follow: 97 description: 98 - This flag indicates that filesystem links in the destination, if they exist, should be followed. 99 type: bool 100 default: no 101 version_added: '1.8' 102 local_follow: 103 description: 104 - This flag indicates that filesystem links in the source tree, if they exist, should be followed. 105 type: bool 106 default: yes 107 version_added: '2.4' 108 checksum: 109 description: 110 - SHA1 checksum of the file being transferred. 111 - Used to validate that the copy of the file was successful. 112 - If this is not provided, ansible will use the local calculated checksum of the src file. 113 type: str 114 version_added: '2.5' 115extends_documentation_fragment: 116- decrypt 117- files 118- validate 119notes: 120- The M(ansible.builtin.copy) module recursively copy facility does not scale to lots (>hundreds) of files. 121- Supports C(check_mode). 122seealso: 123- module: ansible.builtin.assemble 124- module: ansible.builtin.fetch 125- module: ansible.builtin.file 126- module: ansible.builtin.template 127- module: ansible.posix.synchronize 128- module: ansible.windows.win_copy 129author: 130- Ansible Core Team 131- Michael DeHaan 132''' 133 134EXAMPLES = r''' 135- name: Copy file with owner and permissions 136 ansible.builtin.copy: 137 src: /srv/myfiles/foo.conf 138 dest: /etc/foo.conf 139 owner: foo 140 group: foo 141 mode: '0644' 142 143- name: Copy file with owner and permission, using symbolic representation 144 ansible.builtin.copy: 145 src: /srv/myfiles/foo.conf 146 dest: /etc/foo.conf 147 owner: foo 148 group: foo 149 mode: u=rw,g=r,o=r 150 151- name: Another symbolic mode example, adding some permissions and removing others 152 ansible.builtin.copy: 153 src: /srv/myfiles/foo.conf 154 dest: /etc/foo.conf 155 owner: foo 156 group: foo 157 mode: u+rw,g-wx,o-rwx 158 159- name: Copy a new "ntp.conf" file into place, backing up the original if it differs from the copied version 160 ansible.builtin.copy: 161 src: /mine/ntp.conf 162 dest: /etc/ntp.conf 163 owner: root 164 group: root 165 mode: '0644' 166 backup: yes 167 168- name: Copy a new "sudoers" file into place, after passing validation with visudo 169 ansible.builtin.copy: 170 src: /mine/sudoers 171 dest: /etc/sudoers 172 validate: /usr/sbin/visudo -csf %s 173 174- name: Copy a "sudoers" file on the remote machine for editing 175 ansible.builtin.copy: 176 src: /etc/sudoers 177 dest: /etc/sudoers.edit 178 remote_src: yes 179 validate: /usr/sbin/visudo -csf %s 180 181- name: Copy using inline content 182 ansible.builtin.copy: 183 content: '# This file was moved to /etc/other.conf' 184 dest: /etc/mine.conf 185 186- name: If follow=yes, /path/to/file will be overwritten by contents of foo.conf 187 ansible.builtin.copy: 188 src: /etc/foo.conf 189 dest: /path/to/link # link to /path/to/file 190 follow: yes 191 192- name: If follow=no, /path/to/link will become a file and be overwritten by contents of foo.conf 193 ansible.builtin.copy: 194 src: /etc/foo.conf 195 dest: /path/to/link # link to /path/to/file 196 follow: no 197''' 198 199RETURN = r''' 200dest: 201 description: Destination file/path. 202 returned: success 203 type: str 204 sample: /path/to/file.txt 205src: 206 description: Source file used for the copy on the target machine. 207 returned: changed 208 type: str 209 sample: /home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source 210md5sum: 211 description: MD5 checksum of the file after running copy. 212 returned: when supported 213 type: str 214 sample: 2a5aeecc61dc98c4d780b14b330e3282 215checksum: 216 description: SHA1 checksum of the file after running copy. 217 returned: success 218 type: str 219 sample: 6e642bb8dd5c2e027bf21dd923337cbb4214f827 220backup_file: 221 description: Name of backup file created. 222 returned: changed and if backup=yes 223 type: str 224 sample: /path/to/file.txt.2015-02-12@22:09~ 225gid: 226 description: Group id of the file, after execution. 227 returned: success 228 type: int 229 sample: 100 230group: 231 description: Group of the file, after execution. 232 returned: success 233 type: str 234 sample: httpd 235owner: 236 description: Owner of the file, after execution. 237 returned: success 238 type: str 239 sample: httpd 240uid: 241 description: Owner id of the file, after execution. 242 returned: success 243 type: int 244 sample: 100 245mode: 246 description: Permissions of the target, after execution. 247 returned: success 248 type: str 249 sample: 0644 250size: 251 description: Size of the target, after execution. 252 returned: success 253 type: int 254 sample: 1220 255state: 256 description: State of the target, after execution. 257 returned: success 258 type: str 259 sample: file 260''' 261 262import errno 263import filecmp 264import grp 265import os 266import os.path 267import platform 268import pwd 269import shutil 270import stat 271import tempfile 272import traceback 273 274from ansible.module_utils.basic import AnsibleModule 275from ansible.module_utils.common.process import get_bin_path 276from ansible.module_utils._text import to_bytes, to_native 277from ansible.module_utils.six import PY3 278 279 280# The AnsibleModule object 281module = None 282 283 284class AnsibleModuleError(Exception): 285 def __init__(self, results): 286 self.results = results 287 288 289# Once we get run_command moved into common, we can move this into a common/files module. We can't 290# until then because of the module.run_command() method. We may need to move it into 291# basic::AnsibleModule() until then but if so, make it a private function so that we don't have to 292# keep it for backwards compatibility later. 293def clear_facls(path): 294 setfacl = get_bin_path('setfacl') 295 # FIXME "setfacl -b" is available on Linux and FreeBSD. There is "setfacl -D e" on z/OS. Others? 296 acl_command = [setfacl, '-b', path] 297 b_acl_command = [to_bytes(x) for x in acl_command] 298 rc, out, err = module.run_command(b_acl_command, environ_update=dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')) 299 if rc != 0: 300 raise RuntimeError('Error running "{0}": stdout: "{1}"; stderr: "{2}"'.format(' '.join(b_acl_command), out, err)) 301 302 303def split_pre_existing_dir(dirname): 304 ''' 305 Return the first pre-existing directory and a list of the new directories that will be created. 306 ''' 307 head, tail = os.path.split(dirname) 308 b_head = to_bytes(head, errors='surrogate_or_strict') 309 if head == '': 310 return ('.', [tail]) 311 if not os.path.exists(b_head): 312 if head == '/': 313 raise AnsibleModuleError(results={'msg': "The '/' directory doesn't exist on this machine."}) 314 (pre_existing_dir, new_directory_list) = split_pre_existing_dir(head) 315 else: 316 return (head, [tail]) 317 new_directory_list.append(tail) 318 return (pre_existing_dir, new_directory_list) 319 320 321def adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed): 322 ''' 323 Walk the new directories list and make sure that permissions are as we would expect 324 ''' 325 326 if new_directory_list: 327 working_dir = os.path.join(pre_existing_dir, new_directory_list.pop(0)) 328 directory_args['path'] = working_dir 329 changed = module.set_fs_attributes_if_different(directory_args, changed) 330 changed = adjust_recursive_directory_permissions(working_dir, new_directory_list, module, directory_args, changed) 331 return changed 332 333 334def chown_recursive(path, module): 335 changed = False 336 owner = module.params['owner'] 337 group = module.params['group'] 338 339 if owner is not None: 340 if not module.check_mode: 341 for dirpath, dirnames, filenames in os.walk(path): 342 owner_changed = module.set_owner_if_different(dirpath, owner, False) 343 if owner_changed is True: 344 changed = owner_changed 345 for dir in [os.path.join(dirpath, d) for d in dirnames]: 346 owner_changed = module.set_owner_if_different(dir, owner, False) 347 if owner_changed is True: 348 changed = owner_changed 349 for file in [os.path.join(dirpath, f) for f in filenames]: 350 owner_changed = module.set_owner_if_different(file, owner, False) 351 if owner_changed is True: 352 changed = owner_changed 353 else: 354 uid = pwd.getpwnam(owner).pw_uid 355 for dirpath, dirnames, filenames in os.walk(path): 356 owner_changed = (os.stat(dirpath).st_uid != uid) 357 if owner_changed is True: 358 changed = owner_changed 359 for dir in [os.path.join(dirpath, d) for d in dirnames]: 360 owner_changed = (os.stat(dir).st_uid != uid) 361 if owner_changed is True: 362 changed = owner_changed 363 for file in [os.path.join(dirpath, f) for f in filenames]: 364 owner_changed = (os.stat(file).st_uid != uid) 365 if owner_changed is True: 366 changed = owner_changed 367 if group is not None: 368 if not module.check_mode: 369 for dirpath, dirnames, filenames in os.walk(path): 370 group_changed = module.set_group_if_different(dirpath, group, False) 371 if group_changed is True: 372 changed = group_changed 373 for dir in [os.path.join(dirpath, d) for d in dirnames]: 374 group_changed = module.set_group_if_different(dir, group, False) 375 if group_changed is True: 376 changed = group_changed 377 for file in [os.path.join(dirpath, f) for f in filenames]: 378 group_changed = module.set_group_if_different(file, group, False) 379 if group_changed is True: 380 changed = group_changed 381 else: 382 gid = grp.getgrnam(group).gr_gid 383 for dirpath, dirnames, filenames in os.walk(path): 384 group_changed = (os.stat(dirpath).st_gid != gid) 385 if group_changed is True: 386 changed = group_changed 387 for dir in [os.path.join(dirpath, d) for d in dirnames]: 388 group_changed = (os.stat(dir).st_gid != gid) 389 if group_changed is True: 390 changed = group_changed 391 for file in [os.path.join(dirpath, f) for f in filenames]: 392 group_changed = (os.stat(file).st_gid != gid) 393 if group_changed is True: 394 changed = group_changed 395 396 return changed 397 398 399def copy_diff_files(src, dest, module): 400 """Copy files that are different between `src` directory and `dest` directory.""" 401 402 changed = False 403 owner = module.params['owner'] 404 group = module.params['group'] 405 local_follow = module.params['local_follow'] 406 diff_files = filecmp.dircmp(src, dest).diff_files 407 if len(diff_files): 408 changed = True 409 if not module.check_mode: 410 for item in diff_files: 411 src_item_path = os.path.join(src, item) 412 dest_item_path = os.path.join(dest, item) 413 b_src_item_path = to_bytes(src_item_path, errors='surrogate_or_strict') 414 b_dest_item_path = to_bytes(dest_item_path, errors='surrogate_or_strict') 415 if os.path.islink(b_src_item_path) and local_follow is False: 416 linkto = os.readlink(b_src_item_path) 417 os.symlink(linkto, b_dest_item_path) 418 else: 419 shutil.copyfile(b_src_item_path, b_dest_item_path) 420 shutil.copymode(b_src_item_path, b_dest_item_path) 421 422 if owner is not None: 423 module.set_owner_if_different(b_dest_item_path, owner, False) 424 if group is not None: 425 module.set_group_if_different(b_dest_item_path, group, False) 426 changed = True 427 return changed 428 429 430def copy_left_only(src, dest, module): 431 """Copy files that exist in `src` directory only to the `dest` directory.""" 432 433 changed = False 434 owner = module.params['owner'] 435 group = module.params['group'] 436 local_follow = module.params['local_follow'] 437 left_only = filecmp.dircmp(src, dest).left_only 438 if len(left_only): 439 changed = True 440 if not module.check_mode: 441 for item in left_only: 442 src_item_path = os.path.join(src, item) 443 dest_item_path = os.path.join(dest, item) 444 b_src_item_path = to_bytes(src_item_path, errors='surrogate_or_strict') 445 b_dest_item_path = to_bytes(dest_item_path, errors='surrogate_or_strict') 446 447 if os.path.islink(b_src_item_path) and os.path.isdir(b_src_item_path) and local_follow is True: 448 shutil.copytree(b_src_item_path, b_dest_item_path, symlinks=not(local_follow)) 449 chown_recursive(b_dest_item_path, module) 450 451 if os.path.islink(b_src_item_path) and os.path.isdir(b_src_item_path) and local_follow is False: 452 linkto = os.readlink(b_src_item_path) 453 os.symlink(linkto, b_dest_item_path) 454 455 if os.path.islink(b_src_item_path) and os.path.isfile(b_src_item_path) and local_follow is True: 456 shutil.copyfile(b_src_item_path, b_dest_item_path) 457 if owner is not None: 458 module.set_owner_if_different(b_dest_item_path, owner, False) 459 if group is not None: 460 module.set_group_if_different(b_dest_item_path, group, False) 461 462 if os.path.islink(b_src_item_path) and os.path.isfile(b_src_item_path) and local_follow is False: 463 linkto = os.readlink(b_src_item_path) 464 os.symlink(linkto, b_dest_item_path) 465 466 if not os.path.islink(b_src_item_path) and os.path.isfile(b_src_item_path): 467 shutil.copyfile(b_src_item_path, b_dest_item_path) 468 shutil.copymode(b_src_item_path, b_dest_item_path) 469 470 if owner is not None: 471 module.set_owner_if_different(b_dest_item_path, owner, False) 472 if group is not None: 473 module.set_group_if_different(b_dest_item_path, group, False) 474 475 if not os.path.islink(b_src_item_path) and os.path.isdir(b_src_item_path): 476 shutil.copytree(b_src_item_path, b_dest_item_path, symlinks=not(local_follow)) 477 chown_recursive(b_dest_item_path, module) 478 479 changed = True 480 return changed 481 482 483def copy_common_dirs(src, dest, module): 484 changed = False 485 common_dirs = filecmp.dircmp(src, dest).common_dirs 486 for item in common_dirs: 487 src_item_path = os.path.join(src, item) 488 dest_item_path = os.path.join(dest, item) 489 b_src_item_path = to_bytes(src_item_path, errors='surrogate_or_strict') 490 b_dest_item_path = to_bytes(dest_item_path, errors='surrogate_or_strict') 491 diff_files_changed = copy_diff_files(b_src_item_path, b_dest_item_path, module) 492 left_only_changed = copy_left_only(b_src_item_path, b_dest_item_path, module) 493 if diff_files_changed or left_only_changed: 494 changed = True 495 496 # recurse into subdirectory 497 changed = changed or copy_common_dirs(os.path.join(src, item), os.path.join(dest, item), module) 498 return changed 499 500 501def main(): 502 503 global module 504 505 module = AnsibleModule( 506 # not checking because of daisy chain to file module 507 argument_spec=dict( 508 src=dict(type='path'), 509 _original_basename=dict(type='str'), # used to handle 'dest is a directory' via template, a slight hack 510 content=dict(type='str', no_log=True), 511 dest=dict(type='path', required=True), 512 backup=dict(type='bool', default=False), 513 force=dict(type='bool', default=True, aliases=['thirsty']), 514 validate=dict(type='str'), 515 directory_mode=dict(type='raw'), 516 remote_src=dict(type='bool'), 517 local_follow=dict(type='bool'), 518 checksum=dict(type='str'), 519 follow=dict(type='bool', default=False), 520 ), 521 add_file_common_args=True, 522 supports_check_mode=True, 523 ) 524 525 if module.params.get('thirsty'): 526 module.deprecate('The alias "thirsty" has been deprecated and will be removed, use "force" instead', 527 version='2.13', collection_name='ansible.builtin') 528 529 src = module.params['src'] 530 b_src = to_bytes(src, errors='surrogate_or_strict') 531 dest = module.params['dest'] 532 # Make sure we always have a directory component for later processing 533 if os.path.sep not in dest: 534 dest = '.{0}{1}'.format(os.path.sep, dest) 535 b_dest = to_bytes(dest, errors='surrogate_or_strict') 536 backup = module.params['backup'] 537 force = module.params['force'] 538 _original_basename = module.params.get('_original_basename', None) 539 validate = module.params.get('validate', None) 540 follow = module.params['follow'] 541 local_follow = module.params['local_follow'] 542 mode = module.params['mode'] 543 owner = module.params['owner'] 544 group = module.params['group'] 545 remote_src = module.params['remote_src'] 546 checksum = module.params['checksum'] 547 548 if not os.path.exists(b_src): 549 module.fail_json(msg="Source %s not found" % (src)) 550 if not os.access(b_src, os.R_OK): 551 module.fail_json(msg="Source %s not readable" % (src)) 552 553 # Preserve is usually handled in the action plugin but mode + remote_src has to be done on the 554 # remote host 555 if module.params['mode'] == 'preserve': 556 module.params['mode'] = '0%03o' % stat.S_IMODE(os.stat(b_src).st_mode) 557 mode = module.params['mode'] 558 559 checksum_dest = None 560 561 if os.path.isfile(src): 562 checksum_src = module.sha1(src) 563 else: 564 checksum_src = None 565 566 # Backwards compat only. This will be None in FIPS mode 567 try: 568 if os.path.isfile(src): 569 md5sum_src = module.md5(src) 570 else: 571 md5sum_src = None 572 except ValueError: 573 md5sum_src = None 574 575 changed = False 576 577 if checksum and checksum_src != checksum: 578 module.fail_json( 579 msg='Copied file does not match the expected checksum. Transfer failed.', 580 checksum=checksum_src, 581 expected_checksum=checksum 582 ) 583 584 # Special handling for recursive copy - create intermediate dirs 585 if dest.endswith(os.sep): 586 if _original_basename: 587 dest = os.path.join(dest, _original_basename) 588 b_dest = to_bytes(dest, errors='surrogate_or_strict') 589 dirname = os.path.dirname(dest) 590 b_dirname = to_bytes(dirname, errors='surrogate_or_strict') 591 if not os.path.exists(b_dirname): 592 try: 593 (pre_existing_dir, new_directory_list) = split_pre_existing_dir(dirname) 594 except AnsibleModuleError as e: 595 e.result['msg'] += ' Could not copy to {0}'.format(dest) 596 module.fail_json(**e.results) 597 598 os.makedirs(b_dirname) 599 directory_args = module.load_file_common_arguments(module.params) 600 directory_mode = module.params["directory_mode"] 601 if directory_mode is not None: 602 directory_args['mode'] = directory_mode 603 else: 604 directory_args['mode'] = None 605 adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list, module, directory_args, changed) 606 607 if os.path.isdir(b_dest): 608 basename = os.path.basename(src) 609 if _original_basename: 610 basename = _original_basename 611 dest = os.path.join(dest, basename) 612 b_dest = to_bytes(dest, errors='surrogate_or_strict') 613 614 if os.path.exists(b_dest): 615 if os.path.islink(b_dest) and follow: 616 b_dest = os.path.realpath(b_dest) 617 dest = to_native(b_dest, errors='surrogate_or_strict') 618 if not force: 619 module.exit_json(msg="file already exists", src=src, dest=dest, changed=False) 620 if os.access(b_dest, os.R_OK) and os.path.isfile(b_dest): 621 checksum_dest = module.sha1(dest) 622 else: 623 if not os.path.exists(os.path.dirname(b_dest)): 624 try: 625 # os.path.exists() can return false in some 626 # circumstances where the directory does not have 627 # the execute bit for the current user set, in 628 # which case the stat() call will raise an OSError 629 os.stat(os.path.dirname(b_dest)) 630 except OSError as e: 631 if "permission denied" in to_native(e).lower(): 632 module.fail_json(msg="Destination directory %s is not accessible" % (os.path.dirname(dest))) 633 module.fail_json(msg="Destination directory %s does not exist" % (os.path.dirname(dest))) 634 635 if not os.access(os.path.dirname(b_dest), os.W_OK) and not module.params['unsafe_writes']: 636 module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest))) 637 638 backup_file = None 639 if checksum_src != checksum_dest or os.path.islink(b_dest): 640 if not module.check_mode: 641 try: 642 if backup: 643 if os.path.exists(b_dest): 644 backup_file = module.backup_local(dest) 645 # allow for conversion from symlink. 646 if os.path.islink(b_dest): 647 os.unlink(b_dest) 648 open(b_dest, 'w').close() 649 if validate: 650 # if we have a mode, make sure we set it on the temporary 651 # file source as some validations may require it 652 if mode is not None: 653 module.set_mode_if_different(src, mode, False) 654 if owner is not None: 655 module.set_owner_if_different(src, owner, False) 656 if group is not None: 657 module.set_group_if_different(src, group, False) 658 if "%s" not in validate: 659 module.fail_json(msg="validate must contain %%s: %s" % (validate)) 660 (rc, out, err) = module.run_command(validate % src) 661 if rc != 0: 662 module.fail_json(msg="failed to validate", exit_status=rc, stdout=out, stderr=err) 663 b_mysrc = b_src 664 if remote_src and os.path.isfile(b_src): 665 _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) 666 667 shutil.copyfile(b_src, b_mysrc) 668 try: 669 shutil.copystat(b_src, b_mysrc) 670 except OSError as err: 671 if err.errno == errno.ENOSYS and mode == "preserve": 672 module.warn("Unable to copy stats {0}".format(to_native(b_src))) 673 else: 674 raise 675 676 # might be needed below 677 if PY3 and hasattr(os, 'listxattr'): 678 try: 679 src_has_acls = 'system.posix_acl_access' in os.listxattr(src) 680 except Exception as e: 681 # assume unwanted ACLs by default 682 src_has_acls = True 683 684 module.atomic_move(b_mysrc, dest, unsafe_writes=module.params['unsafe_writes']) 685 686 if PY3 and hasattr(os, 'listxattr') and platform.system() == 'Linux' and not remote_src: 687 # atomic_move used above to copy src into dest might, in some cases, 688 # use shutil.copy2 which in turn uses shutil.copystat. 689 # Since Python 3.3, shutil.copystat copies file extended attributes: 690 # https://docs.python.org/3/library/shutil.html#shutil.copystat 691 # os.listxattr (along with others) was added to handle the operation. 692 693 # This means that on Python 3 we are copying the extended attributes which includes 694 # the ACLs on some systems - further limited to Linux as the documentation above claims 695 # that the extended attributes are copied only on Linux. Also, os.listxattr is only 696 # available on Linux. 697 698 # If not remote_src, then the file was copied from the controller. In that 699 # case, any filesystem ACLs are artifacts of the copy rather than preservation 700 # of existing attributes. Get rid of them: 701 702 if src_has_acls: 703 # FIXME If dest has any default ACLs, there are not applied to src now because 704 # they were overridden by copystat. Should/can we do anything about this? 705 # 'system.posix_acl_default' in os.listxattr(os.path.dirname(b_dest)) 706 707 try: 708 clear_facls(dest) 709 except ValueError as e: 710 if 'setfacl' in to_native(e): 711 # No setfacl so we're okay. The controller couldn't have set a facl 712 # without the setfacl command 713 pass 714 else: 715 raise 716 except RuntimeError as e: 717 # setfacl failed. 718 if 'Operation not supported' in to_native(e): 719 # The file system does not support ACLs. 720 pass 721 else: 722 raise 723 724 except (IOError, OSError): 725 module.fail_json(msg="failed to copy: %s to %s" % (src, dest), traceback=traceback.format_exc()) 726 changed = True 727 else: 728 changed = False 729 730 # If neither have checksums, both src and dest are directories. 731 if checksum_src is None and checksum_dest is None: 732 if remote_src and os.path.isdir(module.params['src']): 733 b_src = to_bytes(module.params['src'], errors='surrogate_or_strict') 734 b_dest = to_bytes(module.params['dest'], errors='surrogate_or_strict') 735 736 if src.endswith(os.path.sep) and os.path.isdir(module.params['dest']): 737 diff_files_changed = copy_diff_files(b_src, b_dest, module) 738 left_only_changed = copy_left_only(b_src, b_dest, module) 739 common_dirs_changed = copy_common_dirs(b_src, b_dest, module) 740 owner_group_changed = chown_recursive(b_dest, module) 741 if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: 742 changed = True 743 744 if src.endswith(os.path.sep) and not os.path.exists(module.params['dest']): 745 b_basename = to_bytes(os.path.basename(src), errors='surrogate_or_strict') 746 b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') 747 b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') 748 if not module.check_mode: 749 shutil.copytree(b_src, b_dest, symlinks=not(local_follow)) 750 chown_recursive(dest, module) 751 changed = True 752 753 if not src.endswith(os.path.sep) and os.path.isdir(module.params['dest']): 754 b_basename = to_bytes(os.path.basename(src), errors='surrogate_or_strict') 755 b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') 756 b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') 757 if not module.check_mode and not os.path.exists(b_dest): 758 shutil.copytree(b_src, b_dest, symlinks=not(local_follow)) 759 changed = True 760 chown_recursive(dest, module) 761 if module.check_mode and not os.path.exists(b_dest): 762 changed = True 763 if os.path.exists(b_dest): 764 diff_files_changed = copy_diff_files(b_src, b_dest, module) 765 left_only_changed = copy_left_only(b_src, b_dest, module) 766 common_dirs_changed = copy_common_dirs(b_src, b_dest, module) 767 owner_group_changed = chown_recursive(b_dest, module) 768 if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: 769 changed = True 770 771 if not src.endswith(os.path.sep) and not os.path.exists(module.params['dest']): 772 b_basename = to_bytes(os.path.basename(module.params['src']), errors='surrogate_or_strict') 773 b_dest = to_bytes(os.path.join(b_dest, b_basename), errors='surrogate_or_strict') 774 if not module.check_mode and not os.path.exists(b_dest): 775 os.makedirs(b_dest) 776 b_src = to_bytes(os.path.join(module.params['src'], ""), errors='surrogate_or_strict') 777 diff_files_changed = copy_diff_files(b_src, b_dest, module) 778 left_only_changed = copy_left_only(b_src, b_dest, module) 779 common_dirs_changed = copy_common_dirs(b_src, b_dest, module) 780 owner_group_changed = chown_recursive(b_dest, module) 781 if diff_files_changed or left_only_changed or common_dirs_changed or owner_group_changed: 782 changed = True 783 if module.check_mode and not os.path.exists(b_dest): 784 changed = True 785 786 res_args = dict( 787 dest=dest, src=src, md5sum=md5sum_src, checksum=checksum_src, changed=changed 788 ) 789 if backup_file: 790 res_args['backup_file'] = backup_file 791 792 if not module.check_mode: 793 file_args = module.load_file_common_arguments(module.params, path=dest) 794 res_args['changed'] = module.set_fs_attributes_if_different(file_args, res_args['changed']) 795 796 module.exit_json(**res_args) 797 798 799if __name__ == '__main__': 800 main() 801