From f1e2b972165efc610a9e3d0f991553bc966e8f20 Mon Sep 17 00:00:00 2001 From: AN Long Date: Fri, 10 Oct 2025 01:01:52 +0900 Subject: [PATCH 1/7] Support F_PREALLOCATE in fcntl --- Doc/library/fcntl.rst | 52 ++++++++++++++ Lib/test/test_fcntl.py | 16 +++++ Modules/fcntlmodule.c | 150 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 5c078df44ffa8b..983cb684ac5a83 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`` constant + and related constants (``F_ALLOCATECONTIG``, ``F_ALLOCATEALL``, + ``F_ALLOCATEPERSIST``, ``F_PEOFPOSMODE``, ``F_VOLPOSMODE``) for file + preallocation operations. The module also provides the :class:`fstore` type + for use with the ``F_PREALLOCATE`` command. + The module defines the following functions: @@ -248,6 +255,51 @@ The module defines the following functions: .. audit-event:: fcntl.lockf fd,cmd,len,start,whence fcntl.lockf + +.. class:: fstore(flags=0, posmode=0, offset=0, length=0) + + A Python type that wraps the ``struct fstore`` C structure used with the + ``F_PREALLOCATE`` command on macOS. This type implements the buffer protocol, + allowing it to be used directly with :func:`fcntl`. + + .. attribute:: flags + + Allocation flags. Can be one of: + + * :const:`F_ALLOCATECONTIG` - Allocate contiguous space + * :const:`F_ALLOCATEALL` - Allocate all requested space + * :const:`F_ALLOCATEPERSIST` - Make the allocation persistent + + .. attribute:: posmode + + Position mode. Can be one of: + + * :const:`F_PEOFPOSMODE` - Allocate relative to the physical end of file + * :const:`F_VOLPOSMODE` - Allocate relative to volume position + + .. attribute:: offset + + File offset for the allocation. + + .. attribute:: length + + Length of space to allocate. + + .. attribute:: bytesalloc + + Number of bytes actually allocated (read-only). This field is populated + by the system after the F_PREALLOCATE operation. + + .. staticmethod:: frombytes(data) + + Create an :class:`fstore` instance from bytes data. + + This is useful for creating an fstore instance from the result of + :func:`fcntl.fcntl` when using the F_PREALLOCATE command. + + .. versionadded:: next + + 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..0bbf7e585e7f0d 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -288,6 +288,22 @@ def test_bad_fd(self): with self.assertRaises(OSError): fcntl.fcntl(fd, fcntl.F_DUPFD, b'\0' * 2048) + @unittest.skipUnless(hasattr(fcntl, 'F_PREALLOCATE'), 'need F_PREALLOCATE') + def test_fcntl_preallocate(self): + self.f = open(TESTFN, 'wb+') + fd = self.f.fileno() + + fs = fcntl.fstore( + flags=fcntl.F_ALLOCATECONTIG | fcntl.F_ALLOCATEALL | fcntl.F_ALLOCATEPERSIST, + posmode=fcntl.F_PEOFPOSMODE, + offset=0, + length=1024 + ) + + bs = fcntl.fcntl(fd, fcntl.F_PREALLOCATE, fs) + result = fcntl.fstore.frombytes(bs) + self.assertEqual(result.bytesalloc, 1024) + if __name__ == '__main__': unittest.main() diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index df2c9994127997..8cdd0d8a01a233 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -11,6 +11,13 @@ #include // fcntl() #include // memcpy() #include // ioctl() + +#ifdef F_PREALLOCATE +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include /* for offsetof */ +#endif #ifdef HAVE_SYS_FILE_H # include // flock() #endif @@ -502,6 +509,119 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, Py_RETURN_NONE; } +#ifdef F_PREALLOCATE + +typedef struct { + PyObject_HEAD + struct fstore fstore; +} fstoreObject; + +static int +fstore_init(fstoreObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = { + "flags", "posmode", "offset", "length", NULL + }; + int flags = 0; + int posmode = 0; + off_t offset = 0; + off_t length = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiLL", kwlist, + &flags, &posmode, &offset, &length)) { + return -1; + } + + memset(&self->fstore, 0, sizeof(struct fstore)); + self->fstore.fst_flags = flags; + self->fstore.fst_posmode = posmode; + self->fstore.fst_offset = offset; + self->fstore.fst_length = length; + + return 0; +} + +static Py_ssize_t +fstore_getbuffer(fstoreObject *self, Py_buffer *view, int flags) +{ + return PyBuffer_FillInfo(view, (PyObject *)self, (void *)&self->fstore, + sizeof(struct fstore), 1, flags); +} + +static PyObject * +fstore_frombytes(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"data", NULL}; + PyObject *data_obj; + Py_buffer view; + fstoreObject *self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &data_obj)) { + return NULL; + } + + if (PyObject_GetBuffer(data_obj, &view, PyBUF_SIMPLE) < 0) { + return NULL; + } + + if (view.len != sizeof(struct fstore)) { + PyBuffer_Release(&view); + PyErr_Format(PyExc_ValueError, + "data must be exactly %zu bytes, got %zd bytes", + sizeof(struct fstore), view.len); + return NULL; + } + + self = (fstoreObject *)PyType_GenericNew(type, NULL, NULL); + if (self == NULL) { + PyBuffer_Release(&view); + return NULL; + } + + memcpy(&self->fstore, view.buf, sizeof(struct fstore)); + PyBuffer_Release(&view); + + return (PyObject *)self; +} + +static PyMethodDef fstore_methods[] = { + {"frombytes", (PyCFunction)fstore_frombytes, METH_VARARGS | METH_KEYWORDS | METH_CLASS, + "Create an fstore instance from bytes data."}, + {NULL, NULL} +}; + +static PyMemberDef fstore_members[] = { + {"flags", Py_T_INT, offsetof(fstoreObject, fstore.fst_flags), 0, + "Allocation flags"}, + {"posmode", Py_T_INT, offsetof(fstoreObject, fstore.fst_posmode), 0, + "Position mode"}, + {"offset", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_offset), 0, + "File offset"}, + {"length", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_length), 0, + "Length to allocate"}, + {"bytesalloc", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_bytesalloc), Py_READONLY, + "Number of bytes actually allocated"}, + {NULL}, +}; + +static PyType_Slot fstore_slots[] = { + {Py_tp_init, (initproc)fstore_init}, + {Py_tp_members, fstore_members}, + {Py_tp_methods, fstore_methods}, + {Py_tp_doc, "fstore structure for F_PREALLOCATE"}, + {Py_bf_getbuffer, (getbufferproc)fstore_getbuffer}, + {0, NULL}, +}; + +static PyType_Spec fstore_spec = { + .name = "fcntl.fstore", + .basicsize = sizeof(fstoreObject), + .flags = Py_TPFLAGS_DEFAULT, + .slots = fstore_slots, +}; + +#endif /* F_PREALLOCATE */ + /* List of functions */ static PyMethodDef fcntl_methods[] = { @@ -687,6 +807,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 @@ -808,6 +946,18 @@ fcntl_exec(PyObject *module) if (all_ins(module) < 0) { return -1; } + +#ifdef F_PREALLOCATE + PyObject *fstore_type = PyType_FromSpec(&fstore_spec); + if (fstore_type == NULL) { + return -1; + } + if (PyModule_AddObject(module, "fstore", fstore_type) < 0) { + Py_DECREF(fstore_type); + return -1; + } +#endif + return 0; } From 5bbff3a5aadee03950fee0552c901e0e5ace90af Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 11 Oct 2025 17:23:07 +0900 Subject: [PATCH 2/7] Rename frombytes to from_buffer, and change to use AC --- Doc/library/fcntl.rst | 2 +- Lib/test/test_fcntl.py | 2 +- Modules/clinic/fcntlmodule.c.h | 47 +++++++++++++++++++++++++++++++++- Modules/fcntlmodule.c | 44 ++++++++++++++++--------------- 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 983cb684ac5a83..3b59e5d10a8d08 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -290,7 +290,7 @@ The module defines the following functions: Number of bytes actually allocated (read-only). This field is populated by the system after the F_PREALLOCATE operation. - .. staticmethod:: frombytes(data) + .. staticmethod:: from_buffer(data) Create an :class:`fstore` instance from bytes data. diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 0bbf7e585e7f0d..4fac31584498e2 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -301,7 +301,7 @@ def test_fcntl_preallocate(self): ) bs = fcntl.fcntl(fd, fcntl.F_PREALLOCATE, fs) - result = fcntl.fstore.frombytes(bs) + result = fcntl.fstore.from_buffer(bs) self.assertEqual(result.bytesalloc, 1024) diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 2b61d9f87083f0..905559ba50b1c5 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -265,4 +265,49 @@ 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_fstore_from_buffer__doc__, +"from_buffer($type, data, /)\n" +"--\n" +"\n" +"Create an fstore instance from bytes data.\n" +"\n" +"This is useful for creating an fstore instance from the result of\n" +"fcntl.fcntl when using the F_PREALLOCATE command.\n" +"\n" +"Raises ValueError if the data is not exactly the size of struct fstore."); + +#define FCNTL_FSTORE_FROM_BUFFER_METHODDEF \ + {"from_buffer", (PyCFunction)fcntl_fstore_from_buffer, METH_O|METH_CLASS, fcntl_fstore_from_buffer__doc__}, + +static PyObject * +fcntl_fstore_from_buffer_impl(PyTypeObject *type, Py_buffer *data); + +static PyObject * +fcntl_fstore_from_buffer(PyObject *type, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_buffer data = {NULL, NULL}; + + if (PyObject_GetBuffer(arg, &data, PyBUF_SIMPLE) != 0) { + goto exit; + } + return_value = fcntl_fstore_from_buffer_impl((PyTypeObject *)type, &data); + +exit: + /* Cleanup for data */ + if (data.obj) { + PyBuffer_Release(&data); + } + + return return_value; +} + +#endif /* defined(F_PREALLOCATE) */ + +#ifndef FCNTL_FSTORE_FROM_BUFFER_METHODDEF + #define FCNTL_FSTORE_FROM_BUFFER_METHODDEF +#endif /* !defined(FCNTL_FSTORE_FROM_BUFFER_METHODDEF) */ +/*[clinic end generated code: output=5ed3d5a284b060f6 input=a9049054013a1b77]*/ diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 8cdd0d8a01a233..38163a16ce5f68 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -34,8 +34,9 @@ static const char guard[GUARDSZ] _Py_NONSTRING = "\x00\xfa\x69\xc4\x67\xa3\x6c\x /*[clinic input] module fcntl +class fcntl.fstore "fstoreObject *" "&fstore_type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=124b58387c158179]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=1fbf02539f611b1d]*/ #include "clinic/fcntlmodule.c.h" @@ -548,45 +549,46 @@ fstore_getbuffer(fstoreObject *self, Py_buffer *view, int flags) sizeof(struct fstore), 1, flags); } +/*[clinic input] +@classmethod +fcntl.fstore.from_buffer + + data: Py_buffer + / + +Create an fstore instance from bytes data. + +This is useful for creating an fstore instance from the result of +fcntl.fcntl when using the F_PREALLOCATE command. + +Raises ValueError if the data is not exactly the size of struct fstore. +[clinic start generated code]*/ + static PyObject * -fstore_frombytes(PyTypeObject *type, PyObject *args, PyObject *kwds) +fcntl_fstore_from_buffer_impl(PyTypeObject *type, Py_buffer *data) +/*[clinic end generated code: output=e401a2f775342265 input=3148957e92e9570f]*/ { - static char *kwlist[] = {"data", NULL}; - PyObject *data_obj; - Py_buffer view; fstoreObject *self; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &data_obj)) { - return NULL; - } - - if (PyObject_GetBuffer(data_obj, &view, PyBUF_SIMPLE) < 0) { - return NULL; - } - - if (view.len != sizeof(struct fstore)) { - PyBuffer_Release(&view); + if (data->len != sizeof(struct fstore)) { PyErr_Format(PyExc_ValueError, "data must be exactly %zu bytes, got %zd bytes", - sizeof(struct fstore), view.len); + sizeof(struct fstore), data->len); return NULL; } self = (fstoreObject *)PyType_GenericNew(type, NULL, NULL); if (self == NULL) { - PyBuffer_Release(&view); return NULL; } - memcpy(&self->fstore, view.buf, sizeof(struct fstore)); - PyBuffer_Release(&view); + memcpy(&self->fstore, data->buf, sizeof(struct fstore)); return (PyObject *)self; } static PyMethodDef fstore_methods[] = { - {"frombytes", (PyCFunction)fstore_frombytes, METH_VARARGS | METH_KEYWORDS | METH_CLASS, - "Create an fstore instance from bytes data."}, + FCNTL_FSTORE_FROM_BUFFER_METHODDEF {NULL, NULL} }; From d82cf678d8eaf1af6f109e0c5e6fa19b2155e584 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 11 Oct 2025 19:39:25 +0900 Subject: [PATCH 3/7] Convert fstore constuctor to use AC --- Modules/clinic/fcntlmodule.c.h | 109 ++++++++++++++++++++++++++++++++- Modules/fcntlmodule.c | 69 +++++++++++---------- 2 files changed, 145 insertions(+), 33 deletions(-) diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 905559ba50b1c5..9a77939ed35770 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -2,6 +2,10 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(fcntl_fcntl__doc__, @@ -268,6 +272,109 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #if defined(F_PREALLOCATE) +PyDoc_STRVAR(fcntl_fstore___init____doc__, +"fstore(flags=0, posmode=0, offset=0, length=0)\n" +"--\n" +"\n" +"Initialize an fstore structure.\n" +"\n" +"This structure is used with the F_PREALLOCATE command to preallocate\n" +"file space on macOS systems."); + +static int +fcntl_fstore___init___impl(FStoreObject *self, int flags, int posmode, + long offset, long length); + +static int +fcntl_fstore___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(flags), &_Py_ID(posmode), &_Py_ID(offset), &_Py_ID(length), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"flags", "posmode", "offset", "length", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "fstore", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[4]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + int flags = 0; + int posmode = 0; + long offset = 0; + long length = 0; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + /*minpos*/ 0, /*maxpos*/ 4, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (fastargs[0]) { + flags = PyLong_AsInt(fastargs[0]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (fastargs[1]) { + posmode = PyLong_AsInt(fastargs[1]); + if (posmode == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (fastargs[2]) { + offset = PyLong_AsLong(fastargs[2]); + if (offset == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + length = PyLong_AsLong(fastargs[3]); + if (length == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = fcntl_fstore___init___impl((FStoreObject *)self, flags, posmode, offset, length); + +exit: + return return_value; +} + +#endif /* defined(F_PREALLOCATE) */ + +#if defined(F_PREALLOCATE) + PyDoc_STRVAR(fcntl_fstore_from_buffer__doc__, "from_buffer($type, data, /)\n" "--\n" @@ -310,4 +417,4 @@ fcntl_fstore_from_buffer(PyObject *type, PyObject *arg) #ifndef FCNTL_FSTORE_FROM_BUFFER_METHODDEF #define FCNTL_FSTORE_FROM_BUFFER_METHODDEF #endif /* !defined(FCNTL_FSTORE_FROM_BUFFER_METHODDEF) */ -/*[clinic end generated code: output=5ed3d5a284b060f6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e6b4d62e7dcc1068 input=a9049054013a1b77]*/ diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 38163a16ce5f68..9eadb9505b9a43 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -32,11 +32,18 @@ // NUL followed by random bytes. static const char guard[GUARDSZ] _Py_NONSTRING = "\x00\xfa\x69\xc4\x67\xa3\x6c\x58"; +#ifdef F_PREALLOCATE +typedef struct { + PyObject_HEAD + struct fstore fstore; +} FStoreObject; +#endif /* F_PREALLOCATE */ + /*[clinic input] module fcntl -class fcntl.fstore "fstoreObject *" "&fstore_type" +class fcntl.fstore "FStoreObject *" "&PyType_Type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=1fbf02539f611b1d]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=54136216ba908cf8]*/ #include "clinic/fcntlmodule.c.h" @@ -512,38 +519,36 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, #ifdef F_PREALLOCATE -typedef struct { - PyObject_HEAD - struct fstore fstore; -} fstoreObject; +/*[clinic input] +fcntl.fstore.__init__ + + flags: int = 0 + posmode: int = 0 + offset: long = 0 + length: long = 0 + +Initialize an fstore structure. + +This structure is used with the F_PREALLOCATE command to preallocate +file space on macOS systems. +[clinic start generated code]*/ static int -fstore_init(fstoreObject *self, PyObject *args, PyObject *kwds) +fcntl_fstore___init___impl(FStoreObject *self, int flags, int posmode, + long offset, long length) +/*[clinic end generated code: output=4fcf4413aa57e2dd input=f4caa1fbbe7a0e32]*/ { - static char *kwlist[] = { - "flags", "posmode", "offset", "length", NULL - }; - int flags = 0; - int posmode = 0; - off_t offset = 0; - off_t length = 0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiLL", kwlist, - &flags, &posmode, &offset, &length)) { - return -1; - } - memset(&self->fstore, 0, sizeof(struct fstore)); self->fstore.fst_flags = flags; self->fstore.fst_posmode = posmode; - self->fstore.fst_offset = offset; - self->fstore.fst_length = length; + self->fstore.fst_offset = (off_t)offset; + self->fstore.fst_length = (off_t)length; return 0; } static Py_ssize_t -fstore_getbuffer(fstoreObject *self, Py_buffer *view, int flags) +fstore_getbuffer(FStoreObject *self, Py_buffer *view, int flags) { return PyBuffer_FillInfo(view, (PyObject *)self, (void *)&self->fstore, sizeof(struct fstore), 1, flags); @@ -568,7 +573,7 @@ static PyObject * fcntl_fstore_from_buffer_impl(PyTypeObject *type, Py_buffer *data) /*[clinic end generated code: output=e401a2f775342265 input=3148957e92e9570f]*/ { - fstoreObject *self; + FStoreObject *self; if (data->len != sizeof(struct fstore)) { PyErr_Format(PyExc_ValueError, @@ -577,7 +582,7 @@ fcntl_fstore_from_buffer_impl(PyTypeObject *type, Py_buffer *data) return NULL; } - self = (fstoreObject *)PyType_GenericNew(type, NULL, NULL); + self = (FStoreObject *)PyType_GenericNew(type, NULL, NULL); if (self == NULL) { return NULL; } @@ -593,21 +598,21 @@ static PyMethodDef fstore_methods[] = { }; static PyMemberDef fstore_members[] = { - {"flags", Py_T_INT, offsetof(fstoreObject, fstore.fst_flags), 0, + {"flags", Py_T_INT, offsetof(FStoreObject, fstore.fst_flags), 0, "Allocation flags"}, - {"posmode", Py_T_INT, offsetof(fstoreObject, fstore.fst_posmode), 0, + {"posmode", Py_T_INT, offsetof(FStoreObject, fstore.fst_posmode), 0, "Position mode"}, - {"offset", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_offset), 0, + {"offset", Py_T_LONGLONG, offsetof(FStoreObject, fstore.fst_offset), 0, "File offset"}, - {"length", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_length), 0, + {"length", Py_T_LONGLONG, offsetof(FStoreObject, fstore.fst_length), 0, "Length to allocate"}, - {"bytesalloc", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_bytesalloc), Py_READONLY, + {"bytesalloc", Py_T_LONGLONG, offsetof(FStoreObject, fstore.fst_bytesalloc), Py_READONLY, "Number of bytes actually allocated"}, {NULL}, }; static PyType_Slot fstore_slots[] = { - {Py_tp_init, (initproc)fstore_init}, + {Py_tp_init, (initproc)fcntl_fstore___init__}, {Py_tp_members, fstore_members}, {Py_tp_methods, fstore_methods}, {Py_tp_doc, "fstore structure for F_PREALLOCATE"}, @@ -617,7 +622,7 @@ static PyType_Slot fstore_slots[] = { static PyType_Spec fstore_spec = { .name = "fcntl.fstore", - .basicsize = sizeof(fstoreObject), + .basicsize = sizeof(FStoreObject), .flags = Py_TPFLAGS_DEFAULT, .slots = fstore_slots, }; From ffbe2511db44f6d37eec015ffbe595dec1def350 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 11 Oct 2025 19:54:49 +0900 Subject: [PATCH 4/7] Add news entry --- .../next/Library/2025-10-11-19-40-33.gh-issue-113092.belDmD.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-10-11-19-40-33.gh-issue-113092.belDmD.rst diff --git a/Misc/NEWS.d/next/Library/2025-10-11-19-40-33.gh-issue-113092.belDmD.rst b/Misc/NEWS.d/next/Library/2025-10-11-19-40-33.gh-issue-113092.belDmD.rst new file mode 100644 index 00000000000000..69feeba6d5c59d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-11-19-40-33.gh-issue-113092.belDmD.rst @@ -0,0 +1 @@ +Add ``fstore`` type, ``F_PREALLOCATE`` and related constants support to the :mod:`fcntl` module From 1b9a7231510e75fddc777674b11124ad1134354b Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 11 Oct 2025 19:57:39 +0900 Subject: [PATCH 5/7] Fix imports --- Modules/fcntlmodule.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 9eadb9505b9a43..3d2458ca8220d7 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -13,10 +13,7 @@ #include // ioctl() #ifdef F_PREALLOCATE -#ifdef HAVE_SYS_TYPES_H -#include -#endif -#include /* for offsetof */ +# include // offsetof() #endif #ifdef HAVE_SYS_FILE_H # include // flock() From d30636352085859fa10e355fe65813b36fc1015a Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 11 Oct 2025 20:37:38 +0900 Subject: [PATCH 6/7] Fix linter errors --- Include/internal/pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 1 + Include/internal/pycore_unicodeobject_generated.h | 4 ++++ Modules/fcntlmodule.c | 2 +- 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 1f6b27b14d074b..4e7aa64b03af0d 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1962,6 +1962,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pos1)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pos2)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(posix)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(posmode)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(prec)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(preserve_exc)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(print_file_and_line)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 6959343947c1f4..b179e0c479c9ed 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -685,6 +685,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(pos1) STRUCT_FOR_ID(pos2) STRUCT_FOR_ID(posix) + STRUCT_FOR_ID(posmode) STRUCT_FOR_ID(prec) STRUCT_FOR_ID(preserve_exc) STRUCT_FOR_ID(print_file_and_line) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index be4eae42b5de1b..34ebaaf995ef38 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1960,6 +1960,7 @@ extern "C" { INIT_ID(pos1), \ INIT_ID(pos2), \ INIT_ID(posix), \ + INIT_ID(posmode), \ INIT_ID(prec), \ INIT_ID(preserve_exc), \ INIT_ID(print_file_and_line), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 45b00a20a07dda..eaf73dcd2414f1 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2528,6 +2528,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(posmode); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(prec); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 3d2458ca8220d7..8bb047c87803c3 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -544,7 +544,7 @@ fcntl_fstore___init___impl(FStoreObject *self, int flags, int posmode, return 0; } -static Py_ssize_t +static int fstore_getbuffer(FStoreObject *self, Py_buffer *view, int flags) { return PyBuffer_FillInfo(view, (PyObject *)self, (void *)&self->fstore, From bfb49eb4396c3abf3c51ebdd7215a04b055f1a42 Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 11 Oct 2025 20:40:11 +0900 Subject: [PATCH 7/7] Update document --- Doc/library/fcntl.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 3b59e5d10a8d08..3981758bee9db1 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -266,16 +266,16 @@ The module defines the following functions: Allocation flags. Can be one of: - * :const:`F_ALLOCATECONTIG` - Allocate contiguous space - * :const:`F_ALLOCATEALL` - Allocate all requested space - * :const:`F_ALLOCATEPERSIST` - Make the allocation persistent + * :const:`!F_ALLOCATECONTIG` - Allocate contiguous space + * :const:`!F_ALLOCATEALL` - Allocate all requested space + * :const:`!F_ALLOCATEPERSIST` - Make the allocation persistent .. attribute:: posmode Position mode. Can be one of: - * :const:`F_PEOFPOSMODE` - Allocate relative to the physical end of file - * :const:`F_VOLPOSMODE` - Allocate relative to volume position + * :const:`!F_PEOFPOSMODE` - Allocate relative to the physical end of file + * :const:`!F_VOLPOSMODE` - Allocate relative to volume position .. attribute:: offset @@ -297,6 +297,8 @@ The module defines the following functions: This is useful for creating an fstore instance from the result of :func:`fcntl.fcntl` when using the F_PREALLOCATE command. + .. availability:: macOS + .. versionadded:: next