From: Donghee Na Date: Thu, 18 Dec 2025 14:33:49 +0000 (+0900) Subject: gh-142419: Add mmap.set_name method for user custom annotation (gh-142480) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=14f0b5191ad1d749d2ba5810f4f4495ee5581ce9;p=thirdparty%2FPython%2Fcpython.git gh-142419: Add mmap.set_name method for user custom annotation (gh-142480) --- diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index f32aa322c40d..41b90f2c3b31 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 including the ``'\0'`` terminator. + + .. 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 753b1990eb33..3c7e28a00c9c 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -592,6 +592,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/Include/internal/pycore_mmap.h b/Include/internal/pycore_mmap.h index 214fd4362a55..897816db0107 100644 --- a/Include/internal/pycore_mmap.h +++ b/Include/internal/pycore_mmap.h @@ -17,25 +17,27 @@ extern "C" { #endif #if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__) -static inline void +static inline int _PyAnnotateMemoryMap(void *addr, size_t size, const char *name) { #ifndef Py_DEBUG if (!_Py_GetConfig()->dev_mode) { - return; + return 0; } #endif + // The name length cannot exceed 80 (including the '\0'). assert(strlen(name) < 80); - int old_errno = errno; - prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name); - /* Ignore errno from prctl */ - /* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */ - errno = old_errno; + int res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name); + if (res < 0) { + return -1; + } + return 0; } #else -static inline void +static inline int _PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name)) { + return 0; } #endif diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 368af0cf89c3..aad916ecfe2c 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -6,6 +6,7 @@ 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 errno import unittest import os import re @@ -1165,6 +1166,46 @@ class MmapTests(unittest.TestCase): 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) + 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): 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 000000000000..63955923cd15 --- /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 f7fc172b3af7..b63f7df2a7e3 100644 --- a/Modules/clinic/mmapmodule.c.h +++ b/Modules/clinic/mmapmodule.c.h @@ -479,6 +479,42 @@ exit: 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 @@ exit: #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 37003020de26..ea20fb29c902 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -1117,6 +1117,47 @@ mmap_mmap_seek_impl(mmap_object *self, Py_ssize_t dist, int how) return NULL; } +/*[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) > 79) { + PyErr_SetString(PyExc_ValueError, "name is too long"); + return NULL; + } + if (self->flags & MAP_ANONYMOUS) { + char buf[80]; + sprintf(buf, "%s%s", prefix, name); + if (_PyAnnotateMemoryMap(self->data, self->size, buf) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + 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 +1438,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 +1994,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) { + (void)_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap"); + } +#endif m_obj->access = (access_mode)access; return (PyObject *)m_obj; } diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index b1f9fa2e6922..c4ccc9e283fe 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -468,7 +468,7 @@ _PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size) if (ptr == MAP_FAILED) return NULL; assert(ptr != NULL); - _PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc"); + (void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc"); return ptr; #else return malloc(size); diff --git a/Python/jit.c b/Python/jit.c index ccafe0ce497f..7660f6f9beac 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -77,7 +77,7 @@ jit_alloc(size_t size) unsigned char *memory = mmap(NULL, size, prot, flags, -1, 0); int failed = memory == MAP_FAILED; if (!failed) { - _PyAnnotateMemoryMap(memory, size, "cpython:jit"); + (void)_PyAnnotateMemoryMap(memory, size, "cpython:jit"); } #endif if (failed) { diff --git a/Python/perf_jit_trampoline.c b/Python/perf_jit_trampoline.c index af7d8f9f1ec0..0ffa906d85cc 100644 --- a/Python/perf_jit_trampoline.c +++ b/Python/perf_jit_trampoline.c @@ -1086,7 +1086,8 @@ static void* perf_map_jit_init(void) { close(fd); return NULL; // Memory mapping failed } - _PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, "cpython:perf_jit_trampoline"); + (void)_PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, + "cpython:perf_jit_trampoline"); #endif perf_jit_map_state.mapped_size = page_size; diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c index 669a47ae1737..335d8ac7dadd 100644 --- a/Python/perf_trampoline.c +++ b/Python/perf_trampoline.c @@ -291,7 +291,7 @@ new_code_arena(void) perf_status = PERF_STATUS_FAILED; return -1; } - _PyAnnotateMemoryMap(memory, mem_size, "cpython:perf_trampoline"); + (void)_PyAnnotateMemoryMap(memory, mem_size, "cpython:perf_trampoline"); void *start = &_Py_trampoline_func_start; void *end = &_Py_trampoline_func_end; size_t code_size = end - start;