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

KDECore

  • 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
74class KLockFile::Private
75{
76public:
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
116KLockFile::KLockFile(const QString &file, const KComponentData &componentData)
117 : d(new Private(componentData))
118{
119 d->m_fileName = file;
120}
121
122KLockFile::~KLockFile()
123{
124 unlock();
125 delete d;
126}
127
128int
129KLockFile::staleTime() const
130{
131 return d->staleTime;
132}
133
134
135void
136KLockFile::setStaleTime(int _staleTime)
137{
138 d->staleTime = _staleTime;
139}
140
141static 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
150static bool operator!=( const KDE_struct_stat& st_buf1,
151 const KDE_struct_stat& st_buf2 )
152{
153 return !(st_buf1 == st_buf2);
154}
155
156static 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
168void 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
188void 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
207KLockFile::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
258bool KLockFile::Private::isNfs() const
259{
260 const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(m_fileName);
261 return fsType == KFileSystemType::Nfs;
262}
263
264KLockFile::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
273KLockFile::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
308KLockFile::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
321KLockFile::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
386KLockFile::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
482bool KLockFile::isLocked() const
483{
484 return d->isLocked;
485}
486
487void 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
502bool 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}
KComponentData
Per component data.
Definition: kcomponentdata.h:47
KComponentData::componentName
QString componentName() const
Returns the name of the component.
Definition: kcomponentdata.cpp:226
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::LockResult
LockResult
Possible return values of the lock function.
Definition: klockfile.h:51
KLockFile::LockStale
@ LockStale
A stale lock has been detected.
Definition: klockfile.h:70
KLockFile::LockFail
@ LockFail
The lock could not be acquired because it is held by another process.
Definition: klockfile.h:60
KLockFile::LockOK
@ LockOK
Lock was acquired successfully.
Definition: klockfile.h:55
KLockFile::LockError
@ LockError
The lock could not be acquired due to an error.
Definition: klockfile.h:65
KLockFile::unlock
void unlock()
Release the lock.
Definition: klockfile_unix.cpp:487
KLockFile::~KLockFile
~KLockFile()
Destroys the object, releasing the lock if held.
Definition: klockfile_unix.cpp:122
KLockFile::setStaleTime
void setStaleTime(int _staleTime)
Set the time in seconds after which a lock is considered stale.
Definition: klockfile_unix.cpp:136
KLockFile::KLockFile
KLockFile(const QString &file, const KComponentData &componentName=KGlobal::mainComponent())
Definition: klockfile_unix.cpp:116
KLockFile::lock
LockResult lock(LockFlags flags=LockFlags())
Attempt to acquire the lock.
Definition: klockfile_unix.cpp:386
KLockFile::isLocked
bool isLocked() const
Returns whether the lock is held or not.
Definition: klockfile_unix.cpp:482
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
KLockFile::NoBlockFlag
@ NoBlockFlag
Return immediately, do not wait for the lock to become available.
Definition: klockfile.h:77
KLockFile::ForceFlag
@ ForceFlag
Automatically remove a lock when a lock is detected that is stale for more than staleTime() seconds,...
Definition: klockfile.h:84
KTemporaryFile
A QTemporaryFile that will save in the KDE temp directory.
Definition: ktemporaryfile.h:93
QString
pid
static pid_t pid
Definition: k3resolvermanager.cpp:249
kcomponentdata.h
kfilesystemtype_p.h
kglobal.h
klockfile.h
operator!=
static bool operator!=(const KDE_struct_stat &st_buf1, const KDE_struct_stat &st_buf2)
Definition: klockfile_unix.cpp:150
testLinkCountSupport
static bool testLinkCountSupport(const QByteArray &fileName)
Definition: klockfile_unix.cpp:156
FIELD_EQ
#define FIELD_EQ(what)
operator==
static bool operator==(const KDE_struct_stat &st_buf1, const KDE_struct_stat &st_buf2)
Definition: klockfile_unix.cpp:141
krandom.h
ktemporaryfile.h
KFileSystemType::Type
Type
Definition: kfilesystemtype_p.h:29
KFileSystemType::Nfs
@ Nfs
Definition: kfilesystemtype_p.h:31
KFileSystemType::fileSystemType
Type fileSystemType(const QString &path)
KRandom::random
int random()
Generates a uniform random number.
Definition: krandom.cpp:32
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 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.14.38 API Reference

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