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 diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 222b69a6d250cd..aea5e5e5a3aa70 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -288,6 +288,23 @@ 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, 128) + self.assertIsInstance(result, int) + 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__': unittest.main() 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. diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 2b61d9f87083f0..e186413f7e9e76 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -265,4 +265,66 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=9773e44da302dc7c input=a9049054013a1b77]*/ + +#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."); + +#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; +} + +#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 df2c9994127997..44497c591e84ec 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -502,6 +502,59 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, Py_RETURN_NONE; } + +#ifdef F_PREALLOCATE + +/*[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=4c1a9d46551420ed]*/ +{ + 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); +} + +#endif /* F_PREALLOCATE */ + /* List of functions */ static PyMethodDef fcntl_methods[] = { @@ -509,6 +562,7 @@ static PyMethodDef fcntl_methods[] = { FCNTL_IOCTL_METHODDEF FCNTL_FLOCK_METHODDEF FCNTL_LOCKF_METHODDEF + FCNTL_PREALLOCATE_METHODDEF {NULL, NULL} /* sentinel */ }; @@ -687,6 +741,24 @@ 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 +#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