Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 42 additions & 30 deletions Src/IronPython.Modules/mmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,40 +68,65 @@ private static Exception WindowsError(int code) {
public static PythonType mmap {
get {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapDefault));
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapWindows));
}

return DynamicHelpers.GetPythonTypeFromType(typeof(MmapUnix));
}
}


[PythonType("mmap"), PythonHidden]
public class MmapUnix : MmapDefault {
public MmapUnix(CodeContext/*!*/ context, int fileno, long length, int flags = MAP_SHARED, int prot = PROT_WRITE | PROT_READ, int access = ACCESS_DEFAULT, long offset = 0)
: base(context, fileno, length, null, NormalizeAccess(flags, prot, access), offset) { }
: base(context, fileno, length, null, ToMmapFileAccess(flags, prot, access), offset) { }

private static int NormalizeAccess(int flags, int prot, int access) {
private static MemoryMappedFileAccess ToMmapFileAccess(int flags, int prot, int access) {
if (access == ACCESS_DEFAULT) {
if ((flags & (MAP_PRIVATE | MAP_SHARED)) == 0) {
throw PythonOps.OSError(PythonErrorNumber.EINVAL, "Invalid argument");
}
if ((prot & PROT_WRITE) != 0) {
return (flags & MAP_PRIVATE) != 0 ? ACCESS_COPY : ACCESS_WRITE;
}
if ((prot & PROT_READ) != 0) {
return ACCESS_READ;
}
throw PythonOps.NotImplementedError("this combination of flags and prot is not supported");
prot |= PROT_READ;
}
return (prot & (PROT_READ | PROT_WRITE | PROT_EXEC)) switch {
PROT_READ => MemoryMappedFileAccess.Read,
PROT_READ | PROT_WRITE => (flags & MAP_PRIVATE) == 0 ? MemoryMappedFileAccess.ReadWrite : MemoryMappedFileAccess.CopyOnWrite,
PROT_READ | PROT_EXEC => MemoryMappedFileAccess.ReadExecute,
PROT_READ | PROT_WRITE | PROT_EXEC when (flags & MAP_PRIVATE) == 0 => MemoryMappedFileAccess.ReadWriteExecute,
_ => throw PythonOps.NotImplementedError("this combination of prot is not supported"),
};
} else if (flags != MAP_SHARED || prot != (PROT_WRITE | PROT_READ)) {
throw PythonOps.ValueError("mmap can't specify both access and flags, prot.");
} else if (access != ACCESS_READ && access != ACCESS_WRITE && access != ACCESS_COPY) {
throw PythonOps.ValueError("mmap invalid access parameter");
} else {
return access switch {
ACCESS_READ => MemoryMappedFileAccess.Read,
ACCESS_WRITE => MemoryMappedFileAccess.ReadWrite,
ACCESS_COPY => MemoryMappedFileAccess.CopyOnWrite,
_ => throw PythonOps.ValueError("mmap invalid access parameter"),
};
}
return access;
}
}


[PythonType("mmap"), PythonHidden]
public class MmapWindows : MmapDefault {
public MmapWindows(CodeContext context, int fileno, long length, string tagname = null, int access = ACCESS_DEFAULT, long offset = 0)
: base(context, fileno, length, tagname, ToMmapFileAccess(access), offset) { }

private static MemoryMappedFileAccess ToMmapFileAccess(int access) {
return access switch {
ACCESS_READ => MemoryMappedFileAccess.Read,
// On Windows, default access is write-through
ACCESS_DEFAULT or ACCESS_WRITE => MemoryMappedFileAccess.ReadWrite,
ACCESS_COPY => MemoryMappedFileAccess.CopyOnWrite,
_ => throw PythonOps.ValueError("mmap invalid access parameter"),
};
}
}

