Skip to content

Commit cda1bd3

Browse files
authored
gh-88745: Add _winapi.CopyFile2 and update shutil.copy2 to use it (GH-105055)
1 parent d14eb34 commit cda1bd3

8 files changed

+210
-1
lines changed

Include/internal/pycore_global_objects_fini_generated.h

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

+3
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ struct _Py_global_strings {
407407
STRUCT_FOR_ID(exc_value)
408408
STRUCT_FOR_ID(excepthook)
409409
STRUCT_FOR_ID(exception)
410+
STRUCT_FOR_ID(existing_file_name)
410411
STRUCT_FOR_ID(exp)
411412
STRUCT_FOR_ID(extend)
412413
STRUCT_FOR_ID(extra_tokens)
@@ -559,6 +560,7 @@ struct _Py_global_strings {
559560
STRUCT_FOR_ID(namespaces)
560561
STRUCT_FOR_ID(narg)
561562
STRUCT_FOR_ID(ndigits)
563+
STRUCT_FOR_ID(new_file_name)
562564
STRUCT_FOR_ID(new_limit)
563565
STRUCT_FOR_ID(newline)
564566
STRUCT_FOR_ID(newlines)
@@ -613,6 +615,7 @@ struct _Py_global_strings {
613615
STRUCT_FOR_ID(priority)
614616
STRUCT_FOR_ID(progress)
615617
STRUCT_FOR_ID(progress_handler)
618+
STRUCT_FOR_ID(progress_routine)
616619
STRUCT_FOR_ID(proto)
617620
STRUCT_FOR_ID(protocol)
618621
STRUCT_FOR_ID(ps1)

Include/internal/pycore_runtime_init_generated.h

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/shutil.py

+25
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
if sys.platform == 'win32':
4444
import _winapi
45+
else:
46+
_winapi = None
4547

4648
COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
4749
# This should never be removed, see rationale in:
@@ -435,6 +437,29 @@ def copy2(src, dst, *, follow_symlinks=True):
435437
"""
436438
if os.path.isdir(dst):
437439
dst = os.path.join(dst, os.path.basename(src))
440+
441+
if hasattr(_winapi, "CopyFile2"):
442+
src_ = os.fsdecode(src)
443+
dst_ = os.fsdecode(dst)
444+
flags = _winapi.COPY_FILE_ALLOW_DECRYPTED_DESTINATION # for compat
445+
if not follow_symlinks:
446+
flags |= _winapi.COPY_FILE_COPY_SYMLINK
447+
try:
448+
_winapi.CopyFile2(src_, dst_, flags)
449+
return dst
450+
except OSError as exc:
451+
if (exc.winerror == _winapi.ERROR_PRIVILEGE_NOT_HELD
452+
and not follow_symlinks):
453+
# Likely encountered a symlink we aren't allowed to create.
454+
# Fall back on the old code
455+
pass
456+
elif exc.winerror == _winapi.ERROR_ACCESS_DENIED:
457+
# Possibly encountered a hidden or readonly file we can't
458+
# overwrite. Fall back on old code
459+
pass
460+
else:
461+
raise
462+
438463
copyfile(src, dst, follow_symlinks=follow_symlinks)
439464
copystat(src, dst, follow_symlinks=follow_symlinks)
440465
return dst
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve performance of :func:`shutil.copy2` by using the operating system's
2+
``CopyFile2`` function. This may result in subtle changes to metadata copied
3+
along with some files, bringing them in line with normal OS behavior.

Modules/_winapi.c

+93
Original file line numberDiff line numberDiff line change
@@ -1947,6 +1947,7 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle)
19471947
return result;
19481948
}
19491949

1950+
19501951
/*[clinic input]
19511952
_winapi._mimetypes_read_windows_registry
19521953
@@ -2075,6 +2076,67 @@ _winapi_NeedCurrentDirectoryForExePath_impl(PyObject *module,
20752076
return result;
20762077
}
20772078

2079+
2080+
/*[clinic input]
2081+
_winapi.CopyFile2
2082+
2083+
existing_file_name: LPCWSTR
2084+
new_file_name: LPCWSTR
2085+
flags: DWORD
2086+
progress_routine: object = None
2087+
2088+
Copies a file from one name to a new name.
2089+
2090+
This is implemented using the CopyFile2 API, which preserves all stat
2091+
and metadata information apart from security attributes.
2092+
2093+
progress_routine is reserved for future use, but is currently not
2094+
implemented. Its value is ignored.
2095+
[clinic start generated code]*/
2096+
2097+
static PyObject *
2098+
_winapi_CopyFile2_impl(PyObject *module, LPCWSTR existing_file_name,
2099+
LPCWSTR new_file_name, DWORD flags,
2100+
PyObject *progress_routine)
2101+
/*[clinic end generated code: output=43d960d9df73d984 input=fb976b8d1492d130]*/
2102+
{
2103+
HRESULT hr;
2104+
COPYFILE2_EXTENDED_PARAMETERS params = { sizeof(COPYFILE2_EXTENDED_PARAMETERS) };
2105+
2106+
if (PySys_Audit("_winapi.CopyFile2", "uuI",
2107+
existing_file_name, new_file_name, flags) < 0) {
2108+
return NULL;
2109+
}
2110+
2111+
params.dwCopyFlags = flags;
2112+
/* For future implementation. We ignore the value for now so that
2113+
users only have to test for 'CopyFile2' existing and not whether
2114+
the additional parameter exists.
2115+
if (progress_routine != Py_None) {
2116+
params.pProgressRoutine = _winapi_CopyFile2ProgressRoutine;
2117+
params.pvCallbackContext = Py_NewRef(progress_routine);
2118+
}
2119+
*/
2120+
Py_BEGIN_ALLOW_THREADS;
2121+
hr = CopyFile2(existing_file_name, new_file_name, &params);
2122+
Py_END_ALLOW_THREADS;
2123+
/* For future implementation.
2124+
if (progress_routine != Py_None) {
2125+
Py_DECREF(progress_routine);
2126+
}
2127+
*/
2128+
if (FAILED(hr)) {
2129+
if ((hr & 0xFFFF0000) == 0x80070000) {
2130+
PyErr_SetFromWindowsErr(hr & 0xFFFF);
2131+
} else {
2132+
PyErr_SetFromWindowsErr(hr);
2133+
}
2134+
return NULL;
2135+
}
2136+
Py_RETURN_NONE;
2137+
}
2138+
2139+
20782140
static PyMethodDef winapi_functions[] = {
20792141
_WINAPI_CLOSEHANDLE_METHODDEF
20802142
_WINAPI_CONNECTNAMEDPIPE_METHODDEF
@@ -2110,6 +2172,7 @@ static PyMethodDef winapi_functions[] = {
21102172
_WINAPI_GETFILETYPE_METHODDEF
21112173
_WINAPI__MIMETYPES_READ_WINDOWS_REGISTRY_METHODDEF
21122174
_WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF
2175+
_WINAPI_COPYFILE2_METHODDEF
21132176
{NULL, NULL}
21142177
};
21152178

@@ -2146,6 +2209,7 @@ static int winapi_exec(PyObject *m)
21462209
WINAPI_CONSTANT(F_DWORD, CREATE_NEW_PROCESS_GROUP);
21472210
WINAPI_CONSTANT(F_DWORD, DUPLICATE_SAME_ACCESS);
21482211
WINAPI_CONSTANT(F_DWORD, DUPLICATE_CLOSE_SOURCE);
2212+
WINAPI_CONSTANT(F_DWORD, ERROR_ACCESS_DENIED);
21492213
WINAPI_CONSTANT(F_DWORD, ERROR_ALREADY_EXISTS);
21502214
WINAPI_CONSTANT(F_DWORD, ERROR_BROKEN_PIPE);
21512215
WINAPI_CONSTANT(F_DWORD, ERROR_IO_PENDING);
@@ -2159,6 +2223,7 @@ static int winapi_exec(PyObject *m)
21592223
WINAPI_CONSTANT(F_DWORD, ERROR_OPERATION_ABORTED);
21602224
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_BUSY);
21612225
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_CONNECTED);
2226+
WINAPI_CONSTANT(F_DWORD, ERROR_PRIVILEGE_NOT_HELD);
21622227
WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT);
21632228
WINAPI_CONSTANT(F_DWORD, FILE_FLAG_FIRST_PIPE_INSTANCE);
21642229
WINAPI_CONSTANT(F_DWORD, FILE_FLAG_OVERLAPPED);
@@ -2252,6 +2317,34 @@ static int winapi_exec(PyObject *m)
22522317
WINAPI_CONSTANT(F_DWORD, LCMAP_TRADITIONAL_CHINESE);
22532318
WINAPI_CONSTANT(F_DWORD, LCMAP_UPPERCASE);
22542319

2320+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_ALLOW_DECRYPTED_DESTINATION);
2321+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_COPY_SYMLINK);
2322+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_FAIL_IF_EXISTS);
2323+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_NO_BUFFERING);
2324+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_NO_OFFLOAD);
2325+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_OPEN_SOURCE_FOR_WRITE);
2326+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_RESTARTABLE);
2327+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_REQUEST_SECURITY_PRIVILEGES);
2328+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_RESUME_FROM_PAUSE);
2329+
#ifndef COPY_FILE_REQUEST_COMPRESSED_TRAFFIC
2330+
// Only defined in newer WinSDKs
2331+
#define COPY_FILE_REQUEST_COMPRESSED_TRAFFIC 0x10000000
2332+
#endif
2333+
WINAPI_CONSTANT(F_DWORD, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC);
2334+
2335+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_CHUNK_STARTED);
2336+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_CHUNK_FINISHED);
2337+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_STREAM_STARTED);
2338+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_STREAM_FINISHED);
2339+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_POLL_CONTINUE);
2340+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_ERROR);
2341+
2342+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_CONTINUE);
2343+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_CANCEL);
2344+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_STOP);
2345+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_QUIET);
2346+
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_PAUSE);
2347+
22552348
WINAPI_CONSTANT("i", NULL);
22562349

22572350
return 0;

Modules/clinic/_winapi.c.h

+71-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)