Skip to content

Commit f1e2b97

Browse files
committed
Support F_PREALLOCATE in fcntl
1 parent 8b9606a commit f1e2b97

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

Doc/library/fcntl.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ descriptor.
8383
On Linux >= 6.1, the :mod:`!fcntl` module exposes the ``F_DUPFD_QUERY``
8484
to query a file descriptor pointing to the same file.
8585

86+
.. versionchanged:: next
87+
On macOS, the :mod:`!fcntl` module exposes the ``F_PREALLOCATE`` constant
88+
and related constants (``F_ALLOCATECONTIG``, ``F_ALLOCATEALL``,
89+
``F_ALLOCATEPERSIST``, ``F_PEOFPOSMODE``, ``F_VOLPOSMODE``) for file
90+
preallocation operations. The module also provides the :class:`fstore` type
91+
for use with the ``F_PREALLOCATE`` command.
92+
8693
The module defines the following functions:
8794

8895

@@ -248,6 +255,51 @@ The module defines the following functions:
248255

249256
.. audit-event:: fcntl.lockf fd,cmd,len,start,whence fcntl.lockf
250257

258+
259+
.. class:: fstore(flags=0, posmode=0, offset=0, length=0)
260+
261+
A Python type that wraps the ``struct fstore`` C structure used with the
262+
``F_PREALLOCATE`` command on macOS. This type implements the buffer protocol,
263+
allowing it to be used directly with :func:`fcntl`.
264+
265+
.. attribute:: flags
266+
267+
Allocation flags. Can be one of:
268+
269+
* :const:`F_ALLOCATECONTIG` - Allocate contiguous space
270+
* :const:`F_ALLOCATEALL` - Allocate all requested space
271+
* :const:`F_ALLOCATEPERSIST` - Make the allocation persistent
272+
273+
.. attribute:: posmode
274+
275+
Position mode. Can be one of:
276+
277+
* :const:`F_PEOFPOSMODE` - Allocate relative to the physical end of file
278+
* :const:`F_VOLPOSMODE` - Allocate relative to volume position
279+
280+
.. attribute:: offset
281+
282+
File offset for the allocation.
283+
284+
.. attribute:: length
285+
286+
Length of space to allocate.
287+
288+
.. attribute:: bytesalloc
289+
290+
Number of bytes actually allocated (read-only). This field is populated
291+
by the system after the F_PREALLOCATE operation.
292+
293+
.. staticmethod:: frombytes(data)
294+
295+
Create an :class:`fstore` instance from bytes data.
296+
297+
This is useful for creating an fstore instance from the result of
298+
:func:`fcntl.fcntl` when using the F_PREALLOCATE command.
299+
300+
.. versionadded:: next
301+
302+
251303
Examples (all on a SVR4 compliant system)::
252304

253305
import struct, fcntl, os

Lib/test/test_fcntl.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,22 @@ def test_bad_fd(self):
288288
with self.assertRaises(OSError):
289289
fcntl.fcntl(fd, fcntl.F_DUPFD, b'\0' * 2048)
290290

291+
@unittest.skipUnless(hasattr(fcntl, 'F_PREALLOCATE'), 'need F_PREALLOCATE')
292+
def test_fcntl_preallocate(self):
293+
self.f = open(TESTFN, 'wb+')
294+
fd = self.f.fileno()
295+
296+
fs = fcntl.fstore(
297+
flags=fcntl.F_ALLOCATECONTIG | fcntl.F_ALLOCATEALL | fcntl.F_ALLOCATEPERSIST,
298+
posmode=fcntl.F_PEOFPOSMODE,
299+
offset=0,
300+
length=1024
301+
)
302+
303+
bs = fcntl.fcntl(fd, fcntl.F_PREALLOCATE, fs)
304+
result = fcntl.fstore.frombytes(bs)
305+
self.assertEqual(result.bytesalloc, 1024)
306+
291307

292308
if __name__ == '__main__':
293309
unittest.main()

Modules/fcntlmodule.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
#include <fcntl.h> // fcntl()
1212
#include <string.h> // memcpy()
1313
#include <sys/ioctl.h> // ioctl()
14+
15+
#ifdef F_PREALLOCATE
16+
#ifdef HAVE_SYS_TYPES_H
17+
#include <sys/types.h>
18+
#endif
19+
#include <stddef.h> /* for offsetof */
20+
#endif
1421
#ifdef HAVE_SYS_FILE_H
1522
# include <sys/file.h> // flock()
1623
#endif
@@ -502,6 +509,119 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
502509
Py_RETURN_NONE;
503510
}
504511

