mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
gh-78502: Add a trackfd parameter to mmap.mmap() (GH-25425)
If *trackfd* is False, the file descriptor specified by *fileno* will not be duplicated. Co-authored-by: Erlend E. Aasland <erlend@python.org> Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
42b90cf0d6
commit
8fd287b18f
5 changed files with 101 additions and 11 deletions
|
|
@ -48,7 +48,7 @@ update the underlying file.
|
||||||
|
|
||||||
To map anonymous memory, -1 should be passed as the fileno along with the length.
|
To map anonymous memory, -1 should be passed as the fileno along with the length.
|
||||||
|
|
||||||
.. class:: mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset])
|
.. class:: mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT, offset=0)
|
||||||
|
|
||||||
**(Windows version)** Maps *length* bytes from the file specified by the
|
**(Windows version)** Maps *length* bytes from the file specified by the
|
||||||
file handle *fileno*, and creates a mmap object. If *length* is larger
|
file handle *fileno*, and creates a mmap object. If *length* is larger
|
||||||
|
|
@ -71,7 +71,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
|
||||||
|
|
||||||
.. audit-event:: mmap.__new__ fileno,length,access,offset mmap.mmap
|
.. audit-event:: mmap.__new__ fileno,length,access,offset mmap.mmap
|
||||||
|
|
||||||
.. class:: mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset])
|
.. class:: mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, \
|
||||||
|
access=ACCESS_DEFAULT, offset=0, *, trackfd=True)
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
**(Unix version)** Maps *length* bytes from the file specified by the file
|
**(Unix version)** Maps *length* bytes from the file specified by the file
|
||||||
|
|
@ -102,10 +103,20 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
|
||||||
defaults to 0. *offset* must be a multiple of :const:`ALLOCATIONGRANULARITY`
|
defaults to 0. *offset* must be a multiple of :const:`ALLOCATIONGRANULARITY`
|
||||||
which is equal to :const:`PAGESIZE` on Unix systems.
|
which is equal to :const:`PAGESIZE` on Unix systems.
|
||||||
|
|
||||||
|
If *trackfd* is ``False``, the file descriptor specified by *fileno* will
|
||||||
|
not be duplicated, and the resulting :class:`!mmap` object will not
|
||||||
|
be associated with the map's underlying file.
|
||||||
|
This means that the :meth:`~mmap.mmap.size` and :meth:`~mmap.mmap.resize`
|
||||||
|
methods will fail.
|
||||||
|
This mode is useful to limit the number of open file descriptors.
|
||||||
|
|
||||||
To ensure validity of the created memory mapping the file specified
|
To ensure validity of the created memory mapping the file specified
|
||||||
by the descriptor *fileno* is internally automatically synchronized
|
by the descriptor *fileno* is internally automatically synchronized
|
||||||
with the physical backing store on macOS.
|
with the physical backing store on macOS.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
The *trackfd* parameter was added.
|
||||||
|
|
||||||
This example shows a simple way of using :class:`~mmap.mmap`::
|
This example shows a simple way of using :class:`~mmap.mmap`::
|
||||||
|
|
||||||
import mmap
|
import mmap
|
||||||
|
|
@ -254,9 +265,12 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
|
||||||
|
|
||||||
.. method:: resize(newsize)
|
.. method:: resize(newsize)
|
||||||
|
|
||||||
Resizes the map and the underlying file, if any. If the mmap was created
|
Resizes the map and the underlying file, if any.
|
||||||
with :const:`ACCESS_READ` or :const:`ACCESS_COPY`, resizing the map will
|
|
||||||
raise a :exc:`TypeError` exception.
|
Resizing a map created with *access* of :const:`ACCESS_READ` or
|
||||||
|
:const:`ACCESS_COPY`, will raise a :exc:`TypeError` exception.
|
||||||
|
Resizing a map created with with *trackfd* set to ``False``,
|
||||||
|
will raise a :exc:`ValueError` exception.
|
||||||
|
|
||||||
**On Windows**: Resizing the map will raise an :exc:`OSError` if there are other
|
**On Windows**: Resizing the map will raise an :exc:`OSError` if there are other
|
||||||
maps against the same named file. Resizing an anonymous map (ie against the
|
maps against the same named file. Resizing an anonymous map (ie against the
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,9 @@ mmap
|
||||||
that can be used where it requires a file-like object with seekable and
|
that can be used where it requires a file-like object with seekable and
|
||||||
the :meth:`~mmap.mmap.seek` method return the new absolute position.
|
the :meth:`~mmap.mmap.seek` method return the new absolute position.
|
||||||
(Contributed by Donghee Na and Sylvie Liberman in :gh:`111835`.)
|
(Contributed by Donghee Na and Sylvie Liberman in :gh:`111835`.)
|
||||||
|
* :class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is ``False``,
|
||||||
|
the file descriptor specified by *fileno* will not be duplicated.
|
||||||
|
(Contributed by Zackery Spytz and Petr Viktorin in :gh:`78502`.)
|
||||||
|
|
||||||
opcode
|
opcode
|
||||||
------
|
------
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
from test.support.import_helper import import_module
|
from test.support.import_helper import import_module
|
||||||
from test.support.os_helper import TESTFN, unlink
|
from test.support.os_helper import TESTFN, unlink
|
||||||
import unittest
|
import unittest
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import itertools
|
import itertools
|
||||||
|
|
@ -266,6 +267,62 @@ def test_access_parameter(self):
|
||||||
self.assertRaises(TypeError, m.write_byte, 0)
|
self.assertRaises(TypeError, m.write_byte, 0)
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows')
|
||||||
|
def test_trackfd_parameter(self):
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
@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)
|
||||||
|
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):
|
def test_bad_file_desc(self):
|
||||||
# Try opening a bad file descriptor...
|
# Try opening a bad file descriptor...
|
||||||
self.assertRaises(OSError, mmap.mmap, -2, 4096)
|
self.assertRaises(OSError, mmap.mmap, -2, 4096)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
:class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is
|
||||||
|
``False``, the file descriptor specified by *fileno* will not be duplicated.
|
||||||
|
|
@ -117,6 +117,7 @@ typedef struct {
|
||||||
|
|
||||||
#ifdef UNIX
|
#ifdef UNIX
|
||||||
int fd;
|
int fd;
|
||||||
|
_Bool trackfd;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PyObject *weakreflist;
|
PyObject *weakreflist;
|
||||||
|
|
@ -393,6 +394,13 @@ is_resizeable(mmap_object *self)
|
||||||
"mmap can't resize with extant buffers exported.");
|
"mmap can't resize with extant buffers exported.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#ifdef UNIX
|
||||||
|
if (!self->trackfd) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"mmap can't resize with trackfd=False.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if ((self->access == ACCESS_WRITE) || (self->access == ACCESS_DEFAULT))
|
if ((self->access == ACCESS_WRITE) || (self->access == ACCESS_DEFAULT))
|
||||||
return 1;
|
return 1;
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
|
@ -1154,7 +1162,7 @@ is 0, the maximum length of the map is the current size of the file,\n\
|
||||||
except that if the file is empty Windows raises an exception (you cannot\n\
|
except that if the file is empty Windows raises an exception (you cannot\n\
|
||||||
create an empty mapping on Windows).\n\
|
create an empty mapping on Windows).\n\
|
||||||
\n\
|
\n\
|
||||||
Unix: mmap(fileno, length[, flags[, prot[, access[, offset]]]])\n\
|
Unix: mmap(fileno, length[, flags[, prot[, access[, offset[, trackfd]]]]])\n\
|
||||||
\n\
|
\n\
|
||||||
Maps length bytes from the file specified by the file descriptor fileno,\n\
|
Maps length bytes from the file specified by the file descriptor fileno,\n\
|
||||||
and returns a mmap object. If length is 0, the maximum length of the map\n\
|
and returns a mmap object. If length is 0, the maximum length of the map\n\
|
||||||
|
|
@ -1221,15 +1229,17 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
|
||||||
off_t offset = 0;
|
off_t offset = 0;
|
||||||
int fd, flags = MAP_SHARED, prot = PROT_WRITE | PROT_READ;
|
int fd, flags = MAP_SHARED, prot = PROT_WRITE | PROT_READ;
|
||||||
int devzero = -1;
|
int devzero = -1;
|
||||||
int access = (int)ACCESS_DEFAULT;
|
int access = (int)ACCESS_DEFAULT, trackfd = 1;
|
||||||
static char *keywords[] = {"fileno", "length",
|
static char *keywords[] = {"fileno", "length",
|
||||||
"flags", "prot",
|
"flags", "prot",
|
||||||
"access", "offset", NULL};
|
"access", "offset", "trackfd", NULL};
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|iii" _Py_PARSE_OFF_T, keywords,
|
if (!PyArg_ParseTupleAndKeywords(args, kwdict,
|
||||||
|
"in|iii" _Py_PARSE_OFF_T "$p", keywords,
|
||||||
&fd, &map_size, &flags, &prot,
|
&fd, &map_size, &flags, &prot,
|
||||||
&access, &offset))
|
&access, &offset, &trackfd)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
if (map_size < 0) {
|
if (map_size < 0) {
|
||||||
PyErr_SetString(PyExc_OverflowError,
|
PyErr_SetString(PyExc_OverflowError,
|
||||||
"memory mapped length must be positive");
|
"memory mapped length must be positive");
|
||||||
|
|
@ -1325,6 +1335,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
|
||||||
m_obj->weakreflist = NULL;
|
m_obj->weakreflist = NULL;
|
||||||
m_obj->exports = 0;
|
m_obj->exports = 0;
|
||||||
m_obj->offset = offset;
|
m_obj->offset = offset;
|
||||||
|
m_obj->trackfd = trackfd;
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
m_obj->fd = -1;
|
m_obj->fd = -1;
|
||||||
/* Assume the caller wants to map anonymous memory.
|
/* Assume the caller wants to map anonymous memory.
|
||||||
|
|
@ -1350,13 +1361,16 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else if (trackfd) {
|
||||||
m_obj->fd = _Py_dup(fd);
|
m_obj->fd = _Py_dup(fd);
|
||||||
if (m_obj->fd == -1) {
|
if (m_obj->fd == -1) {
|
||||||
Py_DECREF(m_obj);
|
Py_DECREF(m_obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
m_obj->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);
|
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue