/**************************************************************************** ** ** Copyright (C) 2013 David Faure ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "private/qlockfile_p.h" #include "QtCore/qtemporaryfile.h" #include "QtCore/qcoreapplication.h" #include "QtCore/qfileinfo.h" #include "QtCore/qdebug.h" #include "QtCore/qdatetime.h" #include "private/qcore_unix_p.h" // qt_safe_open #include "private/qabstractfileengine_p.h" #include "private/qtemporaryfile_p.h" #include // flock #include // kill #include // kill #include // gethostname QT_BEGIN_NAMESPACE static QByteArray localHostName() // from QHostInfo::localHostName(), modified to return a QByteArray { QByteArray hostName(512, Qt::Uninitialized); if (gethostname(hostName.data(), hostName.size()) == -1) return QByteArray(); hostName.truncate(strlen(hostName.data())); return hostName; } // ### merge into qt_safe_write? static qint64 qt_write_loop(int fd, const char *data, qint64 len) { qint64 pos = 0; while (pos < len) { const qint64 ret = qt_safe_write(fd, data + pos, len - pos); if (ret == -1) // e.g. partition full return pos; pos += ret; } return pos; } int QLockFilePrivate::checkFcntlWorksAfterFlock() { #ifndef QT_NO_TEMPORARYFILE QTemporaryFile file; if (!file.open()) return 0; const int fd = file.d_func()->engine()->handle(); #if defined(LOCK_EX) && defined(LOCK_NB) if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs return 0; #endif struct flock flockData; flockData.l_type = F_WRLCK; flockData.l_whence = SEEK_SET; flockData.l_start = 0; flockData.l_len = 0; // 0 = entire file flockData.l_pid = getpid(); if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems return 0; return 1; #else return 0; #endif } static QBasicAtomicInt fcntlOK = Q_BASIC_ATOMIC_INITIALIZER(-1); /*! \internal Checks that the OS isn't using POSIX locks to emulate flock(). Mac OS X is one of those. */ static bool fcntlWorksAfterFlock() { int value = fcntlOK.load(); if (Q_UNLIKELY(value == -1)) { value = QLockFilePrivate::checkFcntlWorksAfterFlock(); fcntlOK.store(value); } return value == 1; } static bool setNativeLocks(int fd) { #if defined(LOCK_EX) && defined(LOCK_NB) if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs return false; #endif struct flock flockData; flockData.l_type = F_WRLCK; flockData.l_whence = SEEK_SET; flockData.l_start = 0; flockData.l_len = 0; // 0 = entire file flockData.l_pid = getpid(); if (fcntlWorksAfterFlock() && fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems return false; return true; } QLockFile::LockError QLockFilePrivate::tryLock_sys() { // Assemble data, to write in a single call to write // (otherwise we'd have to check every write call) // Use operator% from the fast builder to avoid multiple memory allocations. QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) % '\n' % qAppName().toUtf8() % '\n' % localHostName() % '\n'; const QByteArray lockFileName = QFile::encodeName(fileName); const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { switch (errno) { case EEXIST: return QLockFile::LockFailedError; case EACCES: case EROFS: return QLockFile::PermissionError; default: return QLockFile::UnknownError; } } // Ensure nobody else can delete the file while we have it if (!setNativeLocks(fd)) qWarning() << "setNativeLocks failed:" << strerror(errno); // We hold the lock, continue. fileHandle = fd; QLockFile::LockError error = QLockFile::NoError; if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) error = QLockFile::UnknownError; // partition full return error; } bool QLockFilePrivate::removeStaleLock() { const QByteArray lockFileName = QFile::encodeName(fileName); const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644); if (fd < 0) // gone already? return false; bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0); close(fd); return success; } bool QLockFilePrivate::isApparentlyStale() const { qint64 pid; QString hostname, appname; if (!getLockInfo(&pid, &hostname, &appname)) return false; if (hostname == QString::fromLocal8Bit(localHostName())) { if (::kill(pid, 0) == -1 && errno == ESRCH) return true; // PID doesn't exist anymore } const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime()); return staleLockTime > 0 && age > staleLockTime; } void QLockFile::unlock() { Q_D(QLockFile); if (!d->isLocked) return; close(d->fileHandle); d->fileHandle = -1; QFile::remove(d->fileName); d->lockError = QLockFile::NoError; d->isLocked = false; } QT_END_NAMESPACE