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
file handle *fileno*, and creates a mmap object. If *length* is larger
.. 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:
**(Unix version)** Maps *length* bytes from the file specified by the file
defaults to 0. *offset* must be a multiple of :const:`ALLOCATIONGRANULARITY`
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
by the descriptor *fileno* is internally automatically synchronized
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`::
import mmap
.. method:: resize(newsize)
- Resizes the map and the underlying file, if any. If the mmap was created
- with :const:`ACCESS_READ` or :const:`ACCESS_COPY`, resizing the map will
- raise a :exc:`TypeError` exception.
+ Resizes the map and the underlying file, if any.
+
+ 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
maps against the same named file. Resizing an anonymous map (ie against the
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.
(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
------
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
import unittest
+import errno
import os
import re
import itertools
self.assertRaises(TypeError, m.write_byte, 0)
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):
# Try opening a bad file descriptor...
self.assertRaises(OSError, mmap.mmap, -2, 4096)
--- /dev/null
+:class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is
+``False``, the file descriptor specified by *fileno* will not be duplicated.
#ifdef UNIX
int fd;
+ _Bool trackfd;
#endif
PyObject *weakreflist;
"mmap can't resize with extant buffers exported.");
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))
return 1;
PyErr_Format(PyExc_TypeError,
except that if the file is empty Windows raises an exception (you cannot\n\
create an empty mapping on Windows).\n\
\n\
-Unix: mmap(fileno, length[, flags[, prot[, access[, offset]]]])\n\
+Unix: mmap(fileno, length[, flags[, prot[, access[, offset[, trackfd]]]]])\n\
\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\
off_t offset = 0;
int fd, flags = MAP_SHARED, prot = PROT_WRITE | PROT_READ;
int devzero = -1;
- int access = (int)ACCESS_DEFAULT;
+ int access = (int)ACCESS_DEFAULT, trackfd = 1;
static char *keywords[] = {"fileno", "length",
"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,
- &access, &offset))
+ &access, &offset, &trackfd)) {
return NULL;
+ }
if (map_size < 0) {
PyErr_SetString(PyExc_OverflowError,
"memory mapped length must be positive");
m_obj->weakreflist = NULL;
m_obj->exports = 0;
m_obj->offset = offset;
+ m_obj->trackfd = trackfd;
if (fd == -1) {
m_obj->fd = -1;
/* Assume the caller wants to map anonymous memory.
}
#endif
}
- else {
+ else if (trackfd) {
m_obj->fd = _Py_dup(fd);
if (m_obj->fd == -1) {
Py_DECREF(m_obj);
return NULL;
}
}
+ else {
+ m_obj->fd = -1;
+ }
Py_BEGIN_ALLOW_THREADS
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);