summaryrefslogtreecommitdiffstats
path: root/Modules/mmapmodule.c
diff options
context:
space:
mode:
authorTim Golden <mail@timgolden.me.uk>2021-10-26 21:56:43 (GMT)
committerGitHub <noreply@github.com>2021-10-26 21:56:43 (GMT)
commitaea5ecc458084e01534ea6a11f4181f369869082 (patch)
tree165b9e684302c6ce45884969a7cb1019a658f623 /Modules/mmapmodule.c
parentb5ee79494b2e0d484b7cf59f6746010e22567702 (diff)
downloadcpython-aea5ecc458084e01534ea6a11f4181f369869082.zip
cpython-aea5ecc458084e01534ea6a11f4181f369869082.tar.gz
cpython-aea5ecc458084e01534ea6a11f4181f369869082.tar.bz2
bpo-40915: Fix mmap resize bugs on Windows (GH-29213)
(original patch by eryksun) Correctly hand various failure modes when resizing an mmap on Windows: * Resizing a pagefile-backed mmap now creates a new mmap and copies data * Attempting to resize when another mapping is held on the same file raises an OSError * Attempting to resize a nametagged mmap raises an OSError if another mapping is held with the same nametag
Diffstat (limited to 'Modules/mmapmodule.c')
-rw-r--r--Modules/mmapmodule.c129
1 files changed, 95 insertions, 34 deletions
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index 6397b0d..dfa10f6 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -32,6 +32,7 @@
#ifdef MS_WINDOWS
#include <windows.h>
+#include <winternl.h>
static int
my_getpagesize(void)
{
@@ -376,14 +377,15 @@ is_resizeable(mmap_object *self)
{
if (self->exports > 0) {
PyErr_SetString(PyExc_BufferError,
- "mmap can't resize with extant buffers exported.");
+ "mmap can't resize with extant buffers exported.");
return 0;
}
if ((self->access == ACCESS_WRITE) || (self->access == ACCESS_DEFAULT))
return 1;
PyErr_Format(PyExc_TypeError,
- "mmap can't resize a readonly or copy-on-write memory map.");
+ "mmap can't resize a readonly or copy-on-write memory map.");
return 0;
+
}
@@ -503,51 +505,110 @@ mmap_resize_method(mmap_object *self,
}
{
+ /*
+ To resize an mmap on Windows:
+
+ - Close the existing mapping
+ - If the mapping is backed to a named file:
+ unmap the view, clear the data, and resize the file
+ If the file can't be resized (eg because it has other mapped references
+ to it) then let the mapping be recreated at the original size and set
+ an error code so an exception will be raised.
+ - Create a new mapping of the relevant size to the same file
+ - Map a new view of the resized file
+ - If the mapping is backed by the pagefile:
+ copy any previous data into the new mapped area
+ unmap the original view which will release the memory
+ */
#ifdef MS_WINDOWS
- DWORD dwErrCode = 0;
- DWORD off_hi, off_lo, newSizeLow, newSizeHigh;
- /* First, unmap the file view */
- UnmapViewOfFile(self->data);
- self->data = NULL;
- /* Close the mapping object */
+ DWORD error = 0, file_resize_error = 0;
+ char* old_data = self->data;
+ LARGE_INTEGER offset, max_size;
+ offset.QuadPart = self->offset;
+ max_size.QuadPart = self->offset + new_size;
+ /* close the file mapping */
CloseHandle(self->map_handle);
- self->map_handle = NULL;
- /* Move to the desired EOF position */
- newSizeHigh = (DWORD)((self->offset + new_size) >> 32);
- newSizeLow = (DWORD)((self->offset + new_size) & 0xFFFFFFFF);
- off_hi = (DWORD)(self->offset >> 32);
- off_lo = (DWORD)(self->offset & 0xFFFFFFFF);
- SetFilePointer(self->file_handle,
- newSizeLow, &newSizeHigh, FILE_BEGIN);
- /* Change the size of the file */
- SetEndOfFile(self->file_handle);
- /* Create another mapping object and remap the file view */
+ /* if the file mapping still exists, it cannot be resized. */
+ if (self->tagname) {
+ self->map_handle = OpenFileMapping(FILE_MAP_WRITE, FALSE,
+ self->tagname);
+ if (self->map_handle) {
+ PyErr_SetFromWindowsErr(ERROR_USER_MAPPED_FILE);
+ return NULL;
+ }
+ } else {
+ self->map_handle = NULL;
+ }
+
+ /* if it's not the paging file, unmap the view and resize the file */
+ if (self->file_handle != INVALID_HANDLE_VALUE) {
+ if (!UnmapViewOfFile(self->data)) {
+ return PyErr_SetFromWindowsErr(GetLastError());
+ };
+ self->data = NULL;
+ /* resize the file */
+ if (!SetFilePointerEx(self->file_handle, max_size, NULL,
+ FILE_BEGIN) ||
+ !SetEndOfFile(self->file_handle)) {
+ /* resizing failed. try to remap the file */
+ file_resize_error = GetLastError();
+ new_size = max_size.QuadPart = self->size;
+ }
+ }
+
+ /* create a new file mapping and map a new view */
+ /* FIXME: call CreateFileMappingW with wchar_t tagname */
self->map_handle = CreateFileMapping(
self->file_handle,
NULL,
PAGE_READWRITE,
- 0,
- 0,
+ max_size.HighPart,
+ max_size.LowPart,
self->tagname);
- if (self->map_handle != NULL) {
- self->data = (char *) MapViewOfFile(self->map_handle,
- FILE_MAP_WRITE,
- off_hi,
- off_lo,
- new_size);
+
+ error = GetLastError();
+ if (error == ERROR_ALREADY_EXISTS) {
+ CloseHandle(self->map_handle);
+ self->map_handle = NULL;
+ }
+ else if (self->map_handle != NULL) {
+ self->data = MapViewOfFile(self->map_handle,
+ FILE_MAP_WRITE,
+ offset.HighPart,
+ offset.LowPart,
+ new_size);
if (self->data != NULL) {
+ /* copy the old view if using the paging file */
+ if (self->file_handle == INVALID_HANDLE_VALUE) {
+ memcpy(self->data, old_data,
+ self->size < new_size ? self->size : new_size);
+ if (!UnmapViewOfFile(old_data)) {
+ error = GetLastError();
+ }
+ }
self->size = new_size;
- Py_RETURN_NONE;
- } else {
- dwErrCode = GetLastError();
+ }
+ else {
+ error = GetLastError();
CloseHandle(self->map_handle);
self->map_handle = NULL;
}
- } else {
- dwErrCode = GetLastError();
}
- PyErr_SetFromWindowsErr(dwErrCode);
- return NULL;
+
+ if (error) {
+ return PyErr_SetFromWindowsErr(error);
+ return NULL;
+ }
+ /* It's possible for a resize to fail, typically because another mapping
+ is still held against the same underlying file. Even if nothing has
+ failed -- ie we're still returning a valid file mapping -- raise the
+ error as an exception as the resize won't have happened
+ */
+ if (file_resize_error) {
+ PyErr_SetFromWindowsErr(file_resize_error);
+ return NULL;
+ }
+ Py_RETURN_NONE;
#endif /* MS_WINDOWS */
#ifdef UNIX