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

KDECore

  • sources
  • kde-4.14
  • kdelibs
  • kdecore
  • io
klockfile_unix.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the KDE libraries
3  Copyright (c) 2004 Waldo Bastian <bastian@kde.org>
4  Copyright (c) 2011 David Faure <faure@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License version 2 as published by the Free Software Foundation.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "klockfile.h"
22 
23 #include <config.h>
24 
25 #include <sys/types.h>
26 #ifdef HAVE_SYS_STAT_H
27 #include <sys/stat.h>
28 #endif
29 #ifdef HAVE_SYS_TIME_H
30 #include <sys/time.h>
31 #endif
32 #include <signal.h>
33 #include <errno.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 
37 #include <QtCore/QDate>
38 #include <QtCore/QFile>
39 #include <QTextStream>
40 
41 #include "krandom.h"
42 #include "kglobal.h"
43 #include "kcomponentdata.h"
44 #include "ktemporaryfile.h"
45 #include "kde_file.h"
46 #include "kfilesystemtype_p.h"
47 
48 #include <unistd.h>
49 #include <fcntl.h>
50 
51 // Related reading:
52 // http://www.spinnaker.de/linux/nfs-locking.html
53 // http://en.wikipedia.org/wiki/File_locking
54 // http://apenwarr.ca/log/?m=201012
55 
56 // Related source code:
57 // * lockfile-create, from the lockfile-progs package, uses the link() trick from lockFileWithLink
58 // below, so it works over NFS but fails on FAT32 too.
59 // * the flock program, which uses flock(LOCK_EX), works on local filesystems (including FAT32),
60 // but not NFS.
61 // Note about flock: don't unlink, it creates a race. http://world.std.com/~swmcd/steven/tech/flock.html
62 
63 // fcntl(F_SETLK) is not a good solution.
64 // It locks other processes but locking out other threads must be done by hand,
65 // and worse, it unlocks when just reading the file in the same process (!).
66 // See the apenwarr.ca article above.
67 
68 // open(O_EXCL) seems to be the best solution for local files (on all filesystems),
69 // it only fails over NFS (at least with old NFS servers).
70 // See http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=144
71 
72 // Conclusion: we use O_EXCL by default, and the link() trick over NFS.
73 
74 class KLockFile::Private
75 {
76 public:
77  Private(const KComponentData &c)
78  : staleTime(30), // 30 seconds
79  isLocked(false),
80  linkCountSupport(true),
81  mustCloseFd(false),
82  m_pid(-1),
83  m_componentData(c)
84  {
85  }
86 
87  // The main method
88  KLockFile::LockResult lockFile(KDE_struct_stat &st_buf);
89 
90  // Two different implementations
91  KLockFile::LockResult lockFileOExcl(KDE_struct_stat &st_buf);
92  KLockFile::LockResult lockFileWithLink(KDE_struct_stat &st_buf);
93 
94  KLockFile::LockResult deleteStaleLock();
95  KLockFile::LockResult deleteStaleLockWithLink();
96 
97  void writeIntoLockFile(QFile& file, const KComponentData& componentData);
98  void readLockFile();
99  bool isNfs() const;
100 
101  QFile m_file;
102  QString m_fileName;
103  int staleTime;
104  bool isLocked;
105  bool linkCountSupport;
106  bool mustCloseFd;
107  QTime staleTimer;
108  KDE_struct_stat statBuf;
109  int m_pid;
110  QString m_hostname;
111  QString m_componentName;
112  KComponentData m_componentData;
113 };
114 
115 
116 KLockFile::KLockFile(const QString &file, const KComponentData &componentData)
117  : d(new Private(componentData))
118 {
119  d->m_fileName = file;
120 }
121 
122 KLockFile::~KLockFile()
123 {
124  unlock();
125  delete d;
126 }
127 
128 int
129 KLockFile::staleTime() const
130 {
131  return d->staleTime;
132 }
133 
134 
135 void
136 KLockFile::setStaleTime(int _staleTime)
137 {
138  d->staleTime = _staleTime;
139 }
140 
141 static bool operator==( const KDE_struct_stat &st_buf1,
142  const KDE_struct_stat &st_buf2)
143 {
144 #define FIELD_EQ(what) (st_buf1.what == st_buf2.what)
145  return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) &&
146  FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink);
147 #undef FIELD_EQ
148 }
149 
150 static bool operator!=( const KDE_struct_stat& st_buf1,
151  const KDE_struct_stat& st_buf2 )
152 {
153  return !(st_buf1 == st_buf2);
154 }
155 
156 static bool testLinkCountSupport(const QByteArray &fileName)
157 {
158  KDE_struct_stat st_buf;
159  int result = -1;
160  // Check if hardlinks raise the link count at all?
161  if(!::link( fileName, QByteArray(fileName+".test") )) {
162  result = KDE_lstat( fileName, &st_buf );
163  ::unlink( QByteArray(fileName+".test") );
164  }
165  return (result < 0 || ((result == 0) && (st_buf.st_nlink == 2)));
166 }
167 
168 void KLockFile::Private::writeIntoLockFile(QFile& file, const KComponentData& componentData)
169 {
170  file.setPermissions(QFile::ReadUser|QFile::WriteUser|QFile::ReadGroup|QFile::ReadOther);
171 
172  char hostname[256];
173  hostname[0] = 0;
174  gethostname(hostname, 255);
175  hostname[255] = 0;
176  m_hostname = QString::fromLocal8Bit(hostname);
177  m_componentName = componentData.componentName();
178 
179  QTextStream stream(&file);
180  m_pid = getpid();
181 
182  stream << QString::number(m_pid) << endl
183  << m_componentName << endl
184  << m_hostname << endl;
185  stream.flush();
186 }
187 
188 void KLockFile::Private::readLockFile()
189 {
190  m_pid = -1;
191  m_hostname.clear();
192  m_componentName.clear();
193 
194  QFile file(m_fileName);
195  if (file.open(QIODevice::ReadOnly))
196  {
197  QTextStream ts(&file);
198  if (!ts.atEnd())
199  m_pid = ts.readLine().toInt();
200  if (!ts.atEnd())
201  m_componentName = ts.readLine();
202  if (!ts.atEnd())
203  m_hostname = ts.readLine();
204  }
205 }
206 
207 KLockFile::LockResult KLockFile::Private::lockFileWithLink(KDE_struct_stat &st_buf)
208 {
209  const QByteArray lockFileName = QFile::encodeName( m_fileName );
210  int result = KDE_lstat( lockFileName, &st_buf );
211  if (result == 0) {
212  return KLockFile::LockFail;
213  }
214 
215  KTemporaryFile uniqueFile(m_componentData);
216  uniqueFile.setFileTemplate(m_fileName);
217  if (!uniqueFile.open())
218  return KLockFile::LockError;
219 
220  writeIntoLockFile(uniqueFile, m_componentData);
221 
222  QByteArray uniqueName = QFile::encodeName( uniqueFile.fileName() );
223 
224  // Create lock file
225  result = ::link( uniqueName, lockFileName );
226  if (result != 0)
227  return KLockFile::LockError;
228 
229  if (!linkCountSupport)
230  return KLockFile::LockOK;
231 
232  KDE_struct_stat st_buf2;
233  result = KDE_lstat( uniqueName, &st_buf2 );
234  if (result != 0)
235  return KLockFile::LockError;
236 
237  result = KDE_lstat( lockFileName, &st_buf );
238  if (result != 0)
239  return KLockFile::LockError;
240 
241  if (st_buf != st_buf2 || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode))
242  {
243  // SMBFS supports hardlinks by copying the file, as a result the above test will always fail
244  // cifs increases link count artifically but the inodes are still different
245  if ((st_buf2.st_nlink > 1 ||
246  ((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1))) && (st_buf.st_ino != st_buf2.st_ino))
247  {
248  linkCountSupport = testLinkCountSupport(uniqueName);
249  if (!linkCountSupport)
250  return KLockFile::LockOK; // Link count support is missing... assume everything is OK.
251  }
252  return KLockFile::LockFail;
253  }
254 
255  return KLockFile::LockOK;
256 }
257 
258 bool KLockFile::Private::isNfs() const
259 {
260  const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(m_fileName);
261  return fsType == KFileSystemType::Nfs;
262 }
263 
264 KLockFile::LockResult KLockFile::Private::lockFile(KDE_struct_stat &st_buf)
265 {
266  if (isNfs()) {
267  return lockFileWithLink(st_buf);
268  }
269 
270  return lockFileOExcl(st_buf);
271 }
272 
273 KLockFile::LockResult KLockFile::Private::lockFileOExcl(KDE_struct_stat &st_buf)
274 {
275  const QByteArray lockFileName = QFile::encodeName( m_fileName );
276 
277  int fd = KDE_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644);
278  if (fd < 0) {
279  if (errno == EEXIST) {
280  // File already exists
281  if (KDE_lstat(lockFileName, &st_buf) != 0) { // caller wants stat buf details
282  // File got deleted meanwhile! Clear struct rather than leaving it unset.
283  st_buf.st_dev = 0;
284  st_buf.st_ino = 0;
285  st_buf.st_uid = 0;
286  st_buf.st_gid = 0;
287  st_buf.st_nlink = 0;
288  }
289  return LockFail;
290  } else {
291  return LockError;
292  }
293  }
294  // We hold the lock, continue.
295  if (!m_file.open(fd, QIODevice::WriteOnly)) {
296  return LockError;
297  }
298  mustCloseFd = true;
299  writeIntoLockFile(m_file, m_componentData);
300 
301  // stat to get the modification time
302  const int result = KDE_lstat(QFile::encodeName(m_fileName), &st_buf);
303  if (result != 0)
304  return KLockFile::LockError;
305  return KLockFile::LockOK;
306 }
307 
308 KLockFile::LockResult KLockFile::Private::deleteStaleLock()
309 {
310  if (isNfs())
311  return deleteStaleLockWithLink();
312 
313  // I see no way to prevent the race condition here, where we could
314  // delete a new lock file that another process just got after we
315  // decided the old one was too stale for us too.
316  qWarning("WARNING: deleting stale lockfile %s", qPrintable(m_fileName));
317  QFile::remove(m_fileName);
318  return LockOK;
319 }
320 
321 KLockFile::LockResult KLockFile::Private::deleteStaleLockWithLink()
322 {
323  // This is dangerous, we could be deleting a new lock instead of
324  // the old stale one, let's be very careful
325 
326  // Create temp file
327  KTemporaryFile *ktmpFile = new KTemporaryFile(m_componentData);
328  ktmpFile->setFileTemplate(m_fileName);
329  if (!ktmpFile->open()) {
330  delete ktmpFile;
331  return KLockFile::LockError;
332  }
333 
334  const QByteArray lckFile = QFile::encodeName(m_fileName);
335  const QByteArray tmpFile = QFile::encodeName(ktmpFile->fileName());
336  delete ktmpFile;
337 
338  // link to lock file
339  if (::link(lckFile, tmpFile) != 0)
340  return KLockFile::LockFail; // Try again later
341 
342  // check if link count increased with exactly one
343  // and if the lock file still matches
344  KDE_struct_stat st_buf1;
345  KDE_struct_stat st_buf2;
346  memcpy(&st_buf1, &statBuf, sizeof(KDE_struct_stat));
347  st_buf1.st_nlink++;
348  if ((KDE_lstat(tmpFile, &st_buf2) == 0) && st_buf1 == st_buf2)
349  {
350  if ((KDE_lstat(lckFile, &st_buf2) == 0) && st_buf1 == st_buf2)
351  {
352  // - - if yes, delete lock file, delete temp file, retry lock
353  qWarning("WARNING: deleting stale lockfile %s", lckFile.data());
354  ::unlink(lckFile);
355  ::unlink(tmpFile);
356  return KLockFile::LockOK;
357  }
358  }
359 
360  // SMBFS supports hardlinks by copying the file, as a result the above test will always fail
361  if (linkCountSupport)
362  {
363  linkCountSupport = testLinkCountSupport(tmpFile);
364  }
365 
366  if (!linkCountSupport)
367  {
368  // Without support for link counts we will have a little race condition
369  qWarning("WARNING: deleting stale lockfile %s", lckFile.data());
370  ::unlink(tmpFile);
371  if (::unlink(lckFile) < 0) {
372  qWarning("WARNING: Problem deleting stale lockfile %s: %s", lckFile.data(),
373  strerror(errno));
374  return KLockFile::LockFail;
375  }
376  return KLockFile::LockOK;
377  }
378 
379  // Failed to delete stale lock file
380  qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data());
381  ::unlink(tmpFile);
382  return KLockFile::LockFail;
383 }
384 
385 
386 KLockFile::LockResult KLockFile::lock(LockFlags options)
387 {
388  if (d->isLocked)
389  return KLockFile::LockOK;
390 
391  KLockFile::LockResult result;
392  int hardErrors = 5;
393  int n = 5;
394  while(true)
395  {
396  KDE_struct_stat st_buf;
397  // Try to create the lock file
398  result = d->lockFile(st_buf);
399 
400  if (result == KLockFile::LockOK)
401  {
402  d->staleTimer = QTime();
403  break;
404  }
405  else if (result == KLockFile::LockError)
406  {
407  d->staleTimer = QTime();
408  if (--hardErrors == 0)
409  {
410  break;
411  }
412  }
413  else // KLockFile::Fail -- there is already such a file present (e.g. left by a crashed app)
414  {
415  if (!d->staleTimer.isNull() && d->statBuf != st_buf)
416  d->staleTimer = QTime();
417 
418  if (d->staleTimer.isNull())
419  {
420  memcpy(&(d->statBuf), &st_buf, sizeof(KDE_struct_stat));
421  d->staleTimer.start();
422 
423  d->readLockFile();
424  }
425 
426  bool isStale = false;
427  if ((d->m_pid > 0) && !d->m_hostname.isEmpty())
428  {
429  // Check if hostname is us
430  char hostname[256];
431  hostname[0] = 0;
432  gethostname(hostname, 255);
433  hostname[255] = 0;
434 
435  if (d->m_hostname == QLatin1String(hostname))
436  {
437  // Check if pid still exists
438  int res = ::kill(d->m_pid, 0);
439  if ((res == -1) && (errno == ESRCH))
440  isStale = true; // pid does not exist
441  }
442  }
443  if (d->staleTimer.elapsed() > (d->staleTime*1000))
444  isStale = true;
445 
446  if (isStale)
447  {
448  if ((options & ForceFlag) == 0)
449  return KLockFile::LockStale;
450 
451  result = d->deleteStaleLock();
452 
453  if (result == KLockFile::LockOK)
454  {
455  // Lock deletion successful
456  d->staleTimer = QTime();
457  continue; // Now try to get the new lock
458  }
459  else if (result != KLockFile::LockFail)
460  {
461  return result;
462  }
463  }
464  }
465 
466  if (options & NoBlockFlag)
467  break;
468 
469  struct timeval tv;
470  tv.tv_sec = 0;
471  tv.tv_usec = n*((KRandom::random() % 200)+100);
472  if (n < 2000)
473  n = n * 2;
474 
475  select(0, 0, 0, 0, &tv);
476  }
477  if (result == LockOK)
478  d->isLocked = true;
479  return result;
480 }
481 
482 bool KLockFile::isLocked() const
483 {
484  return d->isLocked;
485 }
486 
487 void KLockFile::unlock()
488 {
489  if (d->isLocked)
490  {
491  ::unlink(QFile::encodeName(d->m_fileName));
492  if (d->mustCloseFd) {
493  close(d->m_file.handle());
494  d->mustCloseFd = false;
495  }
496  d->m_file.close();
497  d->m_pid = -1;
498  d->isLocked = false;
499  }
500 }
501 
502 bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname)
503 {
504  if (d->m_pid == -1)
505  return false;
506  pid = d->m_pid;
507  hostname = d->m_hostname;
508  appname = d->m_componentName;
509  return true;
510 }
KLockFile::LockError
The lock could not be acquired due to an error.
Definition: klockfile.h:65
krandom.h
KLockFile::KLockFile
KLockFile(const QString &file, const KComponentData &componentName=KGlobal::mainComponent())
Definition: klockfile_unix.cpp:116
KFileSystemType::Type
Type
Definition: kfilesystemtype_p.h:28
QByteArray
QFile::remove
bool remove()
KLockFile::~KLockFile
~KLockFile()
Destroys the object, releasing the lock if held.
Definition: klockfile_unix.cpp:122
QFile::setPermissions
bool setPermissions(QFlags< QFile::Permission > permissions)
operator==
static bool operator==(const KDE_struct_stat &st_buf1, const KDE_struct_stat &st_buf2)
Definition: klockfile_unix.cpp:141
KLockFile::LockResult
LockResult
Possible return values of the lock function.
Definition: klockfile.h:51
KLockFile::ForceFlag
Automatically remove a lock when a lock is detected that is stale for more than staleTime() seconds...
Definition: klockfile.h:84
pid
static pid_t pid
Definition: k3resolvermanager.cpp:249
KTemporaryFile
A QTemporaryFile that will save in the KDE temp directory.
Definition: ktemporaryfile.h:92
KLockFile::lock
LockResult lock(LockFlags flags=LockFlags())
Attempt to acquire the lock.
Definition: klockfile_unix.cpp:386
KLockFile::staleTime
int staleTime() const
Return the time in seconds after which a lock is considered stale The default is 30.
Definition: klockfile_unix.cpp:129
QTime
QFile
KLockFile::LockFail
The lock could not be acquired because it is held by another process.
Definition: klockfile.h:60
QTextStream
KFileSystemType::fileSystemType
Type fileSystemType(const QString &path)
KRandom::random
int random()
Generates a uniform random number.
Definition: krandom.cpp:32
QString::clear
void clear()
KLockFile::getLockInfo
bool getLockInfo(int &pid, QString &hostname, QString &appname)
Returns the pid, hostname and appname of the process holding the lock after the lock functon has retu...
Definition: klockfile_unix.cpp:502
KLockFile::setStaleTime
void setStaleTime(int _staleTime)
Set the time in seconds after which a lock is considered stale.
Definition: klockfile_unix.cpp:136
klockfile.h
kglobal.h
QString::number
QString number(int n, int base)
QString::fromLocal8Bit
QString fromLocal8Bit(const char *str, int size)
QByteArray::constData
const char * constData() const
KLockFile::LockStale
A stale lock has been detected.
Definition: klockfile.h:70
QTemporaryFile::setFileTemplate
void setFileTemplate(const QString &name)
KComponentData::componentName
QString componentName() const
Returns the name of the component.
Definition: kcomponentdata.cpp:226
QString
QFile::open
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
FIELD_EQ
#define FIELD_EQ(what)
testLinkCountSupport
static bool testLinkCountSupport(const QByteArray &fileName)
Definition: klockfile_unix.cpp:156
QTemporaryFile::fileName
QString fileName() const
KLockFile::LockOK
Lock was acquired successfully.
Definition: klockfile.h:55
ktemporaryfile.h
kfilesystemtype_p.h
KFileSystemType::Nfs
Definition: kfilesystemtype_p.h:30
KLockFile::isLocked
bool isLocked() const
Returns whether the lock is held or not.
Definition: klockfile_unix.cpp:482
QLatin1String
QByteArray::data
char * data()
KLockFile::unlock
void unlock()
Release the lock.
Definition: klockfile_unix.cpp:487
KLockFile::NoBlockFlag
Return immediately, do not wait for the lock to become available.
Definition: klockfile.h:77
operator!=
static bool operator!=(const KDE_struct_stat &st_buf1, const KDE_struct_stat &st_buf2)
Definition: klockfile_unix.cpp:150
kcomponentdata.h
QTemporaryFile::open
bool open()
KComponentData
Per component data.
Definition: kcomponentdata.h:46
QFile::encodeName
QByteArray encodeName(const QString &fileName)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:22:11 by doxygen 1.8.7 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 API Reference

Skip menu "kdelibs 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

Search



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