You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/kdecore/klockfile.cpp

377 lines
9.8 KiB

/*
This file is part of the KDE libraries
Copyright (c) 2004 Waldo Bastian <bastian@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <klockfile.h>
#include <config.h>
#include <sys/types.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <qfile.h>
#include <qtextstream.h>
#include <kde_file.h>
#include <kapplication.h>
#include <kcmdlineargs.h>
#include <kglobal.h>
#include <ktempfile.h>
// TODO: http://www.spinnaker.de/linux/nfs-locking.html
// TODO: Make regression test
class KLockFile::KLockFilePrivate {
public:
QString file;
int staleTime;
bool isLocked;
bool recoverLock;
bool linkCountSupport;
QTime staleTimer;
KDE_struct_stat statBuf;
int pid;
QString hostname;
QString instance;
QString lockRecoverFile;
};
// 30 seconds
KLockFile::KLockFile(const QString &file)
{
d = new KLockFilePrivate();
d->file = file;
d->staleTime = 30;
d->isLocked = false;
d->recoverLock = false;
d->linkCountSupport = true;
}
KLockFile::~KLockFile()
{
unlock();
delete d;
}
int
KLockFile::staleTime() const
{
return d->staleTime;
}
void
KLockFile::setStaleTime(int _staleTime)
{
d->staleTime = _staleTime;
}
static bool statResultIsEqual(KDE_struct_stat &st_buf1, KDE_struct_stat &st_buf2)
{
#define FIELD_EQ(what) (st_buf1.what == st_buf2.what)
return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) &&
FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink);
#undef FIELD_EQ
}
static bool testLinkCountSupport(const QCString &fileName)
{
KDE_struct_stat st_buf;
// Check if hardlinks raise the link count at all?
::link( fileName, fileName+".test" );
int result = KDE_lstat( fileName, &st_buf );
::unlink( fileName+".test" );
return ((result == 0) && (st_buf.st_nlink == 2));
}
static KLockFile::LockResult lockFile(const QString &lockFile, KDE_struct_stat &st_buf, bool &linkCountSupport)
{
QCString lockFileName = QFile::encodeName( lockFile );
int result = KDE_lstat( lockFileName, &st_buf );
if (result == 0)
return KLockFile::LockFail;
KTempFile uniqueFile(lockFile, QString::null, 0644);
uniqueFile.setAutoDelete(true);
if (uniqueFile.status() != 0)
return KLockFile::LockError;
char hostname[256];
hostname[0] = 0;
gethostname(hostname, 255);
hostname[255] = 0;
QCString instanceName = KCmdLineArgs::appName();
(*(uniqueFile.textStream())) << QString::number(getpid()) << endl
<< instanceName << endl
<< hostname << endl;
uniqueFile.close();
QCString uniqueName = QFile::encodeName( uniqueFile.name() );
#ifdef Q_OS_UNIX
// Create lock file
result = ::link( uniqueName, lockFileName );
if (result != 0)
return KLockFile::LockError;
if (!linkCountSupport)
return KLockFile::LockOK;
#else
//TODO for win32
return KLockFile::LockOK;
#endif
KDE_struct_stat st_buf2;
result = KDE_lstat( uniqueName, &st_buf2 );
if (result != 0)
return KLockFile::LockError;
result = KDE_lstat( lockFileName, &st_buf );
if (result != 0)
return KLockFile::LockError;
if (!statResultIsEqual(st_buf, st_buf2) || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode))
{
// SMBFS supports hardlinks by copying the file, as a result the above test will always fail
if ((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1) && (st_buf.st_ino != st_buf2.st_ino))
{
linkCountSupport = testLinkCountSupport(uniqueName);
if (!linkCountSupport)
return KLockFile::LockOK; // Link count support is missing... assume everything is OK.
}
return KLockFile::LockFail;
}
return KLockFile::LockOK;
}
static KLockFile::LockResult deleteStaleLock(const QString &lockFile, KDE_struct_stat &st_buf, bool &linkCountSupport)
{
// This is dangerous, we could be deleting a new lock instead of
// the old stale one, let's be very careful
// Create temp file
KTempFile ktmpFile(lockFile);
if (ktmpFile.status() != 0)
return KLockFile::LockError;
QCString lckFile = QFile::encodeName(lockFile);
QCString tmpFile = QFile::encodeName(ktmpFile.name());
ktmpFile.close();
ktmpFile.unlink();
#ifdef Q_OS_UNIX
// link to lock file
if (::link(lckFile, tmpFile) != 0)
return KLockFile::LockFail; // Try again later
#else
//TODO for win32
return KLockFile::LockOK;
#endif
// check if link count increased with exactly one
// and if the lock file still matches
KDE_struct_stat st_buf1;
KDE_struct_stat st_buf2;
memcpy(&st_buf1, &st_buf, sizeof(KDE_struct_stat));
st_buf1.st_nlink++;
if ((KDE_lstat(tmpFile, &st_buf2) == 0) && statResultIsEqual(st_buf1, st_buf2))
{
if ((KDE_lstat(lckFile, &st_buf2) == 0) && statResultIsEqual(st_buf1, st_buf2))
{
// - - if yes, delete lock file, delete temp file, retry lock
qWarning("WARNING: deleting stale lockfile %s", lckFile.data());
::unlink(lckFile);
::unlink(tmpFile);
return KLockFile::LockOK;
}
}
// SMBFS supports hardlinks by copying the file, as a result the above test will always fail
if (linkCountSupport)
{
linkCountSupport = testLinkCountSupport(tmpFile);
}
if (!linkCountSupport &&
(KDE_lstat(lckFile, &st_buf2) == 0) &&
statResultIsEqual(st_buf, st_buf2))
{
// Without support for link counts we will have a little race condition
qWarning("WARNING: deleting stale lockfile %s", lckFile.data());
::unlink(lckFile);
::unlink(tmpFile);
return KLockFile::LockOK;
}
// Failed to delete stale lock file
qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data());
::unlink(tmpFile);
return KLockFile::LockFail;
}
KLockFile::LockResult KLockFile::lock(int options)
{
if (d->isLocked)
return KLockFile::LockOK;
KLockFile::LockResult result;
int hardErrors = 5;
int n = 5;
while(true)
{
KDE_struct_stat st_buf;
result = lockFile(d->file, st_buf, d->linkCountSupport);
if (result == KLockFile::LockOK)
{
d->staleTimer = QTime();
break;
}
else if (result == KLockFile::LockError)
{
d->staleTimer = QTime();
if (--hardErrors == 0)
{
break;
}
}
else // KLockFile::Fail
{
if (!d->staleTimer.isNull() && !statResultIsEqual(d->statBuf, st_buf))
d->staleTimer = QTime();
if (!d->staleTimer.isNull())
{
bool isStale = false;
if ((d->pid > 0) && !d->hostname.isEmpty())
{
// Check if hostname is us
char hostname[256];
hostname[0] = 0;
gethostname(hostname, 255);
hostname[255] = 0;
if (d->hostname == hostname)
{
// Check if pid still exists
int res = ::kill(d->pid, 0);
if ((res == -1) && (errno == ESRCH))
isStale = true;
}
}
if (d->staleTimer.elapsed() > (d->staleTime*1000))
isStale = true;
if (isStale)
{
if ((options & LockForce) == 0)
return KLockFile::LockStale;
result = deleteStaleLock(d->file, d->statBuf, d->linkCountSupport);
if (result == KLockFile::LockOK)
{
// Lock deletion successful
d->staleTimer = QTime();
continue; // Now try to get the new lock
}
else if (result != KLockFile::LockFail)
{
return result;
}
}
}
else
{
memcpy(&(d->statBuf), &st_buf, sizeof(KDE_struct_stat));
d->staleTimer.start();
d->pid = -1;
d->hostname = QString::null;
d->instance = QString::null;
QFile file(d->file);
if (file.open(IO_ReadOnly))
{
QTextStream ts(&file);
if (!ts.atEnd())
d->pid = ts.readLine().toInt();
if (!ts.atEnd())
d->instance = ts.readLine();
if (!ts.atEnd())
d->hostname = ts.readLine();
}
}
}
if ((options & LockNoBlock) != 0)
break;
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = n*((KApplication::random() % 200)+100);
if (n < 2000)
n = n * 2;
#ifdef Q_OS_UNIX
select(0, 0, 0, 0, &tv);
#else
//TODO for win32
#endif
}
if (result == LockOK)
d->isLocked = true;
return result;
}
bool KLockFile::isLocked() const
{
return d->isLocked;
}
void KLockFile::unlock()
{
if (d->isLocked)
{
::unlink(QFile::encodeName(d->file));
d->isLocked = false;
}
}
bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname)
{
if (d->pid == -1)
return false;
pid = d->pid;
hostname = d->hostname;
appname = d->instance;
return true;
}