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 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License version 2 as published by the Free Software Foundation. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "klockfile.h" 00021 00022 #include <config.h> 00023 00024 #include <sys/types.h> 00025 #ifdef HAVE_SYS_STAT_H 00026 #include <sys/stat.h> 00027 #endif 00028 #ifdef HAVE_SYS_TIME_H 00029 #include <sys/time.h> 00030 #endif 00031 #include <signal.h> 00032 #include <errno.h> 00033 #include <stdlib.h> 00034 #include <unistd.h> 00035 00036 #include <QtCore/QDate> 00037 #include <QtCore/QFile> 00038 #include <QtCore/QTextIStream> 00039 00040 #include "krandom.h" 00041 #include "kglobal.h" 00042 #include "kcomponentdata.h" 00043 #include "ktemporaryfile.h" 00044 #include "kde_file.h" 00045 00046 // TODO: http://www.spinnaker.de/linux/nfs-locking.html 00047 00048 class KLockFile::Private 00049 { 00050 public: 00051 Private(const KComponentData &c) 00052 : componentData(c) 00053 { 00054 } 00055 00056 QString file; 00057 int staleTime; 00058 bool isLocked; 00059 bool recoverLock; 00060 bool linkCountSupport; 00061 QTime staleTimer; 00062 KDE_struct_stat statBuf; 00063 int pid; 00064 QString hostname; 00065 QString instance; 00066 QString lockRecoverFile; 00067 KComponentData componentData; 00068 }; 00069 00070 00071 // 30 seconds 00072 KLockFile::KLockFile(const QString &file, const KComponentData &componentData) 00073 : d(new Private(componentData)) 00074 { 00075 d->file = file; 00076 d->staleTime = 30; 00077 d->isLocked = false; 00078 d->recoverLock = false; 00079 d->linkCountSupport = true; 00080 } 00081 00082 KLockFile::~KLockFile() 00083 { 00084 unlock(); 00085 delete d; 00086 } 00087 00088 int 00089 KLockFile::staleTime() const 00090 { 00091 return d->staleTime; 00092 } 00093 00094 00095 void 00096 KLockFile::setStaleTime(int _staleTime) 00097 { 00098 d->staleTime = _staleTime; 00099 } 00100 00101 static bool operator==( const KDE_struct_stat &st_buf1, 00102 const KDE_struct_stat &st_buf2) 00103 { 00104 #define FIELD_EQ(what) (st_buf1.what == st_buf2.what) 00105 return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) && 00106 FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink); 00107 #undef FIELD_EQ 00108 } 00109 00110 static bool operator!=( const KDE_struct_stat& st_buf1, 00111 const KDE_struct_stat& st_buf2 ) 00112 { 00113 return !(st_buf1 == st_buf2); 00114 } 00115 00116 static bool testLinkCountSupport(const QByteArray &fileName) 00117 { 00118 KDE_struct_stat st_buf; 00119 int result = -1; 00120 // Check if hardlinks raise the link count at all? 00121 if(!::link( fileName, fileName+".test" )) { 00122 result = KDE_lstat( fileName, &st_buf ); 00123 ::unlink( fileName+".test" ); 00124 } 00125 return (result < 0 || ((result == 0) && (st_buf.st_nlink == 2))); 00126 } 00127 00128 static KLockFile::LockResult lockFile(const QString &lockFile, KDE_struct_stat &st_buf, 00129 bool &linkCountSupport, const KComponentData &componentData) 00130 { 00131 QByteArray lockFileName = QFile::encodeName( lockFile ); 00132 int result = KDE_lstat( lockFileName, &st_buf ); 00133 if (result == 0) 00134 return KLockFile::LockFail; 00135 00136 KTemporaryFile uniqueFile(componentData); 00137 uniqueFile.setFileTemplate(lockFile); 00138 if (!uniqueFile.open()) 00139 return KLockFile::LockError; 00140 uniqueFile.setPermissions(QFile::ReadUser|QFile::WriteUser|QFile::ReadGroup|QFile::ReadOther); 00141 00142 char hostname[256]; 00143 hostname[0] = 0; 00144 gethostname(hostname, 255); 00145 hostname[255] = 0; 00146 QString componentName = componentData.componentName(); 00147 00148 QTextStream stream(&uniqueFile); 00149 stream << QString::number(getpid()) << endl 00150 << componentName << endl 00151 << hostname << endl; 00152 stream.flush(); 00153 00154 QByteArray uniqueName = QFile::encodeName( uniqueFile.fileName() ); 00155 00156 // Create lock file 00157 result = ::link( uniqueName, lockFileName ); 00158 if (result != 0) 00159 return KLockFile::LockError; 00160 00161 if (!linkCountSupport) 00162 return KLockFile::LockOK; 00163 00164 KDE_struct_stat st_buf2; 00165 result = KDE_lstat( uniqueName, &st_buf2 ); 00166 if (result != 0) 00167 return KLockFile::LockError; 00168 00169 result = KDE_lstat( lockFileName, &st_buf ); 00170 if (result != 0) 00171 return KLockFile::LockError; 00172 00173 if (st_buf != st_buf2 || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode)) 00174 { 00175 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 00176 // cifs increases link count artifically but the inodes are still different 00177 if ((st_buf2.st_nlink > 1 || 00178 ((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1))) && (st_buf.st_ino != st_buf2.st_ino)) 00179 { 00180 linkCountSupport = testLinkCountSupport(uniqueName); 00181 if (!linkCountSupport) 00182 return KLockFile::LockOK; // Link count support is missing... assume everything is OK. 00183 } 00184 return KLockFile::LockFail; 00185 } 00186 00187 return KLockFile::LockOK; 00188 } 00189 00190 static KLockFile::LockResult deleteStaleLock(const QString &lockFile, KDE_struct_stat &st_buf, bool &linkCountSupport, const KComponentData &componentData) 00191 { 00192 // This is dangerous, we could be deleting a new lock instead of 00193 // the old stale one, let's be very careful 00194 00195 // Create temp file 00196 KTemporaryFile *ktmpFile = new KTemporaryFile(componentData); 00197 ktmpFile->setFileTemplate(lockFile); 00198 if (!ktmpFile->open()) 00199 return KLockFile::LockError; 00200 00201 QByteArray lckFile = QFile::encodeName(lockFile); 00202 QByteArray tmpFile = QFile::encodeName(ktmpFile->fileName()); 00203 delete ktmpFile; 00204 00205 // link to lock file 00206 if (::link(lckFile, tmpFile) != 0) 00207 return KLockFile::LockFail; // Try again later 00208 00209 // check if link count increased with exactly one 00210 // and if the lock file still matches 00211 KDE_struct_stat st_buf1; 00212 KDE_struct_stat st_buf2; 00213 memcpy(&st_buf1, &st_buf, sizeof(KDE_struct_stat)); 00214 st_buf1.st_nlink++; 00215 if ((KDE_lstat(tmpFile, &st_buf2) == 0) && st_buf1 == st_buf2) 00216 { 00217 if ((KDE_lstat(lckFile, &st_buf2) == 0) && st_buf1 == st_buf2) 00218 { 00219 // - - if yes, delete lock file, delete temp file, retry lock 00220 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 00221 ::unlink(lckFile); 00222 ::unlink(tmpFile); 00223 return KLockFile::LockOK; 00224 } 00225 } 00226 00227 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 00228 if (linkCountSupport) 00229 { 00230 linkCountSupport = testLinkCountSupport(tmpFile); 00231 } 00232 00233 if (!linkCountSupport) 00234 { 00235 // Without support for link counts we will have a little race condition 00236 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 00237 ::unlink(tmpFile); 00238 if (::unlink(lckFile) < 0) { 00239 qWarning("WARNING: Problem deleting stale lockfile %s: %s", lckFile.data(), 00240 strerror(errno)); 00241 return KLockFile::LockFail; 00242 } 00243 return KLockFile::LockOK; 00244 } 00245 00246 // Failed to delete stale lock file 00247 qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data()); 00248 ::unlink(tmpFile); 00249 return KLockFile::LockFail; 00250 } 00251 00252 00253 KLockFile::LockResult KLockFile::lock(LockFlags options) 00254 { 00255 if (d->isLocked) 00256 return KLockFile::LockOK; 00257 00258 KLockFile::LockResult result; 00259 int hardErrors = 5; 00260 int n = 5; 00261 while(true) 00262 { 00263 KDE_struct_stat st_buf; 00264 result = lockFile(d->file, st_buf, d->linkCountSupport, d->componentData); 00265 if (result == KLockFile::LockOK) 00266 { 00267 d->staleTimer = QTime(); 00268 break; 00269 } 00270 else if (result == KLockFile::LockError) 00271 { 00272 d->staleTimer = QTime(); 00273 if (--hardErrors == 0) 00274 { 00275 break; 00276 } 00277 } 00278 else // KLockFile::Fail -- there is already such a file present (e.g. left by a crashed app) 00279 { 00280 if (!d->staleTimer.isNull() && d->statBuf != st_buf) 00281 d->staleTimer = QTime(); 00282 00283 if (d->staleTimer.isNull()) 00284 { 00285 memcpy(&(d->statBuf), &st_buf, sizeof(KDE_struct_stat)); 00286 d->staleTimer.start(); 00287 00288 d->pid = -1; 00289 d->hostname.clear(); 00290 d->instance.clear(); 00291 00292 QFile file(d->file); 00293 if (file.open(QIODevice::ReadOnly)) 00294 { 00295 QTextStream ts(&file); 00296 if (!ts.atEnd()) 00297 d->pid = ts.readLine().toInt(); 00298 if (!ts.atEnd()) 00299 d->instance = ts.readLine(); 00300 if (!ts.atEnd()) 00301 d->hostname = ts.readLine(); 00302 } 00303 } 00304 00305 bool isStale = false; 00306 if ((d->pid > 0) && !d->hostname.isEmpty()) 00307 { 00308 // Check if hostname is us 00309 char hostname[256]; 00310 hostname[0] = 0; 00311 gethostname(hostname, 255); 00312 hostname[255] = 0; 00313 00314 if (d->hostname == QLatin1String(hostname)) 00315 { 00316 // Check if pid still exists 00317 int res = ::kill(d->pid, 0); 00318 if ((res == -1) && (errno == ESRCH)) 00319 isStale = true; 00320 } 00321 } 00322 if (d->staleTimer.elapsed() > (d->staleTime*1000)) 00323 isStale = true; 00324 00325 if (isStale) 00326 { 00327 if ((options & ForceFlag) == 0) 00328 return KLockFile::LockStale; 00329 00330 result = deleteStaleLock(d->file, d->statBuf, d->linkCountSupport, d->componentData); 00331 00332 if (result == KLockFile::LockOK) 00333 { 00334 // Lock deletion successful 00335 d->staleTimer = QTime(); 00336 continue; // Now try to get the new lock 00337 } 00338 else if (result != KLockFile::LockFail) 00339 { 00340 return result; 00341 } 00342 } 00343 } 00344 00345 if (options & NoBlockFlag) 00346 break; 00347 00348 struct timeval tv; 00349 tv.tv_sec = 0; 00350 tv.tv_usec = n*((KRandom::random() % 200)+100); 00351 if (n < 2000) 00352 n = n * 2; 00353 00354 select(0, 0, 0, 0, &tv); 00355 } 00356 if (result == LockOK) 00357 d->isLocked = true; 00358 return result; 00359 } 00360 00361 bool KLockFile::isLocked() const 00362 { 00363 return d->isLocked; 00364 } 00365 00366 void KLockFile::unlock() 00367 { 00368 if (d->isLocked) 00369 { 00370 ::unlink(QFile::encodeName(d->file)); 00371 d->isLocked = false; 00372 } 00373 } 00374 00375 bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname) 00376 { 00377 if (d->pid == -1) 00378 return false; 00379 pid = d->pid; 00380 hostname = d->hostname; 00381 appname = d->instance; 00382 return true; 00383 }
KDE 4.6 API Reference