Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Doc/library/fcntl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,38 @@ The module defines the following functions:

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

.. function:: getlk(fd, cmd, len=0, start=0, whence=0)

Like :func:`lockf`, essentially a wrapper for the ``F_GETLK`` lock operation.

*fd* is the file descriptor (file objects providing a :meth:`~io.IOBase.fileno`
method are accepted as well) of the file to retrieve lock information on,
and *cmd* specifies an operation to check for would-be conflicts on:

* :const:`LOCK_SH` -- check for conflicting exclusive lock
* :const:`LOCK_EX` -- check for conflicting shared or exclusive lock

Note that the returned information identifies a lock that would conflict
with acquiring the type of lock specified in *cmd*. Calling with
:const:`LOCK_SH` therefore only returns information on a conflicting
:const:`LOCK_EX`, while calling with :const:`LOCK_EX` returns information
on either :const:`LOCK_SH` or :const:`LOCK_EX` locks.

The *len*, *start* and *whence* parameters are as with :func:`lockf`.

Returns a tuple ``(pid, cmd, len, start, whence)`` if a conflicting lock is
found. If more than one lock conflicts, platform code chooses one. There
may not be an associated process, in which case *pid* will be -1.

If no other lock would conflict with the request, this function returns
``None``.

The information returned by this call may already be outdated by the time it
returns. Additionally, only locks acquired using :func:`lockf` (or rather
the underlying ``fcntl(F_SETLK)`` system call) are considered by this call.

.. audit-event:: fcntl.getlk fd,cmd,len,start,whence fcntl.getlk

Examples (all on a SVR4 compliant system)::

import struct, fcntl, os
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_fcntl.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,29 @@ def fileno(self):
def try_lockf_on_other_process_fail(fname, cmd):
f = open(fname, 'wb+')
try:
lockinfo = fcntl.getlk(f, cmd & ~fcntl.LOCK_NB)
fcntl.lockf(f, cmd)
except BlockingIOError:
pass
finally:
f.close()

ppid = os.getppid()
if lockinfo is None or lockinfo[0] not in {ppid, -1}:
sys.stderr.write(f"getlk: {lockinfo}, expected pid={ppid}\n")
sys.exit(1)

def try_lockf_on_other_process(fname, cmd):
f = open(fname, 'wb+')
fcntl.lockf(f, cmd)
fcntl.lockf(f, fcntl.LOCK_UN)
lockinfo = fcntl.getlk(f, cmd & ~fcntl.LOCK_NB)
f.close()

if lockinfo is not None:
sys.stderr.write(f"getlk: {lockinfo}, expected: None\n")
sys.exit(1)

class TestFcntl(unittest.TestCase):

def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The ``fcntl`` module has a new ``getlk`` wrapper to retrieve information on
conflicting locks through the fcntl(F_GETLK) call.
86 changes: 85 additions & 1 deletion Modules/clinic/fcntlmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

141 changes: 118 additions & 23 deletions Modules/fcntlmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,30 @@ fcntl_flock_impl(PyObject *module, int fd, int code)
}


static void
fill_struct_flock(struct flock *l, PyObject *lenobj, PyObject *startobj)
{
l->l_start = l->l_len = 0;
if (startobj != NULL) {
#if !defined(HAVE_LARGEFILE_SUPPORT)
l->l_start = PyLong_AsLong(startobj);
#else
l->l_start = PyLong_Check(startobj) ?
PyLong_AsLongLong(startobj) :
PyLong_AsLong(startobj);
#endif
}
if (lenobj != NULL) {
#if !defined(HAVE_LARGEFILE_SUPPORT)
l->l_len = PyLong_AsLong(lenobj);
#else
l->l_len = PyLong_Check(lenobj) ?
PyLong_AsLongLong(lenobj) :
PyLong_AsLong(lenobj);
#endif
}
}

