diff --git a/Src/IronPython.Modules/mmap.cs b/Src/IronPython.Modules/mmap.cs index 942f35a7d..b6cbb871e 100644 --- a/Src/IronPython.Modules/mmap.cs +++ b/Src/IronPython.Modules/mmap.cs @@ -812,6 +812,10 @@ public void resize(long newsize) { if (_handle.IsInvalid) { throw PythonNT.GetOsError(PythonErrno.EBADF); } + if (_view.Capacity == newsize) { + // resizing to the same size + return; + } if (newsize == 0) { // resizing to an empty mapped region is not allowed throw PythonNT.GetOsError(PythonErrno.EINVAL); @@ -844,14 +848,6 @@ public void resize(long newsize) { throw WindowsError(PythonExceptions._OSError.ERROR_INVALID_PARAMETER); } - if (newsize == 0) { - // resizing to an empty mapped region is not allowed - throw WindowsError(_offset == 0 - ? PythonExceptions._OSError.ERROR_ACCESS_DENIED - : PythonExceptions._OSError.ERROR_FILE_INVALID - ); - } - if (_view.Capacity == newsize) { // resizing to the same size return; @@ -860,6 +856,14 @@ public void resize(long newsize) { long capacity = checked(_offset + newsize); try { + if (newsize == 0) { + // resizing to an empty mapped region is not allowed + throw WindowsError(_offset != 0 && RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? PythonExceptions._OSError.ERROR_ACCESS_DENIED + : PythonExceptions._OSError.ERROR_FILE_INVALID + ); + } + _view.Flush(); _view.Dispose(); _file.Dispose(); @@ -1143,13 +1147,13 @@ private void EnsureOpen() { } } - private struct MmapLocker : IDisposable { + private readonly struct MmapLocker : IDisposable { private readonly MmapDefault _mmap; public MmapLocker(MmapDefault mmap) { _mmap = mmap; - Interlocked.Increment(ref _mmap._refCount); _mmap.EnsureOpen(); + Interlocked.Increment(ref _mmap._refCount); } #region IDisposable Members diff --git a/Tests/modules/io_related/test_mmap.py b/Tests/modules/io_related/test_mmap.py new file mode 100644 index 000000000..249c7a955 --- /dev/null +++ b/Tests/modules/io_related/test_mmap.py @@ -0,0 +1,105 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the Apache 2.0 License. +# See the LICENSE file in the project root for more information. + +''' +Tests the _mmap standard module. +''' + +import sys +import os +import errno +import mmap + +from iptest import IronPythonTestCase, is_cli, is_posix, is_windows, run_test + +class MmapTest(IronPythonTestCase): + + def setUp(self): + super(MmapTest, self).setUp() + + self.temp_file = os.path.join(self.temporary_dir, "temp_mmap_%d.dat" % os.getpid()) + + def tearDown(self): + self.delete_files(self.temp_file) + return super().tearDown() + + + def test_constants(self): + self.assertTrue(hasattr(mmap, "PAGESIZE")) + self.assertTrue(hasattr(mmap, "ALLOCATIONGRANULARITY")) + + self.assertEqual(mmap.ACCESS_READ, 1) + self.assertEqual(mmap.ACCESS_WRITE, 2) + self.assertEqual(mmap.ACCESS_COPY, 3) + if sys.version_info >= (3, 7) or is_cli: + self.assertEqual(mmap.ACCESS_DEFAULT, 0) + self.assertFalse(hasattr(mmap, "ACCESS_NONE")) + + if is_posix: + self.assertEqual(mmap.MAP_SHARED, 1) + self.assertEqual(mmap.MAP_PRIVATE, 2) + self.assertEqual(mmap.PROT_READ, 1) + self.assertEqual(mmap.PROT_WRITE, 2) + self.assertEqual(mmap.PROT_EXEC, 4) + + + def test_resize_errors(self): + with open(self.temp_file, "wb+") as f: + f.write(b"x" * mmap.ALLOCATIONGRANULARITY * 2) + + with open(self.temp_file, "rb+") as f: + m = mmap.mmap(f.fileno(), 0, offset=0) + with self.assertRaises(OSError) as cm: + m.resize(0) + + self.assertEqual(cm.exception.errno, errno.EINVAL) # 22 + if is_windows: + self.assertEqual(cm.exception.winerror, 1006) # ERROR_FILE_INVALID + self.assertEqual(cm.exception.strerror, "The volume for a file has been externally altered so that the opened file is no longer valid") + else: + self.assertEqual(cm.exception.strerror, "Invalid argument") + + self.assertTrue(m.closed) + + + def test_resize_errors_negative(self): + with open(self.temp_file, "wb+") as f: + f.write(b"x" * mmap.ALLOCATIONGRANULARITY * 2) + + with open(self.temp_file, "rb+") as f: + m = mmap.mmap(f.fileno(), 0, offset=0) + if is_cli or sys.version_info >= (3, 5): + self.assertRaises(ValueError, m.resize, -1) + self.assertFalse(m.closed) + else: + self.assertRaises(OSError, m.resize, -1) + self.assertTrue(m.closed) + + m.close() + + + def test_resize_errors_offset(self): + with open(self.temp_file, "wb+") as f: + f.write(b"x" * mmap.ALLOCATIONGRANULARITY * 2) + + with open(self.temp_file, "rb+") as f: + m = mmap.mmap(f.fileno(), 0, offset=mmap.ALLOCATIONGRANULARITY) + + if is_windows: + with self.assertRaises(PermissionError) as cm: + m.resize(0) + self.assertEqual(cm.exception.errno, errno.EACCES) # 13 + self.assertEqual(cm.exception.winerror, 5) # ERROR_ACCESS_DENIED + self.assertEqual(cm.exception.strerror, "Access is denied") + else: + with self.assertRaises(OSError) as cm: + m.resize(0) + self.assertEqual(cm.exception.errno, errno.EINVAL) # 22 + self.assertEqual(cm.exception.strerror, "Invalid argument") + + self.assertTrue(m.closed) + + +run_test(__name__) +