Skip to content

Commit

Permalink
QLockFile/Unix: drop the use of fcntl(F_SETLK)
Browse files Browse the repository at this point in the history
F_SETLK is bad. Explanation in the comment. And flock(2) does work with
NFS on Linux, so let's just stick to that, which is simpler.

We only use the file locks when we attempt to delete an apparently stale
lock: that is, for a lock file that is at least staleLockTime old.

Change-Id: I0b48fc8e90304e0dacc3fffd14e908c8c4c9d59b
Reviewed-by: Oswald Buddenhagen <[email protected]>
Reviewed-by: David Faure <[email protected]>
  • Loading branch information
thiagomacieira committed Oct 6, 2017
1 parent de3f764 commit 3399d79
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 73 deletions.
4 changes: 0 additions & 4 deletions src/corelib/io/qlockfile_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ class QLockFilePrivate
// used in dbusmenu
Q_CORE_EXPORT static QString processNameByPid(qint64 pid);

#ifdef Q_OS_UNIX
static int checkFcntlWorksAfterFlock(const QString &fn);
#endif

QString fileName;
#ifdef Q_OS_WIN
Qt::HANDLE fileHandle;
Expand Down
113 changes: 44 additions & 69 deletions src/corelib/io/qlockfile_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,79 +94,54 @@ static qint64 qt_write_loop(int fd, const char *data, qint64 len)
return pos;
}

int QLockFilePrivate::checkFcntlWorksAfterFlock(const QString &fn)
{
#ifndef QT_NO_TEMPORARYFILE
QTemporaryFile file(fn);
if (!file.open())
return 0;
const int fd = file.d_func()->engine()->handle();
#if defined(LOCK_EX) && defined(LOCK_NB)
if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
return 0;
#endif
struct flock flockData;
flockData.l_type = F_WRLCK;
flockData.l_whence = SEEK_SET;
flockData.l_start = 0;
flockData.l_len = 0; // 0 = entire file
flockData.l_pid = getpid();
if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems
return 0;
return 1;
#else
Q_UNUSED(fn);
return 0;
#endif
}

// Cache the result of checkFcntlWorksAfterFlock for each directory a lock
// file is created in because in some filesystems, like NFS, both locks
// are the same. This does not take into account a filesystem changing.
// QCache is set to hold a maximum of 10 entries, this is to avoid unbounded
// growth, this is caching directories of files and it is assumed a low number
// will be sufficient.
typedef QCache<QString, bool> CacheType;
Q_GLOBAL_STATIC_WITH_ARGS(CacheType, fcntlOK, (10));
static QBasicMutex fcntlLock;
/*
* Details about file locking on Unix.
*
* There are three types of advisory locks on Unix systems:
* 1) POSIX process-wide locks using fcntl(F_SETLK)
* 2) BSD flock(2) system call
* 3) Linux-specific file descriptor locks using fcntl(F_OFD_SETLK)
* There's also a mandatory locking feature by POSIX, which is deprecated on
* Linux and users are advised not to use it.
*
* The first problem is that the POSIX API is braindead. POSIX.1-2008 says:
*
* All locks associated with a file for a given process shall be removed when
* a file descriptor for that file is closed by that process or the process
* holding that file descriptor terminates.
*
* The Linux manpage is clearer:
*
* * If a process closes _any_ file descriptor referring to a file, then all
* of the process's locks on that file are released, regardless of the file
* descriptor(s) on which the locks were obtained. This is bad: [...]
*
* * The threads in a process share locks. In other words, a multithreaded
* program can't use record locking to ensure that threads don't
* simultaneously access the same region of a file.
*
* So in order to use POSIX locks, we'd need a global mutex that stays locked
* while the QLockFile is locked. For that reason, Qt does not use POSIX
* advisory locks anymore.
*
* The next problem is that POSIX leaves undefined the relationship between
* locks with fcntl(), flock() and lockf(). In some systems (like the BSDs),
* all three use the same record set, while on others (like Linux) the locks
* are independent, except if locking over NFS mounts, in which case they're
* actually the same. Therefore, it's a very bad idea to mix them in the same
* process.
*
* We therefore use only flock(2).
*/

/*!
\internal
Checks that the OS isn't using POSIX locks to emulate flock().
\macos is one of those.
*/
static bool fcntlWorksAfterFlock(const QString &fn)
{
QMutexLocker lock(&fcntlLock);
if (fcntlOK.isDestroyed())
return QLockFilePrivate::checkFcntlWorksAfterFlock(fn);
bool *worksPtr = fcntlOK->object(fn);
if (worksPtr)
return *worksPtr;

const bool val = QLockFilePrivate::checkFcntlWorksAfterFlock(fn);
worksPtr = new bool(val);
fcntlOK->insert(fn, worksPtr);

return val;
}

static bool setNativeLocks(const QString &fileName, int fd)
static bool setNativeLocks(int fd)
{
#if defined(LOCK_EX) && defined(LOCK_NB)
if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
return false;
#else
Q_UNUSED(fd);
#endif
struct flock flockData;
flockData.l_type = F_WRLCK;
flockData.l_whence = SEEK_SET;
flockData.l_start = 0;
flockData.l_len = 0; // 0 = entire file
flockData.l_pid = getpid();
if (fcntlWorksAfterFlock(QDir::cleanPath(QFileInfo(fileName).absolutePath()) + QString('/'))
&& fcntl(fd, F_SETLK, &flockData) == -1) { // for networked filesystems
return false;
}
return true;
}

Expand All @@ -193,7 +168,7 @@ QLockFile::LockError QLockFilePrivate::tryLock_sys()
}
}
// Ensure nobody else can delete the file while we have it
if (!setNativeLocks(fileName, fd)) {
if (!setNativeLocks(fd)) {
const int errnoSaved = errno;
qWarning() << "setNativeLocks failed:" << qt_error_string(errnoSaved);
}
Expand Down Expand Up @@ -224,7 +199,7 @@ bool QLockFilePrivate::removeStaleLock()
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0666);
if (fd < 0) // gone already?
return false;
bool success = setNativeLocks(fileName, fd) && (::unlink(lockFileName) == 0);
bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
close(fd);
return success;
}
Expand Down

0 comments on commit 3399d79

Please sign in to comment.