From 5ae8927b4ad60e144a1018179a35761b6f2bda86 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 8 Oct 2025 23:29:41 +0900 Subject: [PATCH 1/5] Add fcntl.preallocate --- Modules/clinic/fcntlmodule.c.h | 56 +++++++++++++++++++++++++++- Modules/fcntlmodule.c | 67 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 2b61d9f87083f0..7b7d8de7235280 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -265,4 +265,58 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=9773e44da302dc7c input=a9049054013a1b77]*/ + +PyDoc_STRVAR(fcntl_preallocate__doc__, +"preallocate($module, fd, flags, posmode, offset, length, /)\n" +"--\n" +"\n" +"Preallocate file storage space.\n" +"\n" +"This is a wrapper around the F_PREALLOCATE fcntl command on macOS."); + +#define FCNTL_PREALLOCATE_METHODDEF \ + {"preallocate", _PyCFunction_CAST(fcntl_preallocate), METH_FASTCALL, fcntl_preallocate__doc__}, + +static PyObject * +fcntl_preallocate_impl(PyObject *module, int fd, int flags, int posmode, + long offset, long length); + +static PyObject * +fcntl_preallocate(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int fd; + int flags; + int posmode; + long offset; + long length; + + if (!_PyArg_CheckPositional("preallocate", nargs, 5, 5)) { + goto exit; + } + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { + goto exit; + } + flags = PyLong_AsInt(args[1]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } + posmode = PyLong_AsInt(args[2]); + if (posmode == -1 && PyErr_Occurred()) { + goto exit; + } + offset = PyLong_AsLong(args[3]); + if (offset == -1 && PyErr_Occurred()) { + goto exit; + } + length = PyLong_AsLong(args[4]); + if (length == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = fcntl_preallocate_impl(module, fd, flags, posmode, offset, length); + +exit: + return return_value; +} +/*[clinic end generated code: output=98ef6f68541bdd50 input=a9049054013a1b77]*/ diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index df2c9994127997..c95a76ce6fe2e8 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -502,6 +502,60 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, Py_RETURN_NONE; } + +/*[clinic input] +fcntl.preallocate + + fd: fildes + flags: int + posmode: int + offset: long + length: long + / + +Preallocate file storage space. + +This is a wrapper around the F_PREALLOCATE fcntl command. +[clinic start generated code]*/ + +static PyObject * +fcntl_preallocate_impl(PyObject *module, int fd, int flags, int posmode, + long offset, long length) +/*[clinic end generated code: output=4934b8a4dc1f5dc1 input=b8e76ad8be51da32]*/ +{ +#ifdef F_PREALLOCATE + int ret; + int async_err = 0; + + if (PySys_Audit("fcntl.preallocate", "iiill", fd, flags, posmode, offset, length) < 0) { + return NULL; + } + + struct fstore fstore = { + .fst_flags = (unsigned int)flags, + .fst_posmode = posmode, + .fst_offset = (off_t)offset, + .fst_length = (off_t)length, + .fst_bytesalloc = 0 + }; + + do { + Py_BEGIN_ALLOW_THREADS + ret = fcntl(fd, F_PREALLOCATE, &fstore); + Py_END_ALLOW_THREADS + } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + + if (ret < 0) { + return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL; + } + + return PyLong_FromLong((long)fstore.fst_bytesalloc); +#else + PyErr_SetString(PyExc_OSError, "F_PREALLOCATE not supported on this platform"); + return NULL; +#endif +} + /* List of functions */ static PyMethodDef fcntl_methods[] = { @@ -509,6 +563,7 @@ static PyMethodDef fcntl_methods[] = { FCNTL_IOCTL_METHODDEF FCNTL_FLOCK_METHODDEF FCNTL_LOCKF_METHODDEF + FCNTL_PREALLOCATE_METHODDEF {NULL, NULL} /* sentinel */ }; @@ -687,6 +742,18 @@ all_ins(PyObject* m) #ifdef F_NOCACHE if (PyModule_AddIntMacro(m, F_NOCACHE)) return -1; #endif +#ifdef F_PREALLOCATE + if (PyModule_AddIntMacro(m, F_PREALLOCATE)) return -1; +#endif +#ifdef F_ALLOCATECONTIG + if (PyModule_AddIntMacro(m, F_ALLOCATECONTIG)) return -1; +#endif +#ifdef F_ALLOCATEALL + if (PyModule_AddIntMacro(m, F_ALLOCATEALL)) return -1; +#endif +#ifdef F_ALLOCATEPERSIST + if (PyModule_AddIntMacro(m, F_ALLOCATEPERSIST)) return -1; +#endif /* FreeBSD specifics */ #ifdef F_DUP2FD From d0d9677856f6c34bba1389b72ae65ea01f79ce4a Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 9 Oct 2025 00:19:32 +0900 Subject: [PATCH 2/5] Add tests --- Lib/test/test_fcntl.py | 9 +++++++++ Modules/clinic/fcntlmodule.c.h | 12 ++++++++++-- Modules/fcntlmodule.c | 17 +++++++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 222b69a6d250cd..3e7a028b54940b 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -288,6 +288,15 @@ def test_bad_fd(self): with self.assertRaises(OSError): fcntl.fcntl(fd, fcntl.F_DUPFD, b'\0' * 2048) + @unittest.skipUnless(hasattr(fcntl, 'preallocate'), 'need fcntl.preallocate') + def test_preallocate(self): + self.f = open(TESTFN, 'wb+') + fd = self.f.fileno() + + result = fcntl.preallocate(fd, fcntl.F_ALLOCATECONTIG, fcntl.F_PEOFPOSMODE, 0, 1024) + self.assertIsInstance(result, int) + self.assertEqual(result, 1024) + if __name__ == '__main__': unittest.main() diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 7b7d8de7235280..e186413f7e9e76 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -266,13 +266,15 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +#if defined(F_PREALLOCATE) + PyDoc_STRVAR(fcntl_preallocate__doc__, "preallocate($module, fd, flags, posmode, offset, length, /)\n" "--\n" "\n" "Preallocate file storage space.\n" "\n" -"This is a wrapper around the F_PREALLOCATE fcntl command on macOS."); +"This is a wrapper around the F_PREALLOCATE fcntl command."); #define FCNTL_PREALLOCATE_METHODDEF \ {"preallocate", _PyCFunction_CAST(fcntl_preallocate), METH_FASTCALL, fcntl_preallocate__doc__}, @@ -319,4 +321,10 @@ fcntl_preallocate(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=98ef6f68541bdd50 input=a9049054013a1b77]*/ + +#endif /* defined(F_PREALLOCATE) */ + +#ifndef FCNTL_PREALLOCATE_METHODDEF + #define FCNTL_PREALLOCATE_METHODDEF +#endif /* !defined(FCNTL_PREALLOCATE_METHODDEF) */ +/*[clinic end generated code: output=2415a3c483423cf9 input=a9049054013a1b77]*/ diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index c95a76ce6fe2e8..44497c591e84ec 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -503,6 +503,8 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, } +#ifdef F_PREALLOCATE + /*[clinic input] fcntl.preallocate @@ -521,9 +523,8 @@ This is a wrapper around the F_PREALLOCATE fcntl command. static PyObject * fcntl_preallocate_impl(PyObject *module, int fd, int flags, int posmode, long offset, long length) -/*[clinic end generated code: output=4934b8a4dc1f5dc1 input=b8e76ad8be51da32]*/ +/*[clinic end generated code: output=4934b8a4dc1f5dc1 input=4c1a9d46551420ed]*/ { -#ifdef F_PREALLOCATE int ret; int async_err = 0; @@ -550,12 +551,10 @@ fcntl_preallocate_impl(PyObject *module, int fd, int flags, int posmode, } return PyLong_FromLong((long)fstore.fst_bytesalloc); -#else - PyErr_SetString(PyExc_OSError, "F_PREALLOCATE not supported on this platform"); - return NULL; -#endif } +#endif /* F_PREALLOCATE */ + /* List of functions */ static PyMethodDef fcntl_methods[] = { @@ -754,6 +753,12 @@ all_ins(PyObject* m) #ifdef F_ALLOCATEPERSIST if (PyModule_AddIntMacro(m, F_ALLOCATEPERSIST)) return -1; #endif +#ifdef F_PEOFPOSMODE + if (PyModule_AddIntMacro(m, F_PEOFPOSMODE)) return -1; +#endif +#ifdef F_VOLPOSMODE + if (PyModule_AddIntMacro(m, F_VOLPOSMODE)) return -1; +#endif /* FreeBSD specifics */ #ifdef F_DUP2FD From ebe58e5d53bad290845c20cd82fd4cabf0b9c48b Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 9 Oct 2025 00:42:39 +0900 Subject: [PATCH 3/5] Add more tests --- Lib/test/test_fcntl.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 3e7a028b54940b..aea5e5e5a3aa70 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -293,9 +293,17 @@ def test_preallocate(self): self.f = open(TESTFN, 'wb+') fd = self.f.fileno() - result = fcntl.preallocate(fd, fcntl.F_ALLOCATECONTIG, fcntl.F_PEOFPOSMODE, 0, 1024) + result = fcntl.preallocate(fd, fcntl.F_ALLOCATECONTIG, fcntl.F_PEOFPOSMODE, 0, 128) self.assertIsInstance(result, int) - self.assertEqual(result, 1024) + self.assertEqual(result, 128) + + result = fcntl.preallocate(fd, fcntl.F_ALLOCATEALL, fcntl.F_PEOFPOSMODE, 0, 256) + self.assertIsInstance(result, int) + self.assertEqual(result, 256) + + result = fcntl.preallocate(fd, fcntl.F_ALLOCATEPERSIST, fcntl.F_PEOFPOSMODE, 0, 512) + self.assertIsInstance(result, int) + self.assertEqual(result, 512) if __name__ == '__main__': From b44415620a8aa25dc1ff33620d0f15ee8cb98b92 Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 9 Oct 2025 00:54:07 +0900 Subject: [PATCH 4/5] Add document --- Doc/library/fcntl.rst | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 5c078df44ffa8b..8b37065c0af19d 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -83,6 +83,13 @@ descriptor. On Linux >= 6.1, the :mod:`!fcntl` module exposes the ``F_DUPFD_QUERY`` to query a file descriptor pointing to the same file. +.. versionchanged:: next + On macOS, the :mod:`!fcntl` module exposes the ``F_PREALLOCATE``, + ``F_ALLOCATECONTIG``, ``F_ALLOCATEALL``, ``F_ALLOCATEPERSIST``, + ``F_PEOFPOSMODE``, and ``F_VOLPOSMODE`` constants for file preallocation + operations, and the :func:`preallocate` function to preallocate file + storage space. + The module defines the following functions: @@ -248,6 +255,47 @@ The module defines the following functions: .. audit-event:: fcntl.lockf fd,cmd,len,start,whence fcntl.lockf + +.. function:: preallocate(fd, flags, posmode, offset, length, /) + + Preallocate file storage space. + + This is a wrapper around the ``F_PREALLOCATE`` fcntl command. + + *fd* is the file descriptor of the file to preallocate space for. + *flags* specifies the allocation behavior and can be one of: + + .. data:: F_ALLOCATECONTIG + + Allocate contiguous space. + + .. data:: F_ALLOCATEALL + + Allocate all requested space or none at all. + + .. data:: F_ALLOCATEPERSIST + + Do not deallocate space on close. + + *posmode* specifies the positioning mode and can be one of: + + .. data:: F_PEOFPOSMODE + + Allocate space relative to the end of the file. + + .. data:: F_VOLPOSMODE + + Allocate space relative to the volume start. + + *offset* is the starting offset for the allocation. + *length* is the number of bytes to allocate. + + Returns the number of bytes actually allocated. + + .. availability:: macOS. + + .. audit-event:: fcntl.preallocate fd,flags,posmode,offset,length fcntl.preallocate + Examples (all on a SVR4 compliant system):: import struct, fcntl, os From 98f6f9b9637d343e2770d9e21cb1a88a38f11122 Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 9 Oct 2025 00:57:34 +0900 Subject: [PATCH 5/5] Add news entry --- .../next/Library/2025-10-09-00-55-12.gh-issue-113092.EC9QN_.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-10-09-00-55-12.gh-issue-113092.EC9QN_.rst diff --git a/Misc/NEWS.d/next/Library/2025-10-09-00-55-12.gh-issue-113092.EC9QN_.rst b/Misc/NEWS.d/next/Library/2025-10-09-00-55-12.gh-issue-113092.EC9QN_.rst new file mode 100644 index 00000000000000..daa43a4782afe3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-09-00-55-12.gh-issue-113092.EC9QN_.rst @@ -0,0 +1 @@ +Add :func:`fcntl.preallocate` function and related constants for macOS.