Skip to content

Commit 3763cce

Browse files
committed
gh-96694: add F_GETLK wrapper in fcntl module
1 parent 5d91cf6 commit 3763cce

5 files changed

Lines changed: 249 additions & 24 deletions

File tree

Doc/library/fcntl.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,38 @@ The module defines the following functions:
245245

246246
.. audit-event:: fcntl.lockf fd,cmd,len,start,whence fcntl.lockf
247247

248+
.. function:: getlk(fd, cmd, len=0, start=0, whence=0)
249+
250+
Like :func:`lockf`, essentially a wrapper for the ``F_GETLK`` lock operation.
251+
252+
*fd* is the file descriptor (file objects providing a :meth:`~io.IOBase.fileno`
253+
method are accepted as well) of the file to retrieve lock information on,
254+
and *cmd* specifies an operation to check for would-be conflicts on:
255+
256+
* :const:`LOCK_SH` -- check for conflicting exclusive lock
257+
* :const:`LOCK_EX` -- check for conflicting shared or exclusive lock
258+
259+
Note that the returned information identifies a lock that would conflict
260+
with acquiring the type of lock specified in *cmd*. Calling with
261+
:const:`LOCK_SH` therefore only returns information on a conflicting
262+
:const:`LOCK_EX`, while calling with :const:`LOCK_EX` returns information
263+
on either :const:`LOCK_SH` or :const:`LOCK_EX` locks.
264+
265+
The *len*, *start* and *whence* parameters are as with :func:`lockf`.
266+
267+
Returns a tuple ``(pid, cmd, len, start, whence)`` if a conflicting lock is
268+
found. If more than one lock conflicts, platform code chooses one. There
269+
may not be an associated process, in which case *pid* will be -1.
270+
271+
If no other lock would conflict with the request, this function returns
272+
``None``.
273+
274+
The information returned by this call may already be outdated by the time it
275+
returns. Additionally, only locks acquired using :func:`lockf` (or rather
276+
the underlying ``fcntl(F_SETLK)`` system call) are considered by this call.
277+
278+
.. audit-event:: fcntl.getlk fd,cmd,len,start,whence fcntl.getlk
279+
248280
Examples (all on a SVR4 compliant system)::
249281

250282
import struct, fcntl, os

Lib/test/test_fcntl.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,29 @@ def fileno(self):
2828
def try_lockf_on_other_process_fail(fname, cmd):
2929
f = open(fname, 'wb+')
3030
try:
31+
lockinfo = fcntl.getlk(f, cmd & ~fcntl.LOCK_NB)
3132
fcntl.lockf(f, cmd)
3233
except BlockingIOError:
3334
pass
3435
finally:
3536
f.close()
3637

38+
ppid = os.getppid()
39+
if lockinfo is None or lockinfo[0] not in {ppid, -1}:
40+
sys.stderr.write(f"getlk: {lockinfo}, expected pid={ppid}\n")
41+
sys.exit(1)
42+
3743
def try_lockf_on_other_process(fname, cmd):
3844
f = open(fname, 'wb+')
3945
fcntl.lockf(f, cmd)
4046
fcntl.lockf(f, fcntl.LOCK_UN)
47+
lockinfo = fcntl.getlk(f, cmd & ~fcntl.LOCK_NB)
4148
f.close()
4249

50+
if lockinfo is not None:
51+
sys.stderr.write(f"getlk: {lockinfo}, expected: None\n")
52+
sys.exit(1)
53+
4354
class TestFcntl(unittest.TestCase):
4455

4556
def setUp(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The ``fcntl`` module has a new ``getlk`` wrapper to retrieve information on
2+
conflicting locks through the fcntl(F_GETLK) call.

Modules/clinic/fcntlmodule.c.h

Lines changed: 83 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/fcntlmodule.c

Lines changed: 121 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,30 @@ fcntl_flock_impl(PyObject *module, int fd, int code)
423423
}
424424

425425

