klockfile.cpp
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 <qfile.h> 00037 #include <qtextstream.h> 00038 00039 #include <kde_file.h> 00040 #include <kapplication.h> 00041 #include <kcmdlineargs.h> 00042 #include <kglobal.h> 00043 #include <ktempfile.h> 00044 00045 // TODO: http://www.spinnaker.de/linux/nfs-locking.html 00046 // TODO: Make regression test 00047 00048 class KLockFile::KLockFilePrivate { 00049 public: 00050 QString file; 00051 int staleTime; 00052 bool isLocked; 00053 bool recoverLock; 00054 bool linkCountSupport; 00055 QTime staleTimer; 00056 KDE_struct_stat statBuf; 00057 int pid; 00058 QString hostname; 00059 QString instance; 00060 QString lockRecoverFile; 00061 }; 00062 00063 00064 // 30 seconds 00065 KLockFile::KLockFile(const QString &file) 00066 { 00067 d = new KLockFilePrivate(); 00068 d->file = file; 00069 d->staleTime = 30; 00070 d->isLocked = false; 00071 d->recoverLock = false; 00072 d->linkCountSupport = true; 00073 } 00074 00075 KLockFile::~KLockFile() 00076 { 00077 unlock(); 00078 delete d; 00079 } 00080 00081 int 00082 KLockFile::staleTime() const 00083 { 00084 return d->staleTime; 00085 } 00086 00087 00088 void 00089 KLockFile::setStaleTime(int _staleTime) 00090 { 00091 d->staleTime = _staleTime; 00092 } 00093 00094 static bool statResultIsEqual(KDE_struct_stat &st_buf1, KDE_struct_stat &st_buf2) 00095 { 00096 #define FIELD_EQ(what) (st_buf1.what == st_buf2.what) 00097 return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) && 00098 FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink); 00099 #undef FIELD_EQ 00100 } 00101 00102 static bool testLinkCountSupport(const QCString &fileName) 00103 { 00104 KDE_struct_stat st_buf; 00105 // Check if hardlinks raise the link count at all? 00106 ::link( fileName, fileName+".test" ); 00107 int result = KDE_lstat( fileName, &st_buf ); 00108 ::unlink( fileName+".test" ); 00109 return ((result == 0) && (st_buf.st_nlink == 2)); 00110 } 00111 00112 static KLockFile::LockResult lockFile(const QString &lockFile, KDE_struct_stat &st_buf, bool &linkCountSupport) 00113 { 00114 QCString lockFileName = QFile::encodeName( lockFile ); 00115 int result = KDE_lstat( lockFileName, &st_buf ); 00116 if (result == 0) 00117 return KLockFile::LockFail; 00118 00119 KTempFile uniqueFile(lockFile, QString::null, 0644); 00120 uniqueFile.setAutoDelete(true); 00121 if (uniqueFile.status() != 0) 00122 return KLockFile::LockError; 00123 00124 char hostname[256]; 00125 hostname[0] = 0; 00126 gethostname(hostname, 255); 00127 hostname[255] = 0; 00128 QCString instanceName = KCmdLineArgs::appName(); 00129 00130 (*(uniqueFile.textStream())) << QString::number(getpid()) << endl 00131 << instanceName << endl 00132 << hostname << endl; 00133 uniqueFile.close(); 00134 00135 QCString uniqueName = QFile::encodeName( uniqueFile.name() ); 00136 00137 #ifdef Q_OS_UNIX 00138 // Create lock file 00139 result = ::link( uniqueName, lockFileName ); 00140 if (result != 0) 00141 return KLockFile::LockError; 00142 00143 if (!linkCountSupport) 00144 return KLockFile::LockOK; 00145 #else 00146 //TODO for win32 00147 return KLockFile::LockOK; 00148 #endif 00149 00150 KDE_struct_stat st_buf2; 00151 result = KDE_lstat( uniqueName, &st_buf2 ); 00152 if (result != 0) 00153 return KLockFile::LockError; 00154 00155 result = KDE_lstat( lockFileName, &st_buf ); 00156 if (result != 0) 00157 return KLockFile::LockError; 00158 00159 if (!statResultIsEqual(st_buf, st_buf2) || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode)) 00160 { 00161 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 00162 if ((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1) && (st_buf.st_ino != st_buf2.st_ino)) 00163 { 00164 linkCountSupport = testLinkCountSupport(uniqueName); 00165 if (!linkCountSupport) 00166 return KLockFile::LockOK; // Link count support is missing... assume everything is OK. 00167 } 00168 return KLockFile::LockFail; 00169 } 00170 00171 return KLockFile::LockOK; 00172 } 00173 00174 static KLockFile::LockResult deleteStaleLock(const QString &lockFile, KDE_struct_stat &st_buf, bool &linkCountSupport) 00175 { 00176 // This is dangerous, we could be deleting a new lock instead of 00177 // the old stale one, let's be very careful 00178 00179 // Create temp file 00180 KTempFile ktmpFile(lockFile); 00181 if (ktmpFile.status() != 0) 00182 return KLockFile::LockError; 00183 00184 QCString lckFile = QFile::encodeName(lockFile); 00185 QCString tmpFile = QFile::encodeName(ktmpFile.name()); 00186 ktmpFile.close(); 00187 ktmpFile.unlink(); 00188 00189 #ifdef Q_OS_UNIX 00190 // link to lock file 00191 if (::link(lckFile, tmpFile) != 0) 00192 return KLockFile::LockFail; // Try again later 00193 #else 00194 //TODO for win32 00195 return KLockFile::LockOK; 00196 #endif 00197 00198 // check if link count increased with exactly one 00199 // and if the lock file still matches 00200 KDE_struct_stat st_buf1; 00201 KDE_struct_stat st_buf2; 00202 memcpy(&st_buf1, &st_buf, sizeof(KDE_struct_stat)); 00203 st_buf1.st_nlink++; 00204 if ((KDE_lstat(tmpFile, &st_buf2) == 0) && statResultIsEqual(st_buf1, st_buf2)) 00205 { 00206 if ((KDE_lstat(lckFile, &st_buf2) == 0) && statResultIsEqual(st_buf1, st_buf2)) 00207 { 00208 // - - if yes, delete lock file, delete temp file, retry lock 00209 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 00210 ::unlink(lckFile); 00211 ::unlink(tmpFile); 00212 return KLockFile::LockOK; 00213 } 00214 } 00215 00216 // SMBFS supports hardlinks by copying the file, as a result the above test will always fail 00217 if (linkCountSupport) 00218 { 00219 linkCountSupport = testLinkCountSupport(tmpFile); 00220 } 00221 00222 if (!linkCountSupport && 00223 (KDE_lstat(lckFile, &st_buf2) == 0) && 00224 statResultIsEqual(st_buf, st_buf2)) 00225 { 00226 // Without support for link counts we will have a little race condition 00227 qWarning("WARNING: deleting stale lockfile %s", lckFile.data()); 00228 ::unlink(lckFile); 00229 ::unlink(tmpFile); 00230 return KLockFile::LockOK; 00231 } 00232 00233 // Failed to delete stale lock file 00234 qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data()); 00235 ::unlink(tmpFile); 00236 return KLockFile::LockFail; 00237 } 00238 00239 00240 KLockFile::LockResult KLockFile::lock(int options) 00241 { 00242 if (d->isLocked) 00243 return KLockFile::LockOK; 00244 00245 KLockFile::LockResult result; 00246 int hardErrors = 5; 00247 int n = 5; 00248 while(true) 00249 { 00250 KDE_struct_stat st_buf; 00251 result = lockFile(d->file, st_buf, d->linkCountSupport); 00252 if (result == KLockFile::LockOK) 00253 { 00254 d->staleTimer = QTime(); 00255 break; 00256 } 00257 else if (result == KLockFile::LockError) 00258 { 00259 d->staleTimer = QTime(); 00260 if (--hardErrors == 0) 00261 { 00262 break; 00263 } 00264 } 00265 else // KLockFile::Fail 00266 { 00267 if (!d->staleTimer.isNull() && !statResultIsEqual(d->statBuf, st_buf)) 00268 d->staleTimer = QTime(); 00269 00270 if (!d->staleTimer.isNull()) 00271 { 00272 bool isStale = false; 00273 if ((d->pid > 0) && !d->hostname.isEmpty()) 00274 { 00275 // Check if hostname is us 00276 char hostname[256]; 00277 hostname[0] = 0; 00278 gethostname(hostname, 255); 00279 hostname[255] = 0; 00280 00281 if (d->hostname == hostname) 00282 { 00283 // Check if pid still exists 00284 int res = ::kill(d->pid, 0); 00285 if ((res == -1) && (errno == ESRCH)) 00286 isStale = true; 00287 } 00288 } 00289 if (d->staleTimer.elapsed() > (d->staleTime*1000)) 00290 isStale = true; 00291 00292 if (isStale) 00293 { 00294 if ((options & LockForce) == 0) 00295 return KLockFile::LockStale; 00296 00297 result = deleteStaleLock(d->file, d->statBuf, d->linkCountSupport); 00298 00299 if (result == KLockFile::LockOK) 00300 { 00301 // Lock deletion successful 00302 d->staleTimer = QTime(); 00303 continue; // Now try to get the new lock 00304 } 00305 else if (result != KLockFile::LockFail) 00306 { 00307 return result; 00308 } 00309 } 00310 } 00311 else 00312 { 00313 memcpy(&(d->statBuf), &st_buf, sizeof(KDE_struct_stat)); 00314 d->staleTimer.start(); 00315 00316 d->pid = -1; 00317 d->hostname = QString::null; 00318 d->instance = QString::null; 00319 00320 QFile file(d->file); 00321 if (file.open(IO_ReadOnly)) 00322 { 00323 QTextStream ts(&file); 00324 if (!ts.atEnd()) 00325 d->pid = ts.readLine().toInt(); 00326 if (!ts.atEnd()) 00327 d->instance = ts.readLine(); 00328 if (!ts.atEnd()) 00329 d->hostname = ts.readLine(); 00330 } 00331 } 00332 } 00333 00334 if ((options & LockNoBlock) != 0) 00335 break; 00336 00337 struct timeval tv; 00338 tv.tv_sec = 0; 00339 tv.tv_usec = n*((KApplication::random() % 200)+100); 00340 if (n < 2000) 00341 n = n * 2; 00342 00343 #ifdef Q_OS_UNIX 00344 select(0, 0, 0, 0, &tv); 00345 #else 00346 //TODO for win32 00347 #endif 00348 } 00349 if (result == LockOK) 00350 d->isLocked = true; 00351 return result; 00352 } 00353 00354 bool KLockFile::isLocked() const 00355 { 00356 return d->isLocked; 00357 } 00358 00359 void KLockFile::unlock() 00360 { 00361 if (d->isLocked) 00362 { 00363 ::unlink(QFile::encodeName(d->file)); 00364 d->isLocked = false; 00365 } 00366 } 00367 00368 bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname) 00369 { 00370 if (d->pid == -1) 00371 return false; 00372 pid = d->pid; 00373 hostname = d->hostname; 00374 appname = d->instance; 00375 return true; 00376 }