gh-88745: Add _winapi.CopyFile2 and update shutil.copy2 to use it (GH-105055)

This commit is contained in:
Steve Dower 2023-05-30 11:00:29 +01:00 committed by GitHub
parent d14eb3433c
commit cda1bd3c9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 210 additions and 1 deletions

View file

@ -919,6 +919,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_value));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(excepthook));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exception));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(existing_file_name));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exp));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(extend));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(extra_tokens));
@ -1071,6 +1072,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(namespaces));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(narg));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ndigits));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(new_file_name));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(new_limit));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(newline));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(newlines));
@ -1125,6 +1127,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(priority));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_handler));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_routine));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(proto));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(protocol));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ps1));

View file

@ -407,6 +407,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(exc_value)
STRUCT_FOR_ID(excepthook)
STRUCT_FOR_ID(exception)
STRUCT_FOR_ID(existing_file_name)
STRUCT_FOR_ID(exp)
STRUCT_FOR_ID(extend)
STRUCT_FOR_ID(extra_tokens)
@ -559,6 +560,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(namespaces)
STRUCT_FOR_ID(narg)
STRUCT_FOR_ID(ndigits)
STRUCT_FOR_ID(new_file_name)
STRUCT_FOR_ID(new_limit)
STRUCT_FOR_ID(newline)
STRUCT_FOR_ID(newlines)
@ -613,6 +615,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(priority)
STRUCT_FOR_ID(progress)
STRUCT_FOR_ID(progress_handler)
STRUCT_FOR_ID(progress_routine)
STRUCT_FOR_ID(proto)
STRUCT_FOR_ID(protocol)
STRUCT_FOR_ID(ps1)

View file