426+
static void
427+
fill_struct_flock(struct flock *l, PyObject *lenobj, PyObject *startobj)
428+
{
429+
l->l_start = l->l_len = 0;
430+
if (startobj != NULL) {
431+
#if !defined(HAVE_LARGEFILE_SUPPORT)
432+
l->l_start = PyLong_AsLong(startobj);
433+
#else
434+
l->l_start = PyLong_Check(startobj) ?
435+
PyLong_AsLongLong(startobj) :
436+
PyLong_AsLong(startobj);
437+
#endif
438+
}
439+
if (lenobj != NULL) {
440+
#if !defined(HAVE_LARGEFILE_SUPPORT)
441+
l->l_len = PyLong_AsLong(lenobj);
442+
#else
443+
l->l_len = PyLong_Check(lenobj) ?
444+
PyLong_AsLongLong(lenobj) :
445+
PyLong_AsLong(lenobj);
446+
#endif
447+
}
448+
}
449+
426450
/*[clinic input]
427451
fcntl.lockf
428452
@@ -489,29 +513,9 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
489513
"unrecognized lockf argument");
490514
return NULL;
491515
}
492-
l.l_start = l.l_len = 0;
493-
if (startobj != NULL) {
494-
#if !defined(HAVE_LARGEFILE_SUPPORT)
495-
l.l_start = PyLong_AsLong(startobj);
496-
#else
497-
l.l_start = PyLong_Check(startobj) ?
498-
PyLong_AsLongLong(startobj) :
499-
PyLong_AsLong(startobj);
500-
#endif
501-
if (PyErr_Occurred())
502-
return NULL;
503-
}
504-
if (lenobj != NULL) {
505-
#if !defined(HAVE_LARGEFILE_SUPPORT)
506-
l.l_len = PyLong_AsLong(lenobj);
507-
#else
508-
l.l_len = PyLong_Check(lenobj) ?
509-
PyLong_AsLongLong(lenobj) :
510-
PyLong_AsLong(lenobj);
511-
#endif
512-
if (PyErr_Occurred())
513-
return NULL;
514-
}
516+
fill_struct_flock(&l, lenobj, startobj);
517+
if (PyErr_Occurred())
518+
return NULL;
515519
l.l_whence = whence;
516520
do {
517521
Py_BEGIN_ALLOW_THREADS
@@ -525,13 +529,107 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
525529
Py_RETURN_NONE;
526530
}
527531

532+
533+
/*[clinic input]
534+
fcntl.getlk
535+
536+
fd: fildes
537+
cmd as code: int
538+
len as lenobj: object(c_default='NULL') = 0
539+
start as startobj: object(c_default='NULL') = 0
540+
whence: int = 0
541+
/
542+
543+
A wrapper around the fcntl(F_GETLK) locking call.
544+
545+
`fd` is the file descriptor of the file whose lock status to get. `cmd`
546+
is one of the following values:
547+
548+
LOCK_SH - check for conflicting exclusive lock
549+
LOCK_EX - check for conflicting shared or exclusive lock
550+
551+
Note that the returned information identifies a lock that would conflict
552+
with acquiring the type of lock specified in `cmd`. Calling with
553+
LOCK_SH therefore only returns information on a conflicting LOCK_EX,
554+
while calling with LOCK_EX returns information on either LOCK_SH or
555+
LOCK_EX locks.
556+
557+
The remaining parameters are as with `lockf`.
558+
559+
Returns a tuple of (pid, cmd, len, start, whence) if a conflicting
560+
lock is found. If more than one lock conflicts, platform code chooses
561+
one. There may not be an associated process, in which case pid will
562+
be -1.
563+
564+
If no other lock would conflict with the request, this function returns
565+
None.
566+
567+
The information returned by this call may already be outdated by the
568+
time it returns. Additionally, only locks acquired using `lockf` (or
569+
`fcntl(F_SETLK)`) are considered by this call.
570+
[clinic start generated code]*/
571+
572+
static PyObject *
573+
fcntl_getlk_impl(PyObject *module, int fd, int code, PyObject *lenobj,
574+
PyObject *startobj, int whence)
575+
/*[clinic end generated code: output=2a7ba40514c0f66b input=6c2227b45838a146]*/
576+
{
577+
int ret;
578+
int async_err = 0;
579+
struct flock l;
580+
581+
if (PySys_Audit("fcntl.getlk", "iiOOi", fd, code, lenobj ? lenobj : Py_None,
582+
startobj ? startobj : Py_None, whence) < 0) {
583+
return NULL;
584+
}
585+
586+
/* LOCK_UN and combinations make no sense for F_GETLK */
587+
if (code == LOCK_SH)
588+
l.l_type = F_RDLCK;
589+
else if (code == LOCK_EX)
590+
l.l_type = F_WRLCK;
591+
else {
592+
PyErr_SetString(PyExc_ValueError,
593+
"unrecognized getlk argument");
594+
return NULL;
595+
}
596+
fill_struct_flock(&l, lenobj, startobj);
597+
if (PyErr_Occurred())
598+
return NULL;
599+
l.l_whence = whence;
600+
do {
601+
Py_BEGIN_ALLOW_THREADS
602+
ret = fcntl(fd, F_GETLK, &l);
603+
Py_END_ALLOW_THREADS
604+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
605+
606+
if (ret < 0) {
607+
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
608+
} else {
609+
int cmd = 0;
610+
611+
if (l.l_type == F_UNLCK)
612+
Py_RETURN_NONE;
613+
else if (l.l_type == F_RDLCK)
614+
cmd = LOCK_SH;
615+
else if (l.l_type == F_WRLCK)
616+
cmd = LOCK_EX;
617+
618+
/* casts necessary since types in OS header are unknown */
619+
return Py_BuildValue("LiLLi", (long long)l.l_pid, cmd,
620+
(long long)l.l_len, (long long)l.l_start,
621+
(int)l.l_whence);
622+
}
623+
}
624+
528625
/* List of functions */
529626

530627
static PyMethodDef fcntl_methods[] = {
531628
FCNTL_FCNTL_METHODDEF
532629
FCNTL_IOCTL_METHODDEF
533630
FCNTL_FLOCK_METHODDEF
534631
FCNTL_LOCKF_METHODDEF
632+
FCNTL_GETLK_METHODDEF
535633
{NULL, NULL} /* sentinel */
536634
};
537635

0 commit comments

Comments
 (0)