mirror of
https://github.com/python/cpython
synced 2024-09-16 03:39:58 +00:00
gh-88745: Add _winapi.CopyFile2 and update shutil.copy2 to use it (GH-105055)
This commit is contained in:
parent
d14eb3433c
commit
cda1bd3c9d
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
3
Include/internal/pycore_runtime_init_generated.h
generated
3
Include/internal/pycore_runtime_init_generated.h
generated
|
@ -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), \
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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, ¶ms);
|
||||
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;
|
||||
|
|
72
Modules/clinic/_winapi.c.h
generated
72
Modules/clinic/_winapi.c.h
generated
|
@ -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]*/
|
||||
|
|
Loading…
Reference in a new issue