diff --git a/Src/IronPython.Modules/mmap.cs b/Src/IronPython.Modules/mmap.cs index 602f8ac66..47de652d2 100644 --- a/Src/IronPython.Modules/mmap.cs +++ b/Src/IronPython.Modules/mmap.cs @@ -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; @@ -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"); @@ -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); } @@ -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."); } @@ -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."); } } diff --git a/Src/IronPythonTest/Cases/CPythonCasesManifest.ini b/Src/IronPythonTest/Cases/CPythonCasesManifest.ini index 4397ad82f..b3f7c5f00 100644 --- a/Src/IronPythonTest/Cases/CPythonCasesManifest.ini +++ b/Src/IronPythonTest/Cases/CPythonCasesManifest.ini @@ -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] diff --git a/Src/StdLib/Lib/test/test_mmap.py b/Src/StdLib/Lib/test/test_mmap.py index eff2f4295..7882be8ca 100644 --- a/Src/StdLib/Lib/test/test_mmap.py +++ b/Src/StdLib/Lib/test/test_mmap.py @@ -4,6 +4,7 @@ import os import re import itertools +import struct # IronPython: for platform architecture detection import socket import sys import weakref @@ -721,7 +722,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): @@ -754,7 +754,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): @@ -774,11 +775,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)