Skip to content

Commit

Permalink
gh-88745: Add _winapi.CopyFile2 and update shutil.copy2 to use it (GH…
Browse files Browse the repository at this point in the history
  • Loading branch information
zooba authored May 30, 2023
1 parent d14eb34 commit cda1bd3
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
93 changes: 93 additions & 0 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,7 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle)
return result;
}


/*[clinic input]
_winapi._mimetypes_read_windows_registry
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
};

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
72 changes: 71 additions & 1 deletion Modules/clinic/_winapi.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cda1bd3

Please sign in to comment.