/*[clinic input]
fcntl.lockf

Expand Down Expand Up @@ -405,29 +429,9 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
"unrecognized lockf argument");
return NULL;
}
l.l_start = l.l_len = 0;
if (startobj != NULL) {
#if !defined(HAVE_LARGEFILE_SUPPORT)
l.l_start = PyLong_AsLong(startobj);
#else
l.l_start = PyLong_Check(startobj) ?
PyLong_AsLongLong(startobj) :
PyLong_AsLong(startobj);
#endif
if (PyErr_Occurred())
return NULL;
}
if (lenobj != NULL) {
#if !defined(HAVE_LARGEFILE_SUPPORT)
l.l_len = PyLong_AsLong(lenobj);
#else
l.l_len = PyLong_Check(lenobj) ?
PyLong_AsLongLong(lenobj) :
PyLong_AsLong(lenobj);
#endif
if (PyErr_Occurred())
return NULL;
}
fill_struct_flock(&l, lenobj, startobj);
if (PyErr_Occurred())
return NULL;
l.l_whence = whence;
do {
Py_BEGIN_ALLOW_THREADS
Expand All @@ -441,13 +445,104 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
Py_RETURN_NONE;
}


/*[clinic input]
fcntl.getlk

fd: fildes
cmd as code: int
len as lenobj: object(c_default='NULL') = 0
start as startobj: object(c_default='NULL') = 0
whence: int = 0
/

A wrapper around the fcntl(F_GETLK) locking call.

`fd` is the file descriptor of the file whose lock status to get. `cmd`
is one of the following values:

LOCK_SH - check for conflicting exclusive lock
LOCK_EX - check for conflicting shared or exclusive lock

Note that the returned information identifies a lock that would conflict with
acquiring the type of lock specified in `cmd`. Calling with LOCK_SH therefore
only returns information on a conflicting LOCK_EX, while calling with LOCK_EX
returns information on either LOCK_SH or LOCK_EX locks.

The remaining parameters are as with `lockf`.

Returns a tuple of (pid, cmd, len, start, whence) if a conflicting lock is
found. If more than one lock conflicts, platform code chooses one. There
may not be an associated process, in which case pid will be -1.

If no other lock would conflict with the request, this function returns None.

The information returned by this call may already be outdated by the time it
returns. Additionally, only locks acquired using `lockf` (or `fcntl(F_SETLK)`)
are considered by this call.
[clinic start generated code]*/

static PyObject *
fcntl_getlk_impl(PyObject *module, int fd, int code, PyObject *lenobj,
PyObject *startobj, int whence)
/*[clinic end generated code: output=2a7ba40514c0f66b input=336315433d0b46b2]*/
{
int ret;
int async_err = 0;
struct flock l;

if (PySys_Audit("fcntl.getlk", "iiOOi", fd, code, lenobj ? lenobj : Py_None,
startobj ? startobj : Py_None, whence) < 0) {
return NULL;
}

/* LOCK_UN and combinations make no sense for F_GETLK */
if (code == LOCK_SH)
l.l_type = F_RDLCK;
else if (code == LOCK_EX)
l.l_type = F_WRLCK;
else {
PyErr_SetString(PyExc_ValueError,
"unrecognized getlk argument");
return NULL;
}
fill_struct_flock(&l, lenobj, startobj);
if (PyErr_Occurred())
return NULL;
l.l_whence = whence;
do {
Py_BEGIN_ALLOW_THREADS
ret = fcntl(fd, F_GETLK, &l);
Py_END_ALLOW_THREADS
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));

if (ret < 0) {
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
} else {
int cmd = 0;

if (l.l_type == F_UNLCK)
Py_RETURN_NONE;
else if (l.l_type == F_RDLCK)
cmd = LOCK_SH;
else if (l.l_type == F_WRLCK)
cmd = LOCK_EX;

/* casts necessary since types in OS header are unknown */
return Py_BuildValue("LiLLi", (long long)l.l_pid, cmd,
(long long)l.l_len, (long long)l.l_start,
(int)l.l_whence);
}
}

/* List of functions */

static PyMethodDef fcntl_methods[] = {
FCNTL_FCNTL_METHODDEF
FCNTL_IOCTL_METHODDEF
FCNTL_FLOCK_METHODDEF
FCNTL_LOCKF_METHODDEF
FCNTL_GETLK_METHODDEF
{NULL, NULL} /* sentinel */
};

Expand Down
Loading