[PythonHidden]
public class MmapDefault : IWeakReferenceable {
private MemoryMappedFile _file;
private MemoryMappedViewAccessor _view;
Expand All @@ -115,21 +140,8 @@ public class MmapDefault : IWeakReferenceable {
private volatile bool _isClosed;
private int _refCount = 1;

public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tagname = null, int access = ACCESS_DEFAULT, long offset = 0) {
switch (access) {
case ACCESS_READ:
_fileAccess = MemoryMappedFileAccess.Read;
break;
case ACCESS_DEFAULT: // On Windows, default access is write-through
case ACCESS_WRITE:
_fileAccess = MemoryMappedFileAccess.ReadWrite;
break;
case ACCESS_COPY:
_fileAccess = MemoryMappedFileAccess.CopyOnWrite;
break;
default:
throw PythonOps.ValueError("mmap invalid access parameter");
}
public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tagname, MemoryMappedFileAccess fileAccess, long offset) {
_fileAccess = fileAccess;

if (length < 0) {
throw PythonOps.OverflowError("memory mapped size must be positive");
Expand Down Expand Up @@ -175,7 +187,7 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tag
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_BLOCK, "Bad file descriptor");
}

if (_fileAccess == MemoryMappedFileAccess.ReadWrite && !_sourceStream.CanWrite) {
if (_fileAccess is MemoryMappedFileAccess.ReadWrite or MemoryMappedFileAccess.ReadWriteExecute && !_sourceStream.CanWrite) {
throw WindowsError(PythonExceptions._OSError.ERROR_ACCESS_DENIED);
}

Expand Down Expand Up @@ -579,7 +591,7 @@ public string readline() {

public void resize(long newsize) {
using (new MmapLocker(this)) {
if (_fileAccess != MemoryMappedFileAccess.ReadWrite) {
if (_fileAccess is not MemoryMappedFileAccess.ReadWrite and not MemoryMappedFileAccess.ReadWriteExecute) {
throw PythonOps.TypeError("mmap can't resize a readonly or copy-on-write memory map.");
}

Expand Down Expand Up @@ -802,7 +814,7 @@ private long Position {
}

private void EnsureWritable() {
if (_fileAccess == MemoryMappedFileAccess.Read) {
if (_fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute) {
throw PythonOps.TypeError("mmap can't modify a read-only memory map.");
}
}
Expand Down
2 changes: 1 addition & 1 deletion Src/IronPythonTest/Cases/CPythonCasesManifest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ IsolationLevel=PROCESS # Also weakref failures; https://github.com/IronLanguages
Ignore=true

[CPython.test_mmap]
RunCondition=NOT $(IS_POSIX) OR (NOT $(IS_MONO) AND '$(FRAMEWORK)' <> '.NETCoreApp,Version=v6.0')
RunCondition=NOT $(IS_MONO) AND (NOT $(IS_OSX) OR '$(FRAMEWORK)' <> '.NETCoreApp,Version=v6.0')
IsolationLevel=PROCESS

[CPython.test_module]
Expand Down
11 changes: 7 additions & 4 deletions Src/StdLib/Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import re
import itertools
import struct # IronPython: for platform architecture detection
import socket
import sys
import weakref
Expand Down Expand Up @@ -715,7 +716,6 @@ def test_weakref(self):
gc_collect()
self.assertIs(wr(), None)

@unittest.skipIf(sys.implementation.name == "ironpython", "TODO")
class LargeMmapTests(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -748,7 +748,8 @@ def test_large_offset(self):

def test_large_filesize(self):
with self._make_test_file(0x17FFFFFFF, b" ") as f:
if sys.maxsize < 0x180000000:
#if sys.maxsize < 0x180000000: # original CPython test
if struct.calcsize('P') * 8 == 32: # IronPython: better detection of 32-bit platform
# On 32 bit platforms the file is larger than sys.maxsize so
# mapping the whole file should fail -- Issue #16743
with self.assertRaises(OverflowError):
Expand All @@ -768,11 +769,13 @@ def _test_around_boundary(self, boundary):
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
self.assertEqual(m[start:end], tail)

@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems")
#@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems") # original CPython decorator
@unittest.skipUnless(struct.calcsize('P') * 8 > 32, "test cannot run on 32-bit systems") # IronPython: better detection of 32-bit platform
def test_around_2GB(self):
self._test_around_boundary(_2G)

@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems")
#@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems") # original CPython decorator
@unittest.skipUnless(struct.calcsize('P') * 8 > 32, "test cannot run on 32-bit systems") # IronPython: better detection of 32-bit platform
def test_around_4GB(self):
self._test_around_boundary(_4G)

Expand Down
Loading