diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index f32aa322c40dbb..b5b8f1d6a5e246 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -328,6 +328,17 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. versionadded:: 3.13 + .. method:: set_name(name, /) + + Annotate the memory mapping with the given *name* for easier identification + in ``/proc//maps`` if the kernel supports the feature and :option:`-X dev <-X>` is passed + to Python or if Python is built in :ref:`debug mode ` + The length of *name* must not exceed 67 bytes. + + .. availability:: Linux >= 5.17 (kernel built with ``CONFIG_ANON_VMA_NAME`` option) + + .. versionadded:: next + .. method:: size() Return the length of the file, which can be larger than the size of the diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 853c47d4402f20..b879abdb664e50 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -581,6 +581,11 @@ mmap not be duplicated. (Contributed by Serhiy Storchaka in :gh:`78502`.) +* Added the :meth:`mmap.mmap.set_name` method + to annotate an anonymous memory mapping + if Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME ` (Linux 5.17 or newer). + (Contributed by Donghee Na in :gh:`142419`.) + os -- diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 368af0cf89c300..75b9a65075e7f3 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1165,6 +1165,36 @@ def test_flush_parameters(self): m.flush(PAGESIZE) m.flush(PAGESIZE, PAGESIZE) + @unittest.skipUnless(sys.platform == 'linux', 'Linux only') + @support.requires_linux_version(5, 17, 0) + def test_set_name(self): + # Test setting name on anonymous mmap + m = mmap.mmap(-1, PAGESIZE) + self.addCleanup(m.close) + result = m.set_name('test_mapping') + self.assertIsNone(result) + + # Test name length limit (80 chars including prefix "cpython:mmap:") + # Prefix is 13 chars, so max name is 67 chars + long_name = 'x' * 67 + result = m.set_name(long_name) + self.assertIsNone(result) + + # Test name too long + too_long_name = 'x' * 68 + 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): diff --git a/Misc/NEWS.d/next/Library/2025-12-10-02-31-43.gh-issue-142419.C8_LES.rst b/Misc/NEWS.d/next/Library/2025-12-10-02-31-43.gh-issue-142419.C8_LES.rst new file mode 100644 index 00000000000000..63955923cd157c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-10-02-31-43.gh-issue-142419.C8_LES.rst @@ -0,0 +1,3 @@ +:meth:`mmap.mmap.set_name` method added to annotate an anonymous memory map +if Linux kernel supports ``PR_SET_VMA_ANON_NAME`` (Linux 5.17 or newer). +Patch by Donghee Na. diff --git a/Modules/clinic/mmapmodule.c.h b/Modules/clinic/mmapmodule.c.h index f7fc172b3af705..b63f7df2a7e334 100644 --- a/Modules/clinic/mmapmodule.c.h +++ b/Modules/clinic/mmapmodule.c.h @@ -479,6 +479,42 @@ mmap_mmap_seek(PyObject *self, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(mmap_mmap_set_name__doc__, +"set_name($self, name, /)\n" +"--\n" +"\n"); + +#define MMAP_MMAP_SET_NAME_METHODDEF \ + {"set_name", (PyCFunction)mmap_mmap_set_name, METH_O, mmap_mmap_set_name__doc__}, + +static PyObject * +mmap_mmap_set_name_impl(mmap_object *self, const char *name); + +static PyObject * +mmap_mmap_set_name(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *name; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_name", "argument", "str", arg); + goto exit; + } + Py_ssize_t name_length; + name = PyUnicode_AsUTF8AndSize(arg, &name_length); + if (name == NULL) { + goto exit; + } + if (strlen(name) != (size_t)name_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + return_value = mmap_mmap_set_name_impl((mmap_object *)self, name); + +exit: + return return_value; +} + PyDoc_STRVAR(mmap_mmap_seekable__doc__, "seekable($self, /)\n" "--\n" @@ -796,4 +832,4 @@ mmap_mmap_madvise(PyObject *self, PyObject *const *args, Py_ssize_t nargs) #ifndef MMAP_MMAP_MADVISE_METHODDEF #define MMAP_MMAP_MADVISE_METHODDEF #endif /* !defined(MMAP_MMAP_MADVISE_METHODDEF) */ -/*[clinic end generated code: output=381f6cf4986ac867 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fd9ca0ef425af934 input=a9049054013a1b77]*/ diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 37003020de2688..483592a0471c2d 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -1117,6 +1117,46 @@ mmap_mmap_seek_impl(mmap_object *self, Py_ssize_t dist, int how) return NULL; } +/*clinic*/ + +/*[clinic input] +mmap.mmap.set_name + + name: str + / + +[clinic start generated code]*/ + +static PyObject * +mmap_mmap_set_name_impl(mmap_object *self, const char *name) +/*[clinic end generated code: output=1edaf4fd51277760 input=6c7dd91cad205f07]*/ +{ +#if defined(MAP_ANONYMOUS) && defined(__linux__) + const char *prefix = "cpython:mmap:"; + if (strlen(name) + strlen(prefix) > 80) { + PyErr_SetString(PyExc_ValueError, "name is too long"); + return NULL; + } + if (self->flags & MAP_ANONYMOUS) { + char buf[81]; + sprintf(buf, "%s%s", prefix, name); + _PyAnnotateMemoryMap(self->data, self->size, buf); + Py_RETURN_NONE; + } + else { + /* cannot name non-anonymous mappings */ + PyErr_SetString(PyExc_ValueError, + "Cannot set annotation on non-anonymous mappings"); + return NULL; + } +#else + /* naming not supported on this platform */ + PyErr_SetString(PyExc_NotImplementedError, + "Annotation of mmap is not supported on this platform"); + return NULL; +#endif +} + /*[clinic input] mmap.mmap.seekable @@ -1397,6 +1437,7 @@ static struct PyMethodDef mmap_object_methods[] = { MMAP_MMAP_RESIZE_METHODDEF MMAP_MMAP_SEEK_METHODDEF MMAP_MMAP_SEEKABLE_METHODDEF + MMAP_MMAP_SET_NAME_METHODDEF MMAP_MMAP_SIZE_METHODDEF MMAP_MMAP_TELL_METHODDEF MMAP_MMAP_WRITE_METHODDEF @@ -1952,7 +1993,11 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) PyErr_SetFromErrno(PyExc_OSError); return NULL; } - _PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap"); +#ifdef MAP_ANONYMOUS + if (m_obj->flags & MAP_ANONYMOUS) { + _PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap"); + } +#endif m_obj->access = (access_mode)access; return (PyObject *)m_obj; }