KDECore
klockfile_unix.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the KDE libraries 00003 Copyright (c) 2004 Waldo Bastian <bastian@kde.org> 00004 Copyright (c) 2011 David Faure <faure@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License version 2 as published by the Free Software Foundation. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "klockfile.h" 00022 00023 #include <config.h> 00024 00025 #include <sys/types.h> 00026 #ifdef HAVE_SYS_STAT_H 00027 #include <sys/stat.h> 00028 #endif 00029 #ifdef HAVE_SYS_TIME_H 00030 #include <sys/time.h> 00031 #endif 00032 #include <signal.h> 00033 #include <errno.h> 00034 #include <stdlib.h> 00035 #include <unistd.h> 00036 00037 #include <QtCore/QDate> 00038 #include <QtCore/QFile> 00039 #include <QTextStream> 00040 00041 #include "krandom.h" 00042 #include "kglobal.h" 00043 #include "kcomponentdata.h" 00044 #include "ktemporaryfile.h" 00045 #include "kde_file.h" 00046 #include "kfilesystemtype_p.h" 00047 00048 #include <unistd.h> 00049 #include <fcntl.h> 00050 00051 // Related reading: 00052 // http://www.spinnaker.de/linux/nfs-locking.html 00053 // http://en.wikipedia.org/wiki/File_locking 00054 // http://apenwarr.ca/log/?m=201012 00055 00056 // Related source code: 00057 // * lockfile-create, from the lockfile-progs package, uses the link() trick from lockFileWithLink 00058 // below, so it works over NFS but fails on FAT32 too. 00059 // * the flock program, which uses flock(LOCK_EX), works on local filesystems (including FAT32), 00060 // but not NFS. 00061 // Note about flock: don't unlink, it creates a race. http://world.std.com/~swmcd/steven/tech/flock.html 00062 00063 // fcntl(F_SETLK) is not a good solution. 00064 // It locks other processes but locking out other threads must be done by hand, 00065 // and worse, it unlocks when just reading the file in the same process (!). 00066 // See the apenwarr.ca article above. 00067 00068 // open(O_EXCL) seems to be the best solution for local files (on all filesystems), 00069 // it only fails over NFS (at least with old NFS servers). 00070 // See http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=144 00071 00072 // Conclusion: we use O_EXCL by default, and the link() trick over NFS. 00073 00074 class KLockFile::Private 00075 { 00076 public: 00077 Private(const KComponentData &c) 00078 : staleTime(30), // 30 seconds 00079 isLocked(false), 00080 linkCountSupport(true), 00081 mustCloseFd(false), 00082 m_pid(-1), 00083 m_componentData(c) 00084 { 00085 } 00086 00087 // The main method 00088 KLockFile::LockResult lockFile(KDE_struct_stat &st_buf); 00089 00090 // Two different implementations 00091 KLockFile::LockResult lockFileOExcl(KDE_struct_stat &st_buf); 00092 KLockFile::LockResult lockFileWithLink(KDE_struct_stat &st_buf); 00093 00094 KLockFile::LockResult deleteStaleLock(); 00095 KLockFile::LockResult deleteStaleLockWithLink(); 00096 00097 void writeIntoLockFile(QFile& file, const KComponentData& componentData); 00098 void readLockFile(); 00099 bool isNfs() const; 00100 00101 QFile m_file; 00102 QString m_fileName; 00103 int staleTime; 00104 bool isLocked; 00105 bool linkCountSupport; 00106 bool mustCloseFd; 00107 QTime staleTimer; 00108 KDE_struct_stat statBuf; 00109 int m_pid; 00110 QString m_hostname; 00111 QString m_componentName; 00112 KComponentData m_componentData; 00113 }; 00114 00115 00116 KLockFile::KLockFile(const QString &file, const KComponentData &componentData) 00117 : d(new Private(componentData)) 00118 { 00119 d->m_fileName = file; 00120 } 00121 00122 KLockFile::~KLockFile() 00123 { 00124 unlock(); 00125 delete d; 00126 } 00127 00128 int 00129 KLockFile::staleTime() const 00130 { 00131 return d->staleTime; 00132 } 00133 00134 00135 void 00136 KLockFile::setStaleTime(int _staleTime) 00137 { 00138 d->staleTime = _staleTime; 00139 } 00140 00141 static bool operator==( const KDE_struct_stat &st_buf1, 00142 const KDE_struct_stat &st_buf2) 00143 { 00144 #define FIELD_EQ(what) (st_buf1.what == st_buf2.what) 00145 return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) && 00146 FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink); 00147 #undef FIELD_EQ 00148 } 00149 00150 static bool operator!=( const KDE_struct_stat& st_buf1, 00151 const KDE_struct_stat& st_buf2 ) 00152 { 00153 return !(st_buf1 == st_buf2); 00154 } 00155 00156 static bool testLinkCountSupport(const QByteArray &fileName) 00157 { 00158 KDE_struct_stat st_buf; 00159 int result = -1; 00160 // Check if hardlinks raise the link count at all? 00161 if(!::link( fileName, QByteArray(fileName+".test") )) { 00162 result = KDE_lstat( fileName, &st_buf ); 00163 ::unlink( QByteArray(fileName+".test") ); 00164 } 00165 return (result < 0 || ((result == 0) && (st_buf.st_nlink == 2))); 00166 } 00167 00168 void KLockFile::Private::writeIntoLockFile(QFile& file, const KComponentData& componentData) 00169 { 00170 file.setPermissions(QFile::ReadUser|QFile::WriteUser|QFile::ReadGroup|QFile::ReadOther); 00171 00172 char hostname[256]; 00173 hostname[0] = 0; 00174 gethostname(hostname, 255); 00175 hostname[255] = 0; 00176 m_hostname = QString::fromLocal8Bit(hostname); 00177 m_componentName = componentData.componentName(); 00178 00179 QTextStream stream(&file); 00180 m_pid = getpid(); 00181 00182 stream << QString::number(m_pid) << endl 00183 << m_componentName << endl 00184 << m_hostname << endl; 00185 stream.flush(); 00186 } 00187 00188 void KLockFile::Private::readLockFile() 00189 { 00190 m_pid = -1; 00191 m_hostname.clear(); 00192 m_componentName.clear(); 00193 00194 QFile file(m_fileName); 00195 if (file.open(QIODevice::ReadOnly)) 00196 { 00197 QTextStream ts(&file); 00198 if (!ts.atEnd()) 00199 m_pid = ts.readLine().toInt(); 00200 if (!ts.atEnd()) 00201 m_componentName = ts.readLine(); 00202 if (!ts.atEnd()) 00203 m_hostname = ts.readLine(); 00204 } 00205 } 00206 00207 KLockFile::LockResult KLockFile::Private::lockFileWithLink(KDE_struct_stat &st_buf) 00208 { 00209 const QByteArray lockFileName = QFile::encodeName( m_fileName ); 00210 int result = KDE_lstat( lockFileName, &st_buf ); 00211 if (result == 0) { 00212 return KLockFile::LockFail; 00213 } 00214 00215 KTemporaryFile uniqueFile(m_componentData); 00216 uniqueFile.setFileTemplate(m_fileName); 00217 if (!uniqueFile.open()) 00218 return KLockFile::LockError; 00219 00220 writeIntoLockFile(uniqueFile, m_componentData); 00221 00222 QByteArray uniqueName = QFile::encodeName( uniqueFile.fileName() ); 00223 00224 // Create lock file 00225 result = ::link( uniqueName, lockFileName ); 00226 if (result != 0) 00227 return KLockFile::LockError; 00228 00229 if (!linkCountSupport) 00230 return KLockFile::LockOK; 00231 00232 KDE_struct_stat st_buf2; 00233 result = KDE_lstat( uniqueName, &st_buf2 ); 00234 if (result != 0) 00235 return KLockFile::LockError; 00236 00237 result = KDE_lstat( lockFileName, &st_buf ); 00238 if (result != 0) 00239 return KLockFile::LockError; 00240 00241 if (st_buf != st_buf2 || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode)) 00242 { 00243 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 00244 // cifs increases link count artifically but the inodes are still different 00245 if ((st_buf2.st_nlink > 1 || 00246 ((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1))) && (st_buf.st_ino != st_buf2.st_ino)) 00247 { 00248 linkCountSupport = testLinkCountSupport(uniqueName); 00249 if (!linkCountSupport) 00250 return KLockFile::LockOK; // Link count support is missing... assume everything is OK. 00251 } 00252 return KLockFile::LockFail; 00253 } 00254 00255 return KLockFile::LockOK; 00256 } 00257 00258 bool KLockFile::Private::isNfs() const 00259 { 00260 const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(m_fileName); 00261 return fsType == KFileSystemType::Nfs; 00262 } 00263 00264 KLockFile::LockResult KLockFile::Private::lockFile(KDE_struct_stat &st_buf) 00265 { 00266 if (isNfs()) { 00267 return lockFileWithLink(st_buf); 00268 } 00269 00270 return lockFileOExcl(st_buf); 00271 } 00272 00273 KLockFile::LockResult KLockFile::Private::lockFileOExcl(KDE_struct_stat &st_buf) 00274 { 00275 const QByteArray lockFileName = QFile::encodeName( m_fileName ); 00276 00277 int fd = KDE_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644); 00278 if (fd < 0) { 00279 if (errno == EEXIST) { 00280 // File already exists 00281 KDE_lstat( lockFileName, &st_buf ); // caller wants stat buf details 00282 return LockFail; 00283 } else { 00284 return LockError; 00285 } 00286 } 00287 // We hold the lock, continue. 00288 if (!m_file.open(fd, QIODevice::WriteOnly)) { 00289 return LockError; 00290 } 00291 mustCloseFd = true; 00292 writeIntoLockFile(m_file, m_componentData); 00293 00294 // stat to get the modification time 00295 const int result = KDE_lstat(QFile::encodeName(m_fileName), &st_buf); 00296 if (result != 0) 00297 return KLockFile::LockError; 00298 return KLockFile::LockOK; 00299 } 00300 00301 KLockFile::LockResult KLockFile::Private::deleteStaleLock() 00302 { 00303 if (isNfs()) 00304 return deleteStaleLockWithLink(); 00305 00306 // I see no way to prevent the race condition here, where we could 00307 // delete a new lock file that another process just got after we 00308 // decided the old one was too stale for us too. 00309 qWarning("WARNING: deleting stale lockfile %s", qPrintable(m_fileName)); 00310 QFile::remove(m_fileName); 00311 return LockOK; 00312 } 00313 00314 KLockFile::LockResult KLockFile::Private::deleteStaleLockWithLink() 00315 { 00316 // This is dangerous, we could be deleting a new lock instead of 00317 // the old stale one, let's be very careful 00318 00319 // Create temp file 00320 KTemporaryFile *ktmpFile = new KTemporaryFile(m_componentData); 00321 ktmpFile->setFileTemplate(m_fileName); 00322 if (!ktmpFile->open()) { 00323 delete ktmpFile; 00324 return KLockFile::LockError; 00325 } 00326 00327 const QByteArray lckFile = QFile::encodeName(m_fileName); 00328 const QByteArray tmpFile = QFile::encodeName(ktmpFile->fileName()); 00329 delete ktmpFile; 00330 00331 // link to lock file 00332 if (::link(lckFile, tmpFile) != 0) 00333 return KLockFile::LockFail; // Try again later 00334 00335 // check if link count increased with exactly one 00336 // and if the lock file still matches 00337 KDE_struct_stat st_buf1; 00338 KDE_struct_stat st_buf2; 00339 memcpy(&st_buf1, &statBuf, sizeof(KDE_struct_stat)); 00340 st_buf1.st_nlink++; 00341 if ((KDE_lstat(tmpFile, &st_buf2) == 0) && st_buf1 == st_buf2) 00342 { 00343 if ((KDE_lstat(lckFile, &st_buf2) == 0) && st_buf1 == st_buf2) 00344 { 00345 // - - if yes, delete lock file, delete temp file, retry lock 00346 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 00347 ::unlink(lckFile); 00348 ::unlink(tmpFile); 00349 return KLockFile::LockOK; 00350 } 00351 } 00352 00353 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 00354 if (linkCountSupport) 00355 { 00356 linkCountSupport = testLinkCountSupport(tmpFile); 00357 } 00358 00359 if (!linkCountSupport) 00360 { 00361 // Without support for link counts we will have a little race condition 00362 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 00363 ::unlink(tmpFile); 00364 if (::unlink(lckFile) < 0) { 00365 qWarning("WARNING: Problem deleting stale lockfile %s: %s", lckFile.data(), 00366 strerror(errno)); 00367 return KLockFile::LockFail; 00368 } 00369 return KLockFile::LockOK; 00370 } 00371 00372 // Failed to delete stale lock file 00373 qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data()); 00374 ::unlink(tmpFile); 00375 return KLockFile::LockFail; 00376 } 00377 00378 00379 KLockFile::LockResult KLockFile::lock(LockFlags options) 00380 { 00381 if (d->isLocked) 00382 return KLockFile::LockOK; 00383 00384 KLockFile::LockResult result; 00385 int hardErrors = 5; 00386 int n = 5; 00387 while(true) 00388 { 00389 KDE_struct_stat st_buf; 00390 // Try to create the lock file 00391 result = d->lockFile(st_buf); 00392 00393 if (result == KLockFile::LockOK) 00394 { 00395 d->staleTimer = QTime(); 00396 break; 00397 } 00398 else if (result == KLockFile::LockError) 00399 { 00400 d->staleTimer = QTime(); 00401 if (--hardErrors == 0) 00402 { 00403 break; 00404 } 00405 } 00406 else // KLockFile::Fail -- there is already such a file present (e.g. left by a crashed app) 00407 { 00408 if (!d->staleTimer.isNull() && d->statBuf != st_buf) 00409 d->staleTimer = QTime(); 00410 00411 if (d->staleTimer.isNull()) 00412 { 00413 memcpy(&(d->statBuf), &st_buf, sizeof(KDE_struct_stat)); 00414 d->staleTimer.start(); 00415 00416 d->readLockFile(); 00417 } 00418 00419 bool isStale = false; 00420 if ((d->m_pid > 0) && !d->m_hostname.isEmpty()) 00421 { 00422 // Check if hostname is us 00423 char hostname[256]; 00424 hostname[0] = 0; 00425 gethostname(hostname, 255); 00426 hostname[255] = 0; 00427 00428 if (d->m_hostname == QLatin1String(hostname)) 00429 { 00430 // Check if pid still exists 00431 int res = ::kill(d->m_pid, 0); 00432 if ((res == -1) && (errno == ESRCH)) 00433 isStale = true; // pid does not exist 00434 } 00435 } 00436 if (d->staleTimer.elapsed() > (d->staleTime*1000)) 00437 isStale = true; 00438 00439 if (isStale) 00440 { 00441 if ((options & ForceFlag) == 0) 00442 return KLockFile::LockStale; 00443 00444 result = d->deleteStaleLock(); 00445 00446 if (result == KLockFile::LockOK) 00447 { 00448 // Lock deletion successful 00449 d->staleTimer = QTime(); 00450 continue; // Now try to get the new lock 00451 } 00452 else if (result != KLockFile::LockFail) 00453 { 00454 return result; 00455 } 00456 } 00457 } 00458 00459 if (options & NoBlockFlag) 00460 break; 00461 00462 struct timeval tv; 00463 tv.tv_sec = 0; 00464 tv.tv_usec = n*((KRandom::random() % 200)+100); 00465 if (n < 2000) 00466 n = n * 2; 00467 00468 select(0, 0, 0, 0, &tv); 00469 } 00470 if (result == LockOK) 00471 d->isLocked = true; 00472 return result; 00473 } 00474 00475 bool KLockFile::isLocked() const 00476 { 00477 return d->isLocked; 00478 } 00479 00480 void KLockFile::unlock() 00481 { 00482 if (d->isLocked) 00483 { 00484 ::unlink(QFile::encodeName(d->m_fileName)); 00485 if (d->mustCloseFd) { 00486 close(d->m_file.handle()); 00487 d->mustCloseFd = false; 00488 } 00489 d->m_file.close(); 00490 d->m_pid = -1; 00491 d->isLocked = false; 00492 } 00493 } 00494 00495 bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname) 00496 { 00497 if (d->m_pid == -1) 00498 return false; 00499 pid = d->m_pid; 00500 hostname = d->m_hostname; 00501 appname = d->m_componentName; 00502 return true; 00503 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:28:11 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:28:11 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.