| 176 | |
| 177 | |
| 178 | def safe_copy(source, dest, overwrite=False): |
| 179 | # type: (Text, Text, bool) -> None |
| 180 | def do_copy(): |
| 181 | # type: () -> None |
| 182 | temp_dest = dest + uuid4().hex |
| 183 | shutil.copy(source, temp_dest) |
| 184 | safe_rename(temp_dest, dest) |
| 185 | |
| 186 | # If the platform supports hard-linking, use that and fall back to copying. |
| 187 | # Windows does not support hard-linking. |
| 188 | if hasattr(os, "link"): |
| 189 | try: |
| 190 | safe_link(source, dest) |
| 191 | except OSError as e: |
| 192 | if e.errno == errno.EEXIST: |
| 193 | # File already exists. If overwrite=True, write otherwise skip. |
| 194 | if overwrite: |
| 195 | do_copy() |
| 196 | elif e.errno in (errno.EPERM, errno.EXDEV): |
| 197 | # For a hard link across devices issue, fall back on copying. |
| 198 | # |
| 199 | # For a permission issue, the cause could be one of: |
| 200 | # 1. We can't read source. |
| 201 | # 2. We can't write dest. |
| 202 | # 3. We don't own source but can read it. |
| 203 | # Although we can't do anything about cases 1 and 2, case 3 is due to |
| 204 | # `protected_hardlinks` (see: https://www.kernel.org/doc/Documentation/sysctl/fs.txt) and |
| 205 | # we can fall back to copying in that case. |
| 206 | # |
| 207 | # See also https://github.com/pex-tool/pex/issues/850 where this was discovered. |
| 208 | do_copy() |
| 209 | else: |
| 210 | raise OSError( |
| 211 | e.errno, |
| 212 | "Failed to link {src} -> {dst}: {strerror}\n" |
| 213 | "{process_diagnostic}\n" |
| 214 | "{src_diagnostic}\n" |
| 215 | "{dst_diagnostic}".format( |
| 216 | src=source, |
| 217 | dst=dest, |
| 218 | strerror=e.strerror, |
| 219 | process_diagnostic=_process_diagnostic(), |
| 220 | src_diagnostic=_path_diagnostic(source), |
| 221 | dst_diagnostic=_path_diagnostic(os.path.dirname(dest)), |
| 222 | ), |
| 223 | ) |
| 224 | elif os.path.exists(dest): |
| 225 | if overwrite: |
| 226 | do_copy() |
| 227 | else: |
| 228 | do_copy() |
| 229 | |
| 230 | |
| 231 | # See http://stackoverflow.com/questions/2572172/referencing-other-modules-in-atexit |