summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qlockfile_unix.cpp
blob: bd0569bce2c69c202d3632d63473d458c59db733 (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
// Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>// Copyright (C) 2017 Intel Corporation.// Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"private/qlockfile_p.h"#include"QtCore/qtemporaryfile.h"#include"QtCore/qfileinfo.h"#include"QtCore/qdebug.h"#include"QtCore/qdatetime.h"#include"QtCore/qfileinfo.h"#include"QtCore/qcache.h"#include"QtCore/qglobalstatic.h"#include"QtCore/qmutex.h"#include"private/qcore_unix_p.h"// qt_safe_open#include"private/qabstractfileengine_p.h"#include"private/qfilesystementry_p.h"#include"private/qtemporaryfile_p.h"#if !defined(Q_OS_INTEGRITY)#include <sys/file.h>// flock#endif#if defined(Q_OS_RTEMS)// flock() does not work in these OSes and produce warnings when we try to use# undef LOCK_EX# undef LOCK_NB#endif#include <sys/types.h>// kill#include <signal.h>// kill#include <unistd.h>// gethostname#if defined(Q_OS_MACOS)# include <libproc.h>#elif defined(Q_OS_LINUX)# include <unistd.h># include <cstdio>#elif defined(Q_OS_HAIKU)# include <kernel/OS.h>#elif defined(Q_OS_BSD4) && !defined(QT_PLATFORM_UIKIT)# include <sys/cdefs.h># include <sys/param.h># include <sys/sysctl.h># if !defined(Q_OS_NETBSD)# include <sys/user.h># endif#endif QT_BEGIN_NAMESPACE // ### merge into qt_safe_write?static qint64 qt_write_loop(int fd,const char*data, qint64 len){ qint64 pos =0;while(pos < len) {const qint64 ret =qt_safe_write(fd, data + pos, len - pos);if(ret == -1)// e.g. partition fullreturn pos; pos += ret;}return pos;}/* * 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), except on Android. * * Android Compatibility: * Some versions of Android have known issues where flock does not function correctly.  * As a result, on Android, we use POSIX fcntl(F_SETLK) to handle file locking. * fcntl is better integrated with Android’s underlying system, avoiding  * the limitations of flock. */static boolsetNativeLocks(int fd){#if defined(Q_OS_ANDROID)struct flock fl; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start =0; fl.l_len =0;if(fcntl(fd, F_SETLK, &fl) == -1)return false;#elif defined(LOCK_EX) && defined(LOCK_NB)if(flock(fd, LOCK_EX | LOCK_NB) == -1)// other threads, and other processes on a local fsreturn false;#elseQ_UNUSED(fd);#endifreturn true;}QLockFile::LockError QLockFilePrivate::tryLock_sys(){const QByteArray lockFileName =QFile::encodeName(fileName);const int fd =qt_safe_open(lockFileName.constData(), O_RDWR | O_CREAT | O_EXCL,0666);if(fd <0) {switch(errno) {case EEXIST:returnQLockFile::LockFailedError;case EACCES:case EROFS:returnQLockFile::PermissionError;default:returnQLockFile::UnknownError;}}// Ensure nobody else can delete the file while we have itif(!setNativeLocks(fd)) {const int errnoSaved = errno;qWarning() <<"setNativeLocks failed:"<<qt_error_string(errnoSaved);} QByteArray fileData =lockFileContents();if(qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {qt_safe_close(fd);if(!QFile::remove(fileName))qWarning("QLockFile: Could not remove our own lock file %ls.",qUtf16Printable(fileName));returnQLockFile::UnknownError;// partition full}// We hold the lock, continue. fileHandle = fd;// Sync to disk if possible. Ignore errors (e.g. not supported).#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0fdatasync(fileHandle);#elsefsync(fileHandle);#endifreturnQLockFile::NoError;}boolQLockFilePrivate::removeStaleLock(){const QByteArray lockFileName =QFile::encodeName(fileName);const int fd =qt_safe_open(lockFileName.constData(), O_WRONLY,0666);if(fd <0)// gone already?return false;bool success =setNativeLocks(fd) && (::unlink(lockFileName) ==0);close(fd);return success;}boolQLockFilePrivate::isProcessRunning(qint64 pid,const QString &appname){if(::kill(pid_t(pid),0) == -1&& errno == ESRCH)return false;// PID doesn't exist anymoreconst QString processName =processNameByPid(pid);if(!processName.isEmpty()) { QFileInfo fi(appname);if(fi.isSymLink()) fi.setFile(fi.symLinkTarget());if(processName != fi.fileName())return false;// PID got reused by a different application.}return true;} QString QLockFilePrivate::processNameByPid(qint64 pid){#if defined(Q_OS_MACOS)char name[1024];proc_name(pid, name,sizeof(name) /sizeof(char));returnQFile::decodeName(name);#elif defined(Q_OS_LINUX)if(!qt_haveLinuxProcfs())returnQString();char exePath[64];sprintf(exePath,"/proc/%lld/exe", pid); QByteArray buf =qt_readlink(exePath);if(buf.isEmpty()) {// The pid is gone. Return some invalid process name to fail the test.returnQStringLiteral("/ERROR/");}// remove the " (deleted)" suffix, if anystatic const char deleted[] =" (deleted)";if(buf.endsWith(deleted)) buf.chop(strlen(deleted));returnQFileSystemEntry(buf,QFileSystemEntry::FromNativePath()).fileName();#elif defined(Q_OS_HAIKU) thread_info info;if(get_thread_info(pid, &info) != B_OK)returnQString();returnQFile::decodeName(info.name);#elif defined(Q_OS_BSD4) && !defined(QT_PLATFORM_UIKIT)# if defined(Q_OS_NETBSD)struct kinfo_proc2 kp;int mib[6] = { CTL_KERN, KERN_PROC2, KERN_PROC_PID, (int)pid,sizeof(struct kinfo_proc2),1};# elif defined(Q_OS_OPENBSD)struct kinfo_proc kp;int mib[6] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid,sizeof(struct kinfo_proc),1};# elsestruct kinfo_proc kp;int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid };# endifsize_t len =sizeof(kp); u_int mib_len =sizeof(mib)/sizeof(u_int);if(sysctl(mib, mib_len, &kp, &len, NULL,0) <0)returnQString();# if defined(Q_OS_OPENBSD) || defined(Q_OS_NETBSD)if(kp.p_pid != pid)returnQString(); QString name =QFile::decodeName(kp.p_comm);# elseif(kp.ki_pid != pid)returnQString(); QString name =QFile::decodeName(kp.ki_comm);# endifreturn name;#elif defined(Q_OS_QNX)char exePath[PATH_MAX];sprintf(exePath,"/proc/%lld/exefile", pid);int fd =qt_safe_open(exePath, O_RDONLY);if(fd == -1)returnQString(); QT_STATBUF sbuf;if(QT_FSTAT(fd, &sbuf) == -1) {qt_safe_close(fd);returnQString();} QByteArray buffer(sbuf.st_size,Qt::Uninitialized); buffer.resize(qt_safe_read(fd, buffer.data(), sbuf.st_size -1));if(buffer.isEmpty()) {// The pid is gone. Return some invalid process name to fail the test.returnQStringLiteral("/ERROR/");}returnQFileSystemEntry(buffer,QFileSystemEntry::FromNativePath()).fileName();#elseQ_UNUSED(pid);returnQString();#endif}voidQLockFile::unlock(){Q_D(QLockFile);if(!d->isLocked)return;close(d->fileHandle); d->fileHandle = -1;if(!QFile::remove(d->fileName)) {qWarning() <<"Could not remove our own lock file"<< d->fileName <<"maybe permissions changed meanwhile?";// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...} d->lockError =QLockFile::NoError; d->isLocked =false;} QT_END_NAMESPACE 
close