512+
#ifdef F_PREALLOCATE
513+
514+
typedef struct {
515+
PyObject_HEAD
516+
struct fstore fstore;
517+
} fstoreObject;
518+
519+
static int
520+
fstore_init(fstoreObject *self, PyObject *args, PyObject *kwds)
521+
{
522+
static char *kwlist[] = {
523+
"flags", "posmode", "offset", "length", NULL
524+
};
525+
int flags = 0;
526+
int posmode = 0;
527+
off_t offset = 0;
528+
off_t length = 0;
529+
530+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiLL", kwlist,
531+
&flags, &posmode, &offset, &length)) {
532+
return -1;
533+
}
534+
535+
memset(&self->fstore, 0, sizeof(struct fstore));
536+
self->fstore.fst_flags = flags;
537+
self->fstore.fst_posmode = posmode;
538+
self->fstore.fst_offset = offset;
539+
self->fstore.fst_length = length;
540+
541+
return 0;
542+
}
543+
544+
static Py_ssize_t
545+
fstore_getbuffer(fstoreObject *self, Py_buffer *view, int flags)
546+
{
547+
return PyBuffer_FillInfo(view, (PyObject *)self, (void *)&self->fstore,
548+
sizeof(struct fstore), 1, flags);
549+
}
550+
551+
static PyObject *
552+
fstore_frombytes(PyTypeObject *type, PyObject *args, PyObject *kwds)
553+
{
554+
static char *kwlist[] = {"data", NULL};
555+
PyObject *data_obj;
556+
Py_buffer view;
557+
fstoreObject *self;
558+
559+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &data_obj)) {
560+
return NULL;
561+
}
562+
563+
if (PyObject_GetBuffer(data_obj, &view, PyBUF_SIMPLE) < 0) {
564+
return NULL;
565+
}
566+
567+
if (view.len != sizeof(struct fstore)) {
568+
PyBuffer_Release(&view);
569+
PyErr_Format(PyExc_ValueError,
570+
"data must be exactly %zu bytes, got %zd bytes",
571+
sizeof(struct fstore), view.len);
572+
return NULL;
573+
}
574+
575+
self = (fstoreObject *)PyType_GenericNew(type, NULL, NULL);
576+
if (self == NULL) {
577+
PyBuffer_Release(&view);
578+
return NULL;
579+
}
580+
581+
memcpy(&self->fstore, view.buf, sizeof(struct fstore));
582+
PyBuffer_Release(&view);
583+
584+
return (PyObject *)self;
585+
}
586+
587+
static PyMethodDef fstore_methods[] = {
588+
{"frombytes", (PyCFunction)fstore_frombytes, METH_VARARGS | METH_KEYWORDS | METH_CLASS,
589+
"Create an fstore instance from bytes data."},
590+
{NULL, NULL}
591+
};
592+
593+
static PyMemberDef fstore_members[] = {
594+
{"flags", Py_T_INT, offsetof(fstoreObject, fstore.fst_flags), 0,
595+
"Allocation flags"},
596+
{"posmode", Py_T_INT, offsetof(fstoreObject, fstore.fst_posmode), 0,
597+
"Position mode"},
598+
{"offset", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_offset), 0,
599+
"File offset"},
600+
{"length", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_length), 0,
601+
"Length to allocate"},
602+
{"bytesalloc", Py_T_LONGLONG, offsetof(fstoreObject, fstore.fst_bytesalloc), Py_READONLY,
603+
"Number of bytes actually allocated"},
604+
{NULL},
605+
};
606+
607+
static PyType_Slot fstore_slots[] = {
608+
{Py_tp_init, (initproc)fstore_init},
609+
{Py_tp_members, fstore_members},
610+
{Py_tp_methods, fstore_methods},
611+
{Py_tp_doc, "fstore structure for F_PREALLOCATE"},
612+
{Py_bf_getbuffer, (getbufferproc)fstore_getbuffer},
613+
{0, NULL},
614+
};
615+
616+
static PyType_Spec fstore_spec = {
617+
.name = "fcntl.fstore",
618+
.basicsize = sizeof(fstoreObject),
619+
.flags = Py_TPFLAGS_DEFAULT,
620+
.slots = fstore_slots,
621+
};
622+
623+
#endif /* F_PREALLOCATE */
624+
505625
/* List of functions */
506626

507627
static PyMethodDef fcntl_methods[] = {
@@ -687,6 +807,24 @@ all_ins(PyObject* m)
687807
#ifdef F_NOCACHE
688808
if (PyModule_AddIntMacro(m, F_NOCACHE)) return -1;
689809
#endif
810+
#ifdef F_PREALLOCATE
811+
if (PyModule_AddIntMacro(m, F_PREALLOCATE)) return -1;
812+
#endif
813+
#ifdef F_ALLOCATECONTIG
814+
if (PyModule_AddIntMacro(m, F_ALLOCATECONTIG)) return -1;
815+
#endif
816+
#ifdef F_ALLOCATEALL
817+
if (PyModule_AddIntMacro(m, F_ALLOCATEALL)) return -1;
818+
#endif
819+
#ifdef F_ALLOCATEPERSIST
820+
if (PyModule_AddIntMacro(m, F_ALLOCATEPERSIST)) return -1;
821+
#endif
822+
#ifdef F_PEOFPOSMODE
823+
if (PyModule_AddIntMacro(m, F_PEOFPOSMODE)) return -1;
824+
#endif
825+
#ifdef F_VOLPOSMODE
826+
if (PyModule_AddIntMacro(m, F_VOLPOSMODE)) return -1;
827+
#endif
690828

691829
/* FreeBSD specifics */
692830
#ifdef F_DUP2FD
@@ -808,6 +946,18 @@ fcntl_exec(PyObject *module)
808946
if (all_ins(module) < 0) {
809947
return -1;
810948
}
949+
950+
#ifdef F_PREALLOCATE
951+
PyObject *fstore_type = PyType_FromSpec(&fstore_spec);
952+
if (fstore_type == NULL) {
953+
return -1;
954+
}
955+
if (PyModule_AddObject(module, "fstore", fstore_type) < 0) {
956+
Py_DECREF(fstore_type);
957+
return -1;
958+
}
959+
#endif
960+
811961
return 0;
812962
}
813963

0 commit comments

Comments
 (0)