@ -913,6 +913,7 @@ extern "C" {
INIT_ID(exc_value), \
INIT_ID(excepthook), \
INIT_ID(exception), \
INIT_ID(existing_file_name), \
INIT_ID(exp), \
INIT_ID(extend), \
INIT_ID(extra_tokens), \
@ -1065,6 +1066,7 @@ extern "C" {
INIT_ID(namespaces), \
INIT_ID(narg), \
INIT_ID(ndigits), \
INIT_ID(new_file_name), \
INIT_ID(new_limit), \
INIT_ID(newline), \
INIT_ID(newlines), \
@ -1119,6 +1121,7 @@ extern "C" {
INIT_ID(priority), \
INIT_ID(progress), \
INIT_ID(progress_handler), \
INIT_ID(progress_routine), \
INIT_ID(proto), \
INIT_ID(protocol), \
INIT_ID(ps1), \

View file

@ -1062,6 +1062,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(exception);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(existing_file_name);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(exp);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@ -1518,6 +1521,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(ndigits);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(new_file_name);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(new_limit);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
@ -1680,6 +1686,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(progress_handler);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(progress_routine);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(proto);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);

View file

@ -42,6 +42,8 @@
if sys.platform == 'win32':
import _winapi
else:
_winapi = None
COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
# This should never be removed, see rationale in:
@ -435,6 +437,29 @@ def copy2(src, dst, *, follow_symlinks=True):
"""
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
if hasattr(_winapi, "CopyFile2"):
src_ = os.fsdecode(src)
dst_ = os.fsdecode(dst)
flags = _winapi.COPY_FILE_ALLOW_DECRYPTED_DESTINATION # for compat
if not follow_symlinks:
flags |= _winapi.COPY_FILE_COPY_SYMLINK
try:
_winapi.CopyFile2(src_, dst_, flags)
return dst
except OSError as exc:
if (exc.winerror == _winapi.ERROR_PRIVILEGE_NOT_HELD
and not follow_symlinks):
# Likely encountered a symlink we aren't allowed to create.
# Fall back on the old code
pass
elif exc.winerror == _winapi.ERROR_ACCESS_DENIED:
# Possibly encountered a hidden or readonly file we can't
# overwrite. Fall back on old code
pass
else:
raise
copyfile(src, dst, follow_symlinks=follow_symlinks)
copystat(src, dst, follow_symlinks=follow_symlinks)
return dst

View file

@ -0,0 +1,3 @@
Improve performance of :func:`shutil.copy2` by using the operating system's
``CopyFile2`` function. This may result in subtle changes to metadata copied
along with some files, bringing them in line with normal OS behavior.

View file

@ -1947,6 +1947,7 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle)
return result;
}
/*[clinic input]
_winapi._mimetypes_read_windows_registry
@ -2075,6 +2076,67 @@ _winapi_NeedCurrentDirectoryForExePath_impl(PyObject *module,
return result;
}
/*[clinic input]
_winapi.CopyFile2
existing_file_name: LPCWSTR
new_file_name: LPCWSTR
flags: DWORD
progress_routine: object = None
Copies a file from one name to a new name.
This is implemented using the CopyFile2 API, which preserves all stat
and metadata information apart from security attributes.
progress_routine is reserved for future use, but is currently not
implemented. Its value is ignored.
[clinic start generated code]*/
static PyObject *
_winapi_CopyFile2_impl(PyObject *module, LPCWSTR existing_file_name,
LPCWSTR new_file_name, DWORD flags,
PyObject *progress_routine)
/*[clinic end generated code: output=43d960d9df73d984 input=fb976b8d1492d130]*/
{
HRESULT hr;
COPYFILE2_EXTENDED_PARAMETERS params = { sizeof(COPYFILE2_EXTENDED_PARAMETERS) };
if (PySys_Audit("_winapi.CopyFile2", "uuI",
existing_file_name, new_file_name, flags) < 0) {
return NULL;
}
params.dwCopyFlags = flags;
/* For future implementation. We ignore the value for now so that
users only have to test for 'CopyFile2' existing and not whether
the additional parameter exists.
if (progress_routine != Py_None) {
params.pProgressRoutine = _winapi_CopyFile2ProgressRoutine;
params.pvCallbackContext = Py_NewRef(progress_routine);
}
*/
Py_BEGIN_ALLOW_THREADS;
hr = CopyFile2(existing_file_name, new_file_name, &params);
Py_END_ALLOW_THREADS;
/* For future implementation.
if (progress_routine != Py_None) {
Py_DECREF(progress_routine);
}
*/
if (FAILED(hr)) {
if ((hr & 0xFFFF0000) == 0x80070000) {
PyErr_SetFromWindowsErr(hr & 0xFFFF);
} else {
PyErr_SetFromWindowsErr(hr);
}
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef winapi_functions[] = {
_WINAPI_CLOSEHANDLE_METHODDEF
_WINAPI_CONNECTNAMEDPIPE_METHODDEF
@ -2110,6 +2172,7 @@ static PyMethodDef winapi_functions[] = {
_WINAPI_GETFILETYPE_METHODDEF
_WINAPI__MIMETYPES_READ_WINDOWS_REGISTRY_METHODDEF
_WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF
_WINAPI_COPYFILE2_METHODDEF
{NULL, NULL}
};
@ -2146,6 +2209,7 @@ static int winapi_exec(PyObject *m)
WINAPI_CONSTANT(F_DWORD, CREATE_NEW_PROCESS_GROUP);
WINAPI_CONSTANT(F_DWORD, DUPLICATE_SAME_ACCESS);
WINAPI_CONSTANT(F_DWORD, DUPLICATE_CLOSE_SOURCE);
WINAPI_CONSTANT(F_DWORD, ERROR_ACCESS_DENIED);
WINAPI_CONSTANT(F_DWORD, ERROR_ALREADY_EXISTS);
WINAPI_CONSTANT(F_DWORD, ERROR_BROKEN_PIPE);
WINAPI_CONSTANT(F_DWORD, ERROR_IO_PENDING);
@ -2159,6 +2223,7 @@ static int winapi_exec(PyObject *m)
WINAPI_CONSTANT(F_DWORD, ERROR_OPERATION_ABORTED);
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_BUSY);
WINAPI_CONSTANT(F_DWORD, ERROR_PIPE_CONNECTED);
WINAPI_CONSTANT(F_DWORD, ERROR_PRIVILEGE_NOT_HELD);
WINAPI_CONSTANT(F_DWORD, ERROR_SEM_TIMEOUT);
WINAPI_CONSTANT(F_DWORD, FILE_FLAG_FIRST_PIPE_INSTANCE);
WINAPI_CONSTANT(F_DWORD, FILE_FLAG_OVERLAPPED);
@ -2252,6 +2317,34 @@ static int winapi_exec(PyObject *m)
WINAPI_CONSTANT(F_DWORD, LCMAP_TRADITIONAL_CHINESE);
WINAPI_CONSTANT(F_DWORD, LCMAP_UPPERCASE);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_ALLOW_DECRYPTED_DESTINATION);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_COPY_SYMLINK);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_FAIL_IF_EXISTS);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_NO_BUFFERING);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_NO_OFFLOAD);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_OPEN_SOURCE_FOR_WRITE);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_RESTARTABLE);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_REQUEST_SECURITY_PRIVILEGES);
WINAPI_CONSTANT(F_DWORD, COPY_FILE_RESUME_FROM_PAUSE);
#ifndef COPY_FILE_REQUEST_COMPRESSED_TRAFFIC
// Only defined in newer WinSDKs
#define COPY_FILE_REQUEST_COMPRESSED_TRAFFIC 0x10000000
#endif
WINAPI_CONSTANT(F_DWORD, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_CHUNK_STARTED);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_CHUNK_FINISHED);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_STREAM_STARTED);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_STREAM_FINISHED);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_POLL_CONTINUE);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_ERROR);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_CONTINUE);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_CANCEL);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_STOP);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_QUIET);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_PROGRESS_PAUSE);
WINAPI_CONSTANT("i", NULL);
return 0;

View file

@ -1411,4 +1411,74 @@ exit:
return return_value;
}
/*[clinic end generated code: output=96ea65ece7912d0a input=a9049054013a1b77]*/
PyDoc_STRVAR(_winapi_CopyFile2__doc__,
"CopyFile2($module, /, existing_file_name, new_file_name, flags,\n"
" progress_routine=None)\n"
"--\n"
"\n"
"Copies a file from one name to a new name.\n"
"\n"
"This is implemented using the CopyFile2 API, which preserves all stat\n"
"and metadata information apart from security attributes.\n"
"\n"
"progress_routine is reserved for future use, but is currently not\n"
"implemented. Its value is ignored.");
#define _WINAPI_COPYFILE2_METHODDEF \
{"CopyFile2", _PyCFunction_CAST(_winapi_CopyFile2), METH_FASTCALL|METH_KEYWORDS, _winapi_CopyFile2__doc__},
static PyObject *
_winapi_CopyFile2_impl(PyObject *module, LPCWSTR existing_file_name,
LPCWSTR new_file_name, DWORD flags,
PyObject *progress_routine);
static PyObject *
_winapi_CopyFile2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
#define NUM_KEYWORDS 4
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
PyObject *ob_item[NUM_KEYWORDS];
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_item = { &_Py_ID(existing_file_name), &_Py_ID(new_file_name), &_Py_ID(flags), &_Py_ID(progress_routine), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
#else // !Py_BUILD_CORE
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
static const char * const _keywords[] = {"existing_file_name", "new_file_name", "flags", "progress_routine", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.format = "O&O&k|O:CopyFile2",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
LPCWSTR existing_file_name = NULL;
LPCWSTR new_file_name = NULL;
DWORD flags;
PyObject *progress_routine = Py_None;
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
_PyUnicode_WideCharString_Converter, &existing_file_name, _PyUnicode_WideCharString_Converter, &new_file_name, &flags, &progress_routine)) {
goto exit;
}
return_value = _winapi_CopyFile2_impl(module, existing_file_name, new_file_name, flags, progress_routine);
exit:
/* Cleanup for existing_file_name */
PyMem_Free((void *)existing_file_name);
/* Cleanup for new_file_name */
PyMem_Free((void *)new_file_name);
return return_value;
}
/*[clinic end generated code: output=be1343b3759e0c96 input=a9049054013a1b77]*/