• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.9.5 API Reference
  • KDE Home
  • Contact Us
 

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

KDE's Doxygen guidelines are available online.

KDECore

Skip menu "KDECore"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.9.5 API Reference

Skip menu "kdelibs-4.9.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal