mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Align mmap operations and capability reporting with Python 3.15 (#9141)
Separate raw descriptor inspection from owned tracking, update flush and naming behavior, and expose resize only on implemented backends. Migrate canonical mmap tests using update_lib while preserving existing RustPython guards. Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
1 parent
16e333bded
commit
0384ddcd70
3 files changed
+479
-234
No files matched your search
Vendored
+207
-117
@@ -1,3 +1,4 @@
|
||||
from test import support
|
||||
from test.support import (
|
||||
requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple,
|
||||
in_systemd_nspawn_sync_suppressed,
|
||||
@@ -5,8 +6,8 @@ from test.support import (
|
||||
from test.support.import_helper import import_module
|
||||
from test.support.os_helper import TESTFN, unlink
|
||||
from test.support.script_helper import assert_python_ok
|
||||
import unittest
|
||||
import errno
|
||||
import unittest
|
||||
import os
|
||||
import re
|
||||
import itertools
|
||||
@@ -58,6 +59,7 @@ class MmapTests(unittest.TestCase):
|
||||
f.write(b'\0'* (PAGESIZE-3) )
|
||||
f.flush()
|
||||
m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
|
||||
self.addCleanup(m.close)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
@@ -115,31 +117,28 @@ class MmapTests(unittest.TestCase):
|
||||
# Try to seek to negative position...
|
||||
self.assertRaises(ValueError, m.seek, -len(m)-1, 2)
|
||||
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_resize(self):
|
||||
# Create a file to be mmap'ed.
|
||||
with open(TESTFN, 'bw+') as f:
|
||||
# Write 2 pages worth of data to the file
|
||||
f.write(b'\0'* 2 * PAGESIZE)
|
||||
f.flush()
|
||||
m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
|
||||
self.addCleanup(m.close)
|
||||
|
||||
# Try resizing map
|
||||
try:
|
||||
m.resize(512)
|
||||
except SystemError:
|
||||
# resize() not supported
|
||||
# No messages are printed, since the output of this test suite
|
||||
# would then be different across platforms.
|
||||
pass
|
||||
else:
|
||||
# resize() is supported
|
||||
self.assertEqual(len(m), 512)
|
||||
# Check that we can no longer seek beyond the new size.
|
||||
self.assertRaises(ValueError, m.seek, 513, 0)
|
||||
m.resize(512)
|
||||
self.assertEqual(len(m), 512)
|
||||
# Check that we can no longer seek beyond the new size.
|
||||
self.assertRaises(ValueError, m.seek, 513, 0)
|
||||
|
||||
# Check that the underlying file is truncated too
|
||||
# (bug #728515)
|
||||
f = open(TESTFN, 'rb')
|
||||
try:
|
||||
f.seek(0, 2)
|
||||
self.assertEqual(f.tell(), 512)
|
||||
finally:
|
||||
f.close()
|
||||
self.assertEqual(m.size(), 512)
|
||||
|
||||
m.close()
|
||||
# Check that the underlying file is truncated too
|
||||
# (bug #728515)
|
||||
with open(TESTFN, 'rb') as f:
|
||||
f.seek(0, 2)
|
||||
self.assertEqual(f.tell(), 512)
|
||||
self.assertEqual(m.size(), 512)
|
||||
|
||||
def test_access_parameter(self):
|
||||
# Test for "access" keyword parameter
|
||||
@@ -184,15 +183,10 @@ class MmapTests(unittest.TestCase):
|
||||
else:
|
||||
self.fail("Able to write to readonly memory map")
|
||||
|
||||
# Ensuring that readonly mmap can't be resized
|
||||
try:
|
||||
m.resize(2*mapsize)
|
||||
except SystemError: # resize is not universally supported
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.fail("Able to resize readonly memory map")
|
||||
if hasattr(m, 'resize'):
|
||||
# Ensuring that readonly mmap can't be resized
|
||||
with self.assertRaises(TypeError):
|
||||
m.resize(2 * mapsize)
|
||||
with open(TESTFN, "rb") as fp:
|
||||
self.assertEqual(fp.read(), b'a'*mapsize,
|
||||
"Readonly memory map data file was modified")
|
||||
@@ -243,8 +237,9 @@ class MmapTests(unittest.TestCase):
|
||||
with open(TESTFN, "rb") as fp:
|
||||
self.assertEqual(fp.read(), b'c'*mapsize,
|
||||
"Copy-on-write test data file should not be modified.")
|
||||
# Ensuring copy-on-write maps cannot be resized
|
||||
self.assertRaises(TypeError, m.resize, 2*mapsize)
|
||||
if hasattr(m, 'resize'):
|
||||
# Ensuring copy-on-write maps cannot be resized
|
||||
self.assertRaises(TypeError, m.resize, 2 * mapsize)
|
||||
m.close()
|
||||
|
||||
# Ensuring invalid access parameter raises exception
|
||||
@@ -271,62 +266,57 @@ class MmapTests(unittest.TestCase):
|
||||
self.assertRaises(TypeError, m.write_byte, 0)
|
||||
m.close()
|
||||
|
||||
@unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows')
|
||||
def test_trackfd_parameter(self):
|
||||
@support.subTests('close_original_fd', (True, False))
|
||||
def test_trackfd_parameter(self, close_original_fd):
|
||||
size = 64
|
||||
with open(TESTFN, "wb") as f:
|
||||
f.write(b"a"*size)
|
||||
for close_original_fd in True, False:
|
||||
with self.subTest(close_original_fd=close_original_fd):
|
||||
with open(TESTFN, "r+b") as f:
|
||||
with mmap.mmap(f.fileno(), size, trackfd=False) as m:
|
||||
if close_original_fd:
|
||||
f.close()
|
||||
self.assertEqual(len(m), size)
|
||||
with self.assertRaises(OSError) as err_cm:
|
||||
m.size()
|
||||
self.assertEqual(err_cm.exception.errno, errno.EBADF)
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(size * 2)
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(size // 2)
|
||||
self.assertEqual(m.closed, False)
|
||||
with open(TESTFN, "r+b") as f:
|
||||
with mmap.mmap(f.fileno(), size, trackfd=False) as m:
|
||||
if close_original_fd:
|
||||
f.close()
|
||||
self.assertEqual(len(m), size)
|
||||
with self.assertRaises(ValueError):
|
||||
m.size()
|
||||
if hasattr(m, 'resize'):
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(size * 2)
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(size // 2)
|
||||
self.assertIs(m.closed, False)
|
||||
|
||||
# Smoke-test other API
|
||||
m.write_byte(ord('X'))
|
||||
m[2] = ord('Y')
|
||||
m.flush()
|
||||
with open(TESTFN, "rb") as f:
|
||||
self.assertEqual(f.read(4), b'XaYa')
|
||||
self.assertEqual(m.tell(), 1)
|
||||
m.seek(0)
|
||||
self.assertEqual(m.tell(), 0)
|
||||
self.assertEqual(m.read_byte(), ord('X'))
|
||||
# Smoke-test other API
|
||||
m.write_byte(ord('X'))
|
||||
m[2] = ord('Y')
|
||||
m.flush()
|
||||
with open(TESTFN, "rb") as f:
|
||||
self.assertEqual(f.read(4), b'XaYa')
|
||||
self.assertEqual(m.tell(), 1)
|
||||
m.seek(0)
|
||||
self.assertEqual(m.tell(), 0)
|
||||
self.assertEqual(m.read_byte(), ord('X'))
|
||||
|
||||
self.assertEqual(m.closed, True)
|
||||
self.assertEqual(os.stat(TESTFN).st_size, size)
|
||||
if os.name == 'nt' and not close_original_fd:
|
||||
self.assertRaises(PermissionError, os.rename, TESTFN, TESTFN+'1')
|
||||
else:
|
||||
os.rename(TESTFN, TESTFN+'1')
|
||||
os.rename(TESTFN+'1', TESTFN)
|
||||
|
||||
self.assertIs(m.closed, True)
|
||||
self.assertEqual(os.stat(TESTFN).st_size, size)
|
||||
|
||||
@unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows')
|
||||
def test_trackfd_neg1(self):
|
||||
size = 64
|
||||
with mmap.mmap(-1, size, trackfd=False) as m:
|
||||
with self.assertRaises(OSError):
|
||||
m.size()
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(size // 2)
|
||||
m.size()
|
||||
if hasattr(m, 'resize'):
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(size // 2)
|
||||
self.assertEqual(len(m), size)
|
||||
m[0] = ord('a')
|
||||
assert m[0] == ord('a')
|
||||
|
||||
@unittest.skipIf(os.name != 'nt', 'trackfd only fails on Windows')
|
||||
def test_no_trackfd_parameter_on_windows(self):
|
||||
# 'trackffd' is an invalid keyword argument for this function
|
||||
size = 64
|
||||
with self.assertRaises(TypeError):
|
||||
mmap.mmap(-1, size, trackfd=True)
|
||||
with self.assertRaises(TypeError):
|
||||
mmap.mmap(-1, size, trackfd=False)
|
||||
|
||||
def test_bad_file_desc(self):
|
||||
# Try opening a bad file descriptor...
|
||||
self.assertRaises(OSError, mmap.mmap, -2, 4096)
|
||||
@@ -367,6 +357,8 @@ class MmapTests(unittest.TestCase):
|
||||
self.assertEqual(m.find(b'one', 1, -1), 8)
|
||||
self.assertEqual(m.find(b'one', 1, -2), -1)
|
||||
self.assertEqual(m.find(bytearray(b'one')), 0)
|
||||
self.assertEqual(m.find(b'', n + 1), -1)
|
||||
self.assertEqual(m.rfind(b'', n + 1), -1)
|
||||
|
||||
for i in range(-n-1, n+1):
|
||||
for j in range(-n-1, n+1):
|
||||
@@ -510,6 +502,7 @@ class MmapTests(unittest.TestCase):
|
||||
b = x & 0xff
|
||||
m[x] = b
|
||||
self.assertEqual(m[x], b)
|
||||
self.assertEqual(m.size(), PAGESIZE)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
def test_read_all(self):
|
||||
@@ -621,13 +614,9 @@ class MmapTests(unittest.TestCase):
|
||||
self.assertEqual(m[0:3], b'foo')
|
||||
f.close()
|
||||
|
||||
# Try resizing map
|
||||
try:
|
||||
if hasattr(m, 'resize'):
|
||||
# Try resizing map
|
||||
m.resize(512)
|
||||
except SystemError:
|
||||
pass
|
||||
else:
|
||||
# resize() is supported
|
||||
self.assertEqual(len(m), 512)
|
||||
# Check that we can no longer seek beyond the new size.
|
||||
self.assertRaises(ValueError, m.seek, 513, 0)
|
||||
@@ -825,14 +814,12 @@ class MmapTests(unittest.TestCase):
|
||||
self.assertEqual(mm.write(b"python"), 6)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_resize_past_pos(self):
|
||||
m = mmap.mmap(-1, 8192)
|
||||
self.addCleanup(m.close)
|
||||
m.read(5000)
|
||||
try:
|
||||
m.resize(4096)
|
||||
except SystemError:
|
||||
self.skipTest("resizing not supported")
|
||||
m.resize(4096)
|
||||
self.assertEqual(m.read(14), b'')
|
||||
self.assertRaises(ValueError, m.read_byte)
|
||||
self.assertRaises(ValueError, m.write_byte, 42)
|
||||
@@ -924,6 +911,7 @@ class MmapTests(unittest.TestCase):
|
||||
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, Number()), None)
|
||||
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), None)
|
||||
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_resize_up_anonymous_mapping(self):
|
||||
"""If the mmap is backed by the pagefile ensure a resize up can happen
|
||||
and that the original data is still in place
|
||||
@@ -934,23 +922,21 @@ class MmapTests(unittest.TestCase):
|
||||
|
||||
with mmap.mmap(-1, start_size) as m:
|
||||
m[:] = data
|
||||
if sys.platform.startswith(('linux', 'android')):
|
||||
# Can't expand a shared anonymous mapping on Linux.
|
||||
# See https://bugzilla.kernel.org/show_bug.cgi?id=8691
|
||||
if sys.platform.startswith(('linux', 'android', 'netbsd')):
|
||||
# Can't expand a shared anonymous mapping on Linux
|
||||
# (see https://bugzilla.kernel.org/show_bug.cgi?id=8691)
|
||||
# or NetBSD.
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(new_size)
|
||||
else:
|
||||
try:
|
||||
m.resize(new_size)
|
||||
except SystemError:
|
||||
pass
|
||||
else:
|
||||
self.assertEqual(len(m), new_size)
|
||||
self.assertEqual(m[:start_size], data)
|
||||
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
|
||||
m.resize(new_size)
|
||||
self.assertEqual(len(m), new_size)
|
||||
self.assertEqual(m[:start_size], data)
|
||||
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
@unittest.skipUnless(os.name == 'posix', 'requires Posix')
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_resize_up_private_anonymous_mapping(self):
|
||||
start_size = PAGESIZE
|
||||
new_size = 2 * start_size
|
||||
@@ -958,16 +944,13 @@ class MmapTests(unittest.TestCase):
|
||||
|
||||
with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m:
|
||||
m[:] = data
|
||||
try:
|
||||
m.resize(new_size)
|
||||
except SystemError:
|
||||
pass
|
||||
else:
|
||||
self.assertEqual(len(m), new_size)
|
||||
self.assertEqual(m[:start_size], data)
|
||||
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
|
||||
m.resize(new_size)
|
||||
self.assertEqual(len(m), new_size)
|
||||
self.assertEqual(m[:start_size], data)
|
||||
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_resize_down_anonymous_mapping(self):
|
||||
"""If the mmap is backed by the pagefile ensure a resize down up can happen
|
||||
and that a truncated form of the original data is still in place
|
||||
@@ -978,17 +961,56 @@ class MmapTests(unittest.TestCase):
|
||||
|
||||
with mmap.mmap(-1, start_size) as m:
|
||||
m[:] = data
|
||||
try:
|
||||
m.resize(new_size)
|
||||
except SystemError:
|
||||
pass
|
||||
else:
|
||||
self.assertEqual(len(m), new_size)
|
||||
self.assertEqual(m[:], data[:new_size])
|
||||
if sys.platform.startswith(('linux', 'android')):
|
||||
# Can't expand to its original size.
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(start_size)
|
||||
m.resize(new_size)
|
||||
self.assertEqual(len(m), new_size)
|
||||
self.assertEqual(m[:], data[:new_size])
|
||||
if sys.platform.startswith(('linux', 'android')):
|
||||
# Can't expand to its original size.
|
||||
with self.assertRaises(ValueError):
|
||||
m.resize(start_size)
|
||||
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_setitem_resize_reentrancy(self):
|
||||
"""Resizing the mmap from inside __index__ while assigning to a
|
||||
single item must not access memory past the new bounds (gh-157335).
|
||||
"""
|
||||
size = 2 * PAGESIZE
|
||||
new_size = PAGESIZE
|
||||
|
||||
class ResizeOnIndex:
|
||||
def __init__(self, m):
|
||||
self.m = m
|
||||
def __index__(self):
|
||||
self.m.resize(new_size)
|
||||
return 0
|
||||
|
||||
with mmap.mmap(-1, size) as m:
|
||||
with self.assertRaises(IndexError):
|
||||
m[size - 1] = ResizeOnIndex(m)
|
||||
self.assertEqual(len(m), new_size)
|
||||
|
||||
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
|
||||
def test_setitem_slice_resize_reentrancy(self):
|
||||
"""Resizing the mmap from inside a value's buffer-protocol
|
||||
callback while assigning to a slice must not access memory past
|
||||
the new bounds (gh-157335).
|
||||
"""
|
||||
size = 2 * PAGESIZE
|
||||
new_size = PAGESIZE
|
||||
|
||||
class ResizeOnBuffer:
|
||||
def __init__(self, m, data):
|
||||
self.m = m
|
||||
self.data = data
|
||||
def __buffer__(self, flags):
|
||||
self.m.resize(new_size)
|
||||
return memoryview(self.data)
|
||||
|
||||
with mmap.mmap(-1, size) as m:
|
||||
value = ResizeOnBuffer(m, bytes(size))
|
||||
with self.assertRaises(IndexError):
|
||||
m[0:size] = value
|
||||
self.assertEqual(len(m), new_size)
|
||||
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
|
||||
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
|
||||
@@ -1197,6 +1219,74 @@ class MmapTests(unittest.TestCase):
|
||||
self.assertEqual(stdout.strip(), b'')
|
||||
self.assertEqual(stderr.strip(), b'')
|
||||
|
||||
def test_flush_parameters(self):
|
||||
with open(TESTFN, 'wb+') as f:
|
||||
f.write(b'x' * PAGESIZE * 3)
|
||||
f.flush()
|
||||
|
||||
m = mmap.mmap(f.fileno(), PAGESIZE * 3)
|
||||
self.addCleanup(m.close)
|
||||
|
||||
m.flush()
|
||||
m.flush(PAGESIZE)
|
||||
m.flush(PAGESIZE, PAGESIZE)
|
||||
|
||||
if hasattr(mmap, 'MS_SYNC'):
|
||||
m.flush(0, PAGESIZE, flags=mmap.MS_SYNC)
|
||||
if hasattr(mmap, 'MS_ASYNC'):
|
||||
m.flush(flags=mmap.MS_ASYNC)
|
||||
if hasattr(mmap, 'MS_INVALIDATE'):
|
||||
m.flush(PAGESIZE * 2, flags=mmap.MS_INVALIDATE)
|
||||
if hasattr(mmap, 'MS_ASYNC') and hasattr(mmap, 'MS_INVALIDATE'):
|
||||
if sys.platform.startswith(('freebsd', 'dragonfly')):
|
||||
# FreeBSD and DragonFly don't support this combination
|
||||
with self.assertRaises(OSError) as cm:
|
||||
m.flush(0, PAGESIZE, flags=mmap.MS_ASYNC | mmap.MS_INVALIDATE)
|
||||
self.assertEqual(cm.exception.errno, errno.EINVAL)
|
||||
else:
|
||||
m.flush(0, PAGESIZE, flags=mmap.MS_ASYNC | mmap.MS_INVALIDATE)
|
||||
|
||||
@unittest.skipUnless(sys.platform == 'linux', 'Linux only')
|
||||
@support.requires_linux_version(5, 17, 0)
|
||||
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, gh-143632")
|
||||
def test_set_name(self):
|
||||
# Test setting name on anonymous mmap
|
||||
m = mmap.mmap(-1, PAGESIZE)
|
||||
self.addCleanup(m.close)
|
||||
try:
|
||||
result = m.set_name('test_mapping')
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EINVAL:
|
||||
# gh-142419: On Fedora, prctl(PR_SET_VMA_ANON_NAME) fails with
|
||||
# EINVAL because the kernel option CONFIG_ANON_VMA_NAME is
|
||||
# disabled.
|
||||
# See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746
|
||||
self.skipTest("prctl() failed with EINVAL")
|
||||
else:
|
||||
raise
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test name length limit (80 chars including prefix "cpython:mmap:" and '\0')
|
||||
# Prefix is 13 chars, so max name is 66 chars
|
||||
long_name = 'x' * 66
|
||||
result = m.set_name(long_name)
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test name too long
|
||||
too_long_name = 'x' * 67
|
||||
with self.assertRaises(ValueError):
|
||||
m.set_name(too_long_name)
|
||||
|
||||
# Test that file-backed mmap raises error
|
||||
with open(TESTFN, 'wb+') as f:
|
||||
f.write(b'x' * PAGESIZE)
|
||||
f.flush()
|
||||
m2 = mmap.mmap(f.fileno(), PAGESIZE)
|
||||
self.addCleanup(m2.close)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
m2.set_name('should_fail')
|
||||
|
||||
|
||||
class LargeMmapTests(unittest.TestCase):
|
||||
|
||||
|
||||
+116
-23
@@ -7,8 +7,8 @@ use std::io;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use libc::{
|
||||
MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, PROT_EXEC, PROT_READ,
|
||||
PROT_WRITE,
|
||||
MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MS_ASYNC,
|
||||
MS_INVALIDATE, MS_SYNC, PROT_EXEC, PROT_READ, PROT_WRITE,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -202,10 +202,43 @@ impl MappedFile {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush_range(&self, offset: usize, size: usize) -> io::Result<()> {
|
||||
pub fn flush_range(&self, offset: usize, size: usize, flags: i32) -> io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if offset > self.as_slice().len() || size > self.as_slice().len() - offset {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
let ptr = unsafe { self.as_ptr().add(offset) };
|
||||
let flags = if flags == 0 { MS_SYNC } else { flags };
|
||||
if unsafe { libc::msync(ptr.cast_mut().cast(), size, flags) } == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let _ = flags;
|
||||
match self {
|
||||
Self::Read(_) => Ok(()),
|
||||
Self::Write(mmap) => mmap.flush_range(offset, size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The backing file must cover the mapping offset plus `new_size`, and no
|
||||
/// exported pointers may remain live while the mapping is moved or shrunk.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub unsafe fn remap(&mut self, new_size: usize) -> io::Result<()> {
|
||||
// memmap2 permits a zero-length mapping, whereas mmap.resize(0) must
|
||||
// preserve mremap's EINVAL result.
|
||||
if new_size == 0 {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
let options = memmap2::RemapOptions::new().may_move(true);
|
||||
match self {
|
||||
Self::Read(_) => Ok(()),
|
||||
Self::Write(mmap) => mmap.flush_range(offset, size),
|
||||
Self::Read(mmap) => unsafe { mmap.remap(new_size, options) },
|
||||
Self::Write(mmap) => unsafe { mmap.remap(new_size, options) },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +249,23 @@ impl MappedFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn set_mapping_name(address: usize, size: usize, name: &core::ffi::CStr) -> io::Result<()> {
|
||||
if unsafe {
|
||||
libc::prctl(
|
||||
libc::PR_SET_VMA,
|
||||
libc::PR_SET_VMA_ANON_NAME,
|
||||
address,
|
||||
size,
|
||||
name.as_ptr(),
|
||||
)
|
||||
} == -1
|
||||
{
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe impl Send for NamedMmap {}
|
||||
#[cfg(windows)]
|
||||
@@ -293,16 +343,40 @@ pub fn file_len(fd: crt_fd::Borrowed<'_>) -> io::Result<i64> {
|
||||
Ok(fileutils::fstat(fd)?.st_size.into())
|
||||
}
|
||||
|
||||
/// Inspect Python's raw descriptor without assuming that it is a valid Rust fd.
|
||||
/// CPython ignores failed fstat calls and only bounds regular-file mappings.
|
||||
#[cfg(unix)]
|
||||
pub fn prepare_file_mapping(fd: crt_fd::Borrowed<'_>) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = posix::full_fsync(fd.into());
|
||||
pub fn regular_file_len(fd: i32) -> Option<i64> {
|
||||
// Use the same target selection as memmap2's mmap64/off64_t aliases.
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
all(target_os = "linux", not(target_env = "musl"))
|
||||
)))]
|
||||
use libc::{fstat, stat as Stat};
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
all(target_os = "linux", not(target_env = "musl"))
|
||||
))]
|
||||
use libc::{fstat64 as fstat, stat64 as Stat};
|
||||
|
||||
let mut status = core::mem::MaybeUninit::<Stat>::uninit();
|
||||
// SAFETY: fstat accepts an arbitrary integer and initializes status only
|
||||
// on success. No borrowed/owned descriptor is constructed from that input.
|
||||
if unsafe { fstat(fd, status.as_mut_ptr()) } != 0 {
|
||||
return None;
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let _ = fd;
|
||||
let status = unsafe { status.assume_init() };
|
||||
if status.st_mode & libc::S_IFMT != libc::S_IFREG {
|
||||
return None;
|
||||
}
|
||||
#[allow(clippy::useless_conversion, reason = "needed for 32-bit platforms")]
|
||||
i64::try_from(status.st_size).ok()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn prepare_file_mapping(fd: i32) {
|
||||
// CPython intentionally ignores F_FULLFSYNC errors, including invalid fds.
|
||||
let _ = unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -409,25 +483,44 @@ pub fn map_anon(size: usize) -> io::Result<MappedFile> {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn map_file(
|
||||
fd: crt_fd::Borrowed<'_>,
|
||||
offset: i64,
|
||||
size: usize,
|
||||
access: AccessMode,
|
||||
) -> io::Result<(crt_fd::Owned, MappedFile)> {
|
||||
let new_fd: crt_fd::Owned = posix::dup_noninheritable(fd.into())?.into();
|
||||
pub fn duplicate_descriptor(fd: i32) -> io::Result<crt_fd::Owned> {
|
||||
// Python supplies an arbitrary integer, so validate it through fcntl before
|
||||
// constructing a Rust descriptor. Only the successful duplicate is owned.
|
||||
let new_fd = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) };
|
||||
if new_fd != -1 {
|
||||
return Ok(unsafe { crt_fd::Owned::from_raw(new_fd) });
|
||||
}
|
||||
let err = io::Error::last_os_error();
|
||||
if err.raw_os_error() != Some(libc::EINVAL) {
|
||||
return Err(err);
|
||||
}
|
||||
// Older kernels may not support F_DUPFD_CLOEXEC.
|
||||
let new_fd = unsafe { libc::dup(fd) };
|
||||
if new_fd == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let new_fd = unsafe { crt_fd::Owned::from_raw(new_fd) };
|
||||
posix::set_inheritable(new_fd.borrow().into(), false)?;
|
||||
Ok(new_fd)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn map_file(fd: i32, offset: i64, size: usize, access: AccessMode) -> io::Result<MappedFile> {
|
||||
let mut mmap_opt = MmapOptions::new();
|
||||
let mmap_opt = mmap_opt.offset(offset as u64).len(size);
|
||||
|
||||
// len is always explicit, so memmap2 never wraps this raw fd in File to
|
||||
// query metadata. Its mmap syscall rejects invalid descriptors directly.
|
||||
// The caller retains any owned tracking descriptor through this call.
|
||||
let mapped = match access {
|
||||
AccessMode::Default | AccessMode::Write => {
|
||||
unsafe { mmap_opt.map_mut(&new_fd) }.map(MappedFile::Write)?
|
||||
unsafe { mmap_opt.map_mut(fd) }.map(MappedFile::Write)?
|
||||
}
|
||||
AccessMode::Read => unsafe { mmap_opt.map(&new_fd) }.map(MappedFile::Read)?,
|
||||
AccessMode::Copy => unsafe { mmap_opt.map_copy(&new_fd) }.map(MappedFile::Write)?,
|
||||
AccessMode::Read => unsafe { mmap_opt.map(fd) }.map(MappedFile::Read)?,
|
||||
AccessMode::Copy => unsafe { mmap_opt.map_copy(fd) }.map(MappedFile::Write)?,
|
||||
};
|
||||
|
||||
Ok((new_fd, mapped))
|
||||
Ok(mapped)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
|
||||
+156
-94
@@ -11,7 +11,7 @@ mod mmap {
|
||||
use crate::vm::{
|
||||
AsObject, FromArgs, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
TryFromBorrowedObject, VirtualMachine, atomic_func,
|
||||
builtins::{PyBytes, PyBytesRef, PyInt, PyIntRef, PyType, PyTypeRef},
|
||||
builtins::{PyBytes, PyBytesRef, PyInt, PyIntRef, PyType, PyTypeRef, PyUtf8StrRef},
|
||||
byte::{bytes_from_object, value_from_object},
|
||||
convert::ToPyException,
|
||||
function::{ArgBytesLike, ArgIndex},
|
||||
@@ -72,7 +72,8 @@ mod mmap {
|
||||
#[pyattr]
|
||||
use host_mmap::{
|
||||
MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MAP_ANON,
|
||||
MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, PROT_EXEC, PROT_READ, PROT_WRITE,
|
||||
MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, MS_ASYNC, MS_INVALIDATE, MS_SYNC, PROT_EXEC,
|
||||
PROT_READ, PROT_WRITE,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -191,9 +192,6 @@ mod mmap {
|
||||
mmap: PyMutex<Option<MmapObj>>,
|
||||
#[cfg(unix)]
|
||||
fd: AtomicCell<i32>,
|
||||
// only read by the linux/netbsd mremap expansion check
|
||||
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
|
||||
flags: core::ffi::c_int,
|
||||
#[cfg(windows)]
|
||||
handle: AtomicCell<isize>, // host_mmap::Handle is isize on Windows
|
||||
offset: i64,
|
||||
@@ -203,6 +201,10 @@ mod mmap {
|
||||
access: AccessMode,
|
||||
#[cfg(unix)]
|
||||
trackfd: bool,
|
||||
#[cfg(target_os = "linux")]
|
||||
anonymous: bool,
|
||||
#[cfg(target_os = "linux")]
|
||||
private: bool,
|
||||
}
|
||||
|
||||
impl PyMmap {
|
||||
@@ -296,6 +298,8 @@ mod mmap {
|
||||
offset: isize,
|
||||
#[pyarg(positional, optional)]
|
||||
size: Option<isize>,
|
||||
#[pyarg(named, default)]
|
||||
flags: core::ffi::c_int,
|
||||
}
|
||||
|
||||
impl FlushOptions {
|
||||
@@ -304,14 +308,15 @@ mod mmap {
|
||||
return None;
|
||||
}
|
||||
let offset = self.offset as usize;
|
||||
let remaining = len.checked_sub(offset)?;
|
||||
|
||||
let size = match self.size {
|
||||
None | Some(-1) => remaining,
|
||||
Some(s) if s < 0 => return None,
|
||||
Some(s) => s as usize,
|
||||
None => len,
|
||||
};
|
||||
|
||||
if len.checked_sub(offset)? < size {
|
||||
if remaining < size {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -408,10 +413,7 @@ mod mmap {
|
||||
}
|
||||
|
||||
// TODO: memmap2 doesn't support mapping with prot and flags right now
|
||||
#[cfg_attr(
|
||||
not(any(target_os = "linux", target_os = "netbsd")),
|
||||
allow(unused_variables)
|
||||
)]
|
||||
#[cfg_attr(not(target_os = "linux"), allow(unused_variables))]
|
||||
let (flags, _prot, access) = match access {
|
||||
AccessMode::Read => (MAP_SHARED, PROT_READ, access),
|
||||
AccessMode::Write => (MAP_SHARED, PROT_READ | PROT_WRITE, access),
|
||||
@@ -428,24 +430,23 @@ mod mmap {
|
||||
}
|
||||
};
|
||||
|
||||
let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(fd) };
|
||||
let anonymous = fd == -1 || flags & host_mmap::MAP_ANONYMOUS != 0;
|
||||
|
||||
// macOS: Issue #11277: fsync(2) is not enough on OS X - a special, OS X specific
|
||||
// fcntl(2) is necessary to force DISKSYNC and get around mmap(2) bug
|
||||
// Inspect the supplied descriptor before acquiring ownership, as
|
||||
// CPython does. Invalid and non-regular fds do not impose file bounds.
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Ok(fd) = fd {
|
||||
if fd != -1 {
|
||||
host_mmap::prepare_file_mapping(fd);
|
||||
}
|
||||
|
||||
if let Ok(fd) = fd {
|
||||
let file_len = host_mmap::file_len(fd).map_err(|err| err.to_pyexception(vm))?;
|
||||
|
||||
if fd != -1
|
||||
&& let Some(file_len) = host_mmap::regular_file_len(fd)
|
||||
{
|
||||
if map_size == 0 {
|
||||
if file_len == 0 {
|
||||
return Err(vm.new_value_error("cannot mmap an empty file"));
|
||||
}
|
||||
|
||||
if offset > file_len {
|
||||
if offset >= file_len {
|
||||
return Err(vm.new_value_error("mmap offset is greater than file size"));
|
||||
}
|
||||
|
||||
@@ -457,37 +458,40 @@ mod mmap {
|
||||
}
|
||||
}
|
||||
|
||||
let (fd, mmap) = || -> std::io::Result<_> {
|
||||
if let Ok(fd) = fd {
|
||||
let (new_fd, mmap) = host_mmap::map_file(
|
||||
fd,
|
||||
offset,
|
||||
map_size,
|
||||
match access {
|
||||
AccessMode::Default => host_mmap::AccessMode::Default,
|
||||
AccessMode::Read => host_mmap::AccessMode::Read,
|
||||
AccessMode::Write => host_mmap::AccessMode::Write,
|
||||
AccessMode::Copy => host_mmap::AccessMode::Copy,
|
||||
},
|
||||
)?;
|
||||
Ok((Some(new_fd), mmap))
|
||||
} else {
|
||||
let mmap = host_mmap::map_anon(map_size)?;
|
||||
Ok((None, mmap))
|
||||
}
|
||||
}()
|
||||
let retained_fd = if fd != -1 && trackfd {
|
||||
Some(host_mmap::duplicate_descriptor(fd).map_err(|err| err.to_pyexception(vm))?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mmap = if anonymous {
|
||||
host_mmap::map_anon(map_size)
|
||||
} else {
|
||||
// Keep an owned tracking fd alive through mmap when requested.
|
||||
// Otherwise use the raw integer directly, without fabricating a
|
||||
// BorrowedFd or creating a transient duplicate that can hit EMFILE.
|
||||
let mapping_fd = retained_fd.as_ref().map_or(fd, |owned| owned.as_raw());
|
||||
host_mmap::map_file(
|
||||
mapping_fd,
|
||||
offset,
|
||||
map_size,
|
||||
match access {
|
||||
AccessMode::Default => host_mmap::AccessMode::Default,
|
||||
AccessMode::Read => host_mmap::AccessMode::Read,
|
||||
AccessMode::Write => host_mmap::AccessMode::Write,
|
||||
AccessMode::Copy => host_mmap::AccessMode::Copy,
|
||||
},
|
||||
)
|
||||
}
|
||||
.map_err(|e| e.to_pyexception(vm))?;
|
||||
|
||||
Ok(Self {
|
||||
closed: AtomicCell::new(false),
|
||||
mmap: PyMutex::new(Some(MmapObj::Mapped(mmap))),
|
||||
fd: AtomicCell::new(if trackfd {
|
||||
fd.map_or(-1, |owned| owned.into_raw())
|
||||
} else {
|
||||
-1
|
||||
}),
|
||||
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
|
||||
flags,
|
||||
#[cfg(target_os = "linux")]
|
||||
anonymous,
|
||||
#[cfg(target_os = "linux")]
|
||||
private: flags & MAP_PRIVATE != 0,
|
||||
fd: AtomicCell::new(retained_fd.map_or(-1, |owned| owned.into_raw())),
|
||||
offset,
|
||||
size: AtomicCell::new(map_size),
|
||||
pos: AtomicCell::new(0),
|
||||
@@ -793,8 +797,7 @@ mod mmap {
|
||||
Ok(m)
|
||||
}
|
||||
|
||||
/// TODO: impl resize
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(target_os = "linux", windows, all(test, feature = "threading")))]
|
||||
fn check_resizeable(
|
||||
&self,
|
||||
vm: &VirtualMachine,
|
||||
@@ -913,6 +916,8 @@ mod mmap {
|
||||
|
||||
#[pymethod]
|
||||
fn flush(zelf: &Py<Self>, options: FlushOptions, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let mmap = zelf.check_valid(vm)?;
|
||||
let flags = options.flags;
|
||||
let (offset, size) = options
|
||||
.values(zelf.__len__())
|
||||
.ok_or_else(|| vm.new_value_error("flush values out of range"))?;
|
||||
@@ -921,9 +926,9 @@ mod mmap {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match zelf.check_valid(vm)?.deref().as_ref().unwrap() {
|
||||
match mmap.deref().as_ref().unwrap() {
|
||||
MmapObj::Mapped(mmap) => {
|
||||
mmap.flush_range(offset, size)
|
||||
mmap.flush_range(offset, size, flags)
|
||||
.map_err(|e| e.to_pyexception(vm))?;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
@@ -937,15 +942,54 @@ mod mmap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn set_name(zelf: &Py<Self>, name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let name =
|
||||
alloc::ffi::CString::new(name.as_str()).map_err(|err| err.to_pyexception(vm))?;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
const PREFIX: &str = "cpython:mmap:";
|
||||
if name.as_bytes().len() > 79 - PREFIX.len() {
|
||||
return Err(vm.new_value_error("name is too long"));
|
||||
}
|
||||
if !zelf.anonymous {
|
||||
return Err(
|
||||
vm.new_value_error("Cannot set annotation on non-anonymous mappings")
|
||||
);
|
||||
}
|
||||
// As in CPython's release build, kernel annotations are enabled
|
||||
// only in development mode.
|
||||
if vm.state.config.settings.dev_mode {
|
||||
let annotation =
|
||||
alloc::ffi::CString::new([PREFIX.as_bytes(), name.as_bytes()].concat())
|
||||
.expect("the name was checked for embedded NUL characters");
|
||||
let mmap = zelf.mmap.lock();
|
||||
let address = mmap
|
||||
.as_ref()
|
||||
.map_or(0, |mapping| mapping.as_slice().as_ptr() as usize);
|
||||
host_mmap::set_mapping_name(address, zelf.__len__(), &annotation)
|
||||
.map_err(|err| err.to_pyexception(vm))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let _ = (zelf, name);
|
||||
Err(vm.new_not_implemented_error(
|
||||
"Annotation of mmap is not supported on this platform",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
#[pymethod]
|
||||
fn madvise(zelf: &Py<Self>, options: AdviseOptions, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let guard = zelf.check_valid(vm)?;
|
||||
let (option, start, length) = options.values(zelf.__len__(), vm)?;
|
||||
if !host_mmap::validate_advice(option) {
|
||||
return Err(vm.new_value_error("Not a valid Advice value"));
|
||||
}
|
||||
|
||||
let guard = zelf.check_valid(vm)?;
|
||||
let mmap = guard.deref().as_ref().unwrap();
|
||||
match mmap {
|
||||
MmapObj::Mapped(m) => m.madvise_range(start, length, option),
|
||||
@@ -987,14 +1031,13 @@ mod mmap {
|
||||
Some((dest, src, cnt))
|
||||
}
|
||||
|
||||
let size = zelf.__len__();
|
||||
let (dest, src, cnt) = args(dest.as_ref(), src.as_ref(), count.as_ref(), size, vm)
|
||||
.ok_or_else(|| vm.new_value_error("source, destination, or count out of range"))?;
|
||||
|
||||
let dest_end = dest + cnt;
|
||||
let src_end = src + cnt;
|
||||
|
||||
zelf.try_writable(vm, |mmap| {
|
||||
let (dest, src, cnt) =
|
||||
args(dest.as_ref(), src.as_ref(), count.as_ref(), mmap.len(), vm).ok_or_else(
|
||||
|| vm.new_value_error("source, destination, or count out of range"),
|
||||
)?;
|
||||
let dest_end = dest + cnt;
|
||||
let src_end = src + cnt;
|
||||
let src_buf = mmap[src..src_end].to_vec();
|
||||
(&mut mmap[dest..dest_end])
|
||||
.write(&src_buf)
|
||||
@@ -1025,6 +1068,9 @@ mod mmap {
|
||||
.filter(|&n| n >= 0 && (n as usize) <= remaining)
|
||||
.map_or(remaining, |n| n as usize);
|
||||
|
||||
if num_bytes == 0 {
|
||||
return Ok(PyBytes::from(vec![]).into_ref(&vm.ctx));
|
||||
}
|
||||
let end_pos = pos + num_bytes;
|
||||
let bytes = mmap.deref().as_ref().unwrap().as_slice()[pos..end_pos].to_vec();
|
||||
|
||||
@@ -1037,12 +1083,13 @@ mod mmap {
|
||||
|
||||
#[pymethod]
|
||||
fn read_byte(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIntRef> {
|
||||
let mmap = zelf.check_valid(vm)?;
|
||||
let pos = zelf.pos();
|
||||
if pos >= zelf.__len__() {
|
||||
return Err(vm.new_value_error("read byte out of range"));
|
||||
}
|
||||
|
||||
let b = zelf.check_valid(vm)?.deref().as_ref().unwrap().as_slice()[pos];
|
||||
let b = mmap.deref().as_ref().unwrap().as_slice()[pos];
|
||||
|
||||
zelf.advance_pos(1);
|
||||
|
||||
@@ -1077,29 +1124,38 @@ mod mmap {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "linux")]
|
||||
#[pymethod]
|
||||
fn resize(zelf: &Py<Self>, newsize: PyIntRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
drop(zelf.check_resizeable(vm)?);
|
||||
|
||||
let new_size: isize = newsize.try_to_primitive(vm).map_err(|_| {
|
||||
let newsize: isize = newsize.try_to_primitive(vm).map_err(|_| {
|
||||
vm.new_overflow_error("Python int too large to convert to C ssize_t")
|
||||
})?;
|
||||
|
||||
// Linux mremap() refuses to grow a shared anonymous mapping, and NetBSD
|
||||
// mremap() returns a mapping whose grown region is not backed.
|
||||
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
|
||||
if zelf.fd.load() == -1
|
||||
&& zelf.flags & host_mmap::MAP_PRIVATE == 0
|
||||
&& new_size > zelf.size.load() as isize
|
||||
{
|
||||
// Reject exports before taking the data lock, and retain the guard
|
||||
// through truncation and remapping so a new export cannot race us.
|
||||
let mut mmap = zelf.check_resizeable(vm)?;
|
||||
let file_size = i128::from(zelf.offset) + newsize as i128;
|
||||
if newsize < 0 || file_size > isize::MAX as i128 {
|
||||
return Err(vm.new_value_error("new size out of range"));
|
||||
}
|
||||
let newsize = newsize as usize;
|
||||
if zelf.anonymous && !zelf.private && newsize > zelf.__len__() {
|
||||
return Err(vm.new_value_error("mmap: can't expand a shared anonymous mapping"));
|
||||
}
|
||||
|
||||
// TODO: implement using mremap on Linux
|
||||
#[cfg(not(any(target_os = "linux", target_os = "netbsd")))]
|
||||
let _ = new_size;
|
||||
Err(vm.new_system_error("mmap: resizing not available--no mremap()"))
|
||||
let fd = zelf.fd.load();
|
||||
if fd != -1 {
|
||||
let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }
|
||||
.map_err(|err| err.to_pyexception(vm))?;
|
||||
let file_size = file_size
|
||||
.try_into()
|
||||
.map_err(|_| vm.new_value_error("new size out of range"))?;
|
||||
crt_fd::ftruncate(fd, file_size).map_err(|err| err.to_pyexception(vm))?;
|
||||
}
|
||||
let MmapObj::Mapped(mapping) = mmap.as_mut().unwrap();
|
||||
// The mapping is locked, exported buffers are prohibited, and its
|
||||
// backing file has already been resized to cover the new mapping.
|
||||
unsafe { mapping.remap(newsize) }.map_err(|err| err.to_pyexception(vm))?;
|
||||
zelf.size.store(newsize);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -1237,15 +1293,25 @@ mod mmap {
|
||||
|
||||
#[cfg(unix)]
|
||||
#[pymethod]
|
||||
fn size(zelf: &Py<Self>, vm: &VirtualMachine) -> std::io::Result<PyIntRef> {
|
||||
let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(zelf.fd.load())? };
|
||||
let file_len = host_mmap::file_len(fd)?;
|
||||
fn size(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIntRef> {
|
||||
let _mmap = zelf.check_valid(vm)?;
|
||||
if !zelf.trackfd {
|
||||
return Err(vm.new_value_error("can't get size with trackfd=False"));
|
||||
}
|
||||
let fd = zelf.fd.load();
|
||||
if fd == -1 {
|
||||
return Ok(PyInt::from(zelf.__len__()).into_ref(&vm.ctx));
|
||||
}
|
||||
let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }
|
||||
.map_err(|err| err.to_pyexception(vm))?;
|
||||
let file_len = host_mmap::file_len(fd).map_err(|err| err.to_pyexception(vm))?;
|
||||
Ok(PyInt::from(file_len).into_ref(&vm.ctx))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[pymethod]
|
||||
fn size(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIntRef> {
|
||||
let _mmap = zelf.check_valid(vm)?;
|
||||
let handle = zelf.handle.load();
|
||||
if host_mmap::is_invalid_handle_value(handle) {
|
||||
// Anonymous mapping, return the mmap size
|
||||
@@ -1271,7 +1337,6 @@ mod mmap {
|
||||
fn write(zelf: &Py<Self>, bytes: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyIntRef> {
|
||||
let self_ = &**zelf;
|
||||
let pos = self_.pos();
|
||||
let size = self_.__len__();
|
||||
|
||||
// Writing locks the map, and reading a source that views this same
|
||||
// map locks it too, so such a source is copied out first.
|
||||
@@ -1285,11 +1350,10 @@ mod mmap {
|
||||
&borrowed
|
||||
};
|
||||
|
||||
if pos > size || size - pos < data.len() {
|
||||
return Err(vm.new_value_error("data out of range"));
|
||||
}
|
||||
|
||||
let len = self_.try_writable(vm, |mmap| {
|
||||
if pos > mmap.len() || mmap.len() - pos < data.len() {
|
||||
return Err(vm.new_value_error("data out of range"));
|
||||
}
|
||||
(&mut mmap[pos..(pos + data.len())])
|
||||
.write(data)
|
||||
.map_err(|err| err.to_pyexception(vm))?;
|
||||
@@ -1306,15 +1370,13 @@ mod mmap {
|
||||
let b = value_from_object(vm, &byte)?;
|
||||
|
||||
let pos = zelf.pos();
|
||||
let size = zelf.__len__();
|
||||
|
||||
if pos >= size {
|
||||
return Err(vm.new_value_error("write byte out of range"));
|
||||
}
|
||||
|
||||
zelf.try_writable(vm, |mmap| {
|
||||
if pos >= mmap.len() {
|
||||
return Err(vm.new_value_error("write byte out of range"));
|
||||
}
|
||||
mmap[pos] = b;
|
||||
})?;
|
||||
Ok(())
|
||||
})??;
|
||||
|
||||
zelf.advance_pos(1);
|
||||
|
||||
@@ -1397,11 +1459,12 @@ mod mmap {
|
||||
}
|
||||
|
||||
fn getitem_by_index(&self, i: isize, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let mmap = self.check_valid(vm)?;
|
||||
let i = i
|
||||
.wrapped_at(self.__len__())
|
||||
.ok_or_else(|| vm.new_index_error("mmap index out of range"))?;
|
||||
|
||||
let b = self.check_valid(vm)?.deref().as_ref().unwrap().as_slice()[i];
|
||||
let b = mmap.deref().as_ref().unwrap().as_slice()[i];
|
||||
|
||||
Ok(PyInt::from(b).into_ref(&vm.ctx).into())
|
||||
}
|
||||
@@ -1411,9 +1474,8 @@ mod mmap {
|
||||
slice: &SaturatedSlice,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let (range, step, slice_len) = slice.adjust_indices(self.__len__());
|
||||
|
||||
let mmap = self.check_valid(vm)?;
|
||||
let (range, step, slice_len) = slice.adjust_indices(self.__len__());
|
||||
let slice_data = mmap.deref().as_ref().unwrap().as_slice();
|
||||
|
||||
if slice_len == 0 {
|
||||
|
||||
Reference in new issue
Block a user