Warning, file /frameworks/kdelibs4support/src/kdecore/klockfile_unix.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 Copyright (c) 2004 Waldo Bastian <bastian@kde.org> 0004 Copyright (c) 2011 David Faure <faure@kde.org> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License version 2 as published by the Free Software Foundation. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "klockfile.h" 0022 0023 #include <config-klockfile.h> 0024 0025 #include <sys/types.h> 0026 #if HAVE_SYS_STAT_H 0027 #include <sys/stat.h> 0028 #endif 0029 #if HAVE_SYS_TIME_H 0030 #include <sys/time.h> 0031 #endif 0032 #include <signal.h> 0033 #include <errno.h> 0034 #include <stdlib.h> 0035 #include <unistd.h> 0036 0037 #include <qplatformdefs.h> // QT_STATBUF, QT_LSTAT, QT_OPEN 0038 #include <QDate> 0039 #include <QFile> 0040 #include <QCoreApplication> 0041 #include <QTextStream> 0042 #include <QTemporaryFile> 0043 0044 #include "krandom.h" 0045 #include "kfilesystemtype.h" 0046 0047 #include <unistd.h> 0048 #include <fcntl.h> 0049 0050 // Related reading: 0051 // http://www.spinnaker.de/linux/nfs-locking.html 0052 // http://en.wikipedia.org/wiki/File_locking 0053 // http://apenwarr.ca/log/?m=201012 0054 0055 // Related source code: 0056 // * lockfile-create, from the lockfile-progs package, uses the link() trick from lockFileWithLink 0057 // below, so it works over NFS but fails on FAT32 too. 0058 // * the flock program, which uses flock(LOCK_EX), works on local filesystems (including FAT32), 0059 // but not NFS. 0060 // Note about flock: don't unlink, it creates a race. http://world.std.com/~swmcd/steven/tech/flock.html 0061 0062 // fcntl(F_SETLK) is not a good solution. 0063 // It locks other processes but locking out other threads must be done by hand, 0064 // and worse, it unlocks when just reading the file in the same process (!). 0065 // See the apenwarr.ca article above. 0066 0067 // open(O_EXCL) seems to be the best solution for local files (on all filesystems), 0068 // it only fails over NFS (at least with old NFS servers). 0069 // See http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=144 0070 0071 // Conclusion: we use O_EXCL by default, and the link() trick over NFS. 0072 0073 class Q_DECL_HIDDEN KLockFile::Private 0074 { 0075 public: 0076 Private(const QString &componentName) 0077 : staleTime(30), // 30 seconds 0078 isLocked(false), 0079 linkCountSupport(true), 0080 mustCloseFd(false), 0081 m_pid(-1), 0082 m_componentName(componentName) 0083 { 0084 } 0085 0086 // The main method 0087 KLockFile::LockResult lockFile(QT_STATBUF &st_buf); 0088 0089 // Two different implementations 0090 KLockFile::LockResult lockFileOExcl(QT_STATBUF &st_buf); 0091 KLockFile::LockResult lockFileWithLink(QT_STATBUF &st_buf); 0092 0093 KLockFile::LockResult deleteStaleLock(); 0094 KLockFile::LockResult deleteStaleLockWithLink(); 0095 0096 void writeIntoLockFile(QFile &file); 0097 void readLockFile(); 0098 bool isNfs() const; 0099 0100 QFile m_file; 0101 QString m_fileName; 0102 int staleTime; 0103 bool isLocked; 0104 bool linkCountSupport; 0105 bool mustCloseFd; 0106 QTime staleTimer; 0107 QT_STATBUF statBuf; 0108 int m_pid; 0109 QString m_hostname; 0110 QString m_componentName; // as set for this instance 0111 QString m_componentNameFromFile; // as read from the lock file 0112 }; 0113 0114 KLockFile::KLockFile(const QString &file, const QString &componentName) 0115 : d(new Private(componentName)) 0116 { 0117 d->m_fileName = file; 0118 } 0119 0120 KLockFile::~KLockFile() 0121 { 0122 unlock(); 0123 delete d; 0124 } 0125 0126 int 0127 KLockFile::staleTime() const 0128 { 0129 return d->staleTime; 0130 } 0131 0132 void 0133 KLockFile::setStaleTime(int _staleTime) 0134 { 0135 d->staleTime = _staleTime; 0136 } 0137 0138 static bool operator==(const QT_STATBUF &st_buf1, 0139 const QT_STATBUF &st_buf2) 0140 { 0141 #define FIELD_EQ(what) (st_buf1.what == st_buf2.what) 0142 return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) && 0143 FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink); 0144 #undef FIELD_EQ 0145 } 0146 0147 static bool operator!=(const QT_STATBUF &st_buf1, 0148 const QT_STATBUF &st_buf2) 0149 { 0150 return !(st_buf1 == st_buf2); 0151 } 0152 0153 static bool testLinkCountSupport(const QByteArray &fileName) 0154 { 0155 QT_STATBUF st_buf; 0156 int result = -1; 0157 // Check if hardlinks raise the link count at all? 0158 if (!::link(fileName.data(), QByteArray(fileName + ".test").data())) { 0159 result = QT_LSTAT(fileName.data(), &st_buf); 0160 ::unlink(QByteArray(fileName + ".test").data()); 0161 } 0162 return (result < 0 || ((result == 0) && (st_buf.st_nlink == 2))); 0163 } 0164 0165 void KLockFile::Private::writeIntoLockFile(QFile &file) 0166 { 0167 file.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup | QFile::ReadOther); 0168 0169 char hostname[256]; 0170 hostname[0] = 0; 0171 gethostname(hostname, 255); 0172 hostname[255] = 0; 0173 m_hostname = QString::fromLocal8Bit(hostname); 0174 if (m_componentName.isEmpty() && QCoreApplication::instance()) { // TODO Qt5: should be fixed by new Q_GLOBAL_STATIC: qcoreappdata() was dangling, in kconfigtest testSyncOnExit. 0175 m_componentName = QCoreApplication::applicationName(); 0176 } 0177 0178 m_pid = getpid(); 0179 0180 file.write(QByteArray::number(m_pid) + '\n'); 0181 file.write(m_componentName.toUtf8() + '\n'); 0182 file.write(hostname); 0183 file.flush(); 0184 } 0185 0186 void KLockFile::Private::readLockFile() 0187 { 0188 m_pid = -1; 0189 m_hostname.clear(); 0190 m_componentNameFromFile.clear(); 0191 0192 QFile file(m_fileName); 0193 if (file.open(QIODevice::ReadOnly)) { 0194 QTextStream ts(&file); 0195 if (!ts.atEnd()) { 0196 m_pid = ts.readLine().toInt(); 0197 } 0198 if (!ts.atEnd()) { 0199 m_componentNameFromFile = ts.readLine(); 0200 } 0201 if (!ts.atEnd()) { 0202 m_hostname = ts.readLine(); 0203 } 0204 } 0205 } 0206 0207 KLockFile::LockResult KLockFile::Private::lockFileWithLink(QT_STATBUF &st_buf) 0208 { 0209 const QByteArray lockFileName = QFile::encodeName(m_fileName); 0210 int result = QT_LSTAT(lockFileName.data(), &st_buf); 0211 if (result == 0) { 0212 return KLockFile::LockFail; 0213 } 0214 0215 QTemporaryFile uniqueFile; 0216 uniqueFile.setFileTemplate(m_fileName); 0217 if (!uniqueFile.open()) { 0218 return KLockFile::LockError; 0219 } 0220 0221 writeIntoLockFile(uniqueFile); 0222 0223 QByteArray uniqueName = QFile::encodeName(uniqueFile.fileName()); 0224 0225 // Create lock file 0226 result = ::link(uniqueName.data(), lockFileName.data()); 0227 if (result != 0) { 0228 return KLockFile::LockError; 0229 } 0230 0231 if (!linkCountSupport) { 0232 return KLockFile::LockOK; 0233 } 0234 0235 QT_STATBUF st_buf2; 0236 result = QT_LSTAT(uniqueName.data(), &st_buf2); 0237 if (result != 0) { 0238 return KLockFile::LockError; 0239 } 0240 0241 result = QT_LSTAT(lockFileName.data(), &st_buf); 0242 if (result != 0) { 0243 return KLockFile::LockError; 0244 } 0245 0246 if (st_buf != st_buf2 || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode)) { 0247 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 0248 // cifs increases link count artifically but the inodes are still different 0249 if ((st_buf2.st_nlink > 1 || 0250 ((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1))) && (st_buf.st_ino != st_buf2.st_ino)) { 0251 linkCountSupport = testLinkCountSupport(uniqueName); 0252 if (!linkCountSupport) { 0253 return KLockFile::LockOK; // Link count support is missing... assume everything is OK. 0254 } 0255 } 0256 return KLockFile::LockFail; 0257 } 0258 0259 return KLockFile::LockOK; 0260 } 0261 0262 bool KLockFile::Private::isNfs() const 0263 { 0264 const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(m_fileName); 0265 return fsType == KFileSystemType::Nfs; 0266 } 0267 0268 KLockFile::LockResult KLockFile::Private::lockFile(QT_STATBUF &st_buf) 0269 { 0270 if (isNfs()) { 0271 return lockFileWithLink(st_buf); 0272 } 0273 0274 return lockFileOExcl(st_buf); 0275 } 0276 0277 KLockFile::LockResult KLockFile::Private::lockFileOExcl(QT_STATBUF &st_buf) 0278 { 0279 const QByteArray lockFileName = QFile::encodeName(m_fileName); 0280 0281 int fd = QT_OPEN(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644); 0282 if (fd < 0) { 0283 if (errno == EEXIST) { 0284 // File already exists 0285 if (QT_LSTAT(lockFileName.constData(), &st_buf) != 0) { // caller wants stat buf details 0286 // File got deleted meanwhile! Clear struct rather than leaving it unset. 0287 st_buf.st_dev = 0; 0288 st_buf.st_ino = 0; 0289 st_buf.st_uid = 0; 0290 st_buf.st_gid = 0; 0291 st_buf.st_nlink = 0; 0292 } 0293 return LockFail; 0294 } else { 0295 return LockError; 0296 } 0297 } 0298 // We hold the lock, continue. 0299 if (!m_file.open(fd, QIODevice::WriteOnly)) { 0300 close(fd); 0301 return LockError; 0302 } 0303 writeIntoLockFile(m_file); 0304 0305 // stat to get the modification time 0306 const int result = QT_LSTAT(QFile::encodeName(m_fileName).data(), &st_buf); 0307 if (result != 0) { 0308 close(fd); 0309 return KLockFile::LockError; 0310 } 0311 mustCloseFd = true; 0312 return KLockFile::LockOK; 0313 } 0314 0315 KLockFile::LockResult KLockFile::Private::deleteStaleLock() 0316 { 0317 if (isNfs()) { 0318 return deleteStaleLockWithLink(); 0319 } 0320 0321 // I see no way to prevent the race condition here, where we could 0322 // delete a new lock file that another process just got after we 0323 // decided the old one was too stale for us too. 0324 qWarning("WARNING: deleting stale lockfile %s", qPrintable(m_fileName)); 0325 QFile::remove(m_fileName); 0326 return LockOK; 0327 } 0328 0329 KLockFile::LockResult KLockFile::Private::deleteStaleLockWithLink() 0330 { 0331 // This is dangerous, we could be deleting a new lock instead of 0332 // the old stale one, let's be very careful 0333 0334 // Create temp file 0335 QTemporaryFile *ktmpFile = new QTemporaryFile; 0336 ktmpFile->setFileTemplate(m_fileName); 0337 if (!ktmpFile->open()) { 0338 delete ktmpFile; 0339 return KLockFile::LockError; 0340 } 0341 0342 const QByteArray lckFile = QFile::encodeName(m_fileName); 0343 const QByteArray tmpFile = QFile::encodeName(ktmpFile->fileName()); 0344 delete ktmpFile; 0345 0346 // link to lock file 0347 if (::link(lckFile.data(), tmpFile.data()) != 0) { 0348 return KLockFile::LockFail; // Try again later 0349 } 0350 0351 // check if link count increased with exactly one 0352 // and if the lock file still matches 0353 QT_STATBUF st_buf1; 0354 QT_STATBUF st_buf2; 0355 memcpy(&st_buf1, &statBuf, sizeof(QT_STATBUF)); 0356 st_buf1.st_nlink++; 0357 if ((QT_LSTAT(tmpFile.data(), &st_buf2) == 0) && st_buf1 == st_buf2) { 0358 if ((QT_LSTAT(lckFile.data(), &st_buf2) == 0) && st_buf1 == st_buf2) { 0359 // - - if yes, delete lock file, delete temp file, retry lock 0360 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 0361 ::unlink(lckFile.data()); 0362 ::unlink(tmpFile.data()); 0363 return KLockFile::LockOK; 0364 } 0365 } 0366 0367 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 0368 if (linkCountSupport) { 0369 linkCountSupport = testLinkCountSupport(tmpFile); 0370 } 0371 0372 if (!linkCountSupport) { 0373 // Without support for link counts we will have a little race condition 0374 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 0375 ::unlink(tmpFile.data()); 0376 if (::unlink(lckFile.data()) < 0) { 0377 qWarning("WARNING: Problem deleting stale lockfile %s: %s", lckFile.data(), 0378 strerror(errno)); 0379 return KLockFile::LockFail; 0380 } 0381 return KLockFile::LockOK; 0382 } 0383 0384 // Failed to delete stale lock file 0385 qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data()); 0386 ::unlink(tmpFile.data()); 0387 return KLockFile::LockFail; 0388 } 0389 0390 KLockFile::LockResult KLockFile::lock(LockFlags options) 0391 { 0392 if (d->isLocked) { 0393 return KLockFile::LockOK; 0394 } 0395 0396 KLockFile::LockResult result; 0397 int hardErrors = 5; 0398 int n = 5; 0399 while (true) { 0400 QT_STATBUF st_buf; 0401 // Try to create the lock file 0402 result = d->lockFile(st_buf); 0403 0404 if (result == KLockFile::LockOK) { 0405 d->staleTimer = QTime(); 0406 break; 0407 } else if (result == KLockFile::LockError) { 0408 d->staleTimer = QTime(); 0409 if (--hardErrors == 0) { 0410 break; 0411 } 0412 } else { // KLockFile::Fail -- there is already such a file present (e.g. left by a crashed app) 0413 if (!d->staleTimer.isNull() && d->statBuf != st_buf) { 0414 d->staleTimer = QTime(); 0415 } 0416 0417 if (d->staleTimer.isNull()) { 0418 memcpy(&(d->statBuf), &st_buf, sizeof(QT_STATBUF)); 0419 d->staleTimer.start(); 0420 0421 d->readLockFile(); 0422 } 0423 0424 bool isStale = false; 0425 if ((d->m_pid > 0) && !d->m_hostname.isEmpty()) { 0426 // Check if hostname is us 0427 char hostname[256]; 0428 hostname[0] = 0; 0429 gethostname(hostname, 255); 0430 hostname[255] = 0; 0431 0432 if (d->m_hostname == QString::fromLocal8Bit(hostname)) { 0433 // Check if pid still exists 0434 int res = ::kill(d->m_pid, 0); 0435 if ((res == -1) && (errno == ESRCH)) { 0436 isStale = true; // pid does not exist 0437 } 0438 } 0439 } 0440 if (d->staleTimer.elapsed() > (d->staleTime * 1000)) { 0441 isStale = true; 0442 } 0443 0444 if (isStale) { 0445 if ((options & ForceFlag) == 0) { 0446 return KLockFile::LockStale; 0447 } 0448 0449 result = d->deleteStaleLock(); 0450 0451 if (result == KLockFile::LockOK) { 0452 // Lock deletion successful 0453 d->staleTimer = QTime(); 0454 continue; // Now try to get the new lock 0455 } else if (result != KLockFile::LockFail) { 0456 return result; 0457 } 0458 } 0459 } 0460 0461 if (options & NoBlockFlag) { 0462 break; 0463 } 0464 0465 struct timeval tv; 0466 tv.tv_sec = 0; 0467 tv.tv_usec = n * ((KRandom::random() % 200) + 100); 0468 if (n < 2000) { 0469 n = n * 2; 0470 } 0471 0472 select(0, nullptr, nullptr, nullptr, &tv); 0473 } 0474 if (result == LockOK) { 0475 d->isLocked = true; 0476 } 0477 return result; 0478 } 0479 0480 bool KLockFile::isLocked() const 0481 { 0482 return d->isLocked; 0483 } 0484 0485 void KLockFile::unlock() 0486 { 0487 if (d->isLocked) { 0488 ::unlink(QFile::encodeName(d->m_fileName).data()); 0489 if (d->mustCloseFd) { 0490 close(d->m_file.handle()); 0491 d->mustCloseFd = false; 0492 } 0493 d->m_file.close(); 0494 d->m_pid = -1; 0495 d->isLocked = false; 0496 } 0497 } 0498 0499 bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname) 0500 { 0501 if (d->m_pid == -1) { 0502 return false; 0503 } 0504 pid = d->m_pid; 0505 hostname = d->m_hostname; 0506 appname = d->m_componentNameFromFile; 0507 return true; 0508 }