1 /*
2     Copyright © 2015-2019 by The qTox Project Contributors
3 
4     This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6     qTox is libre software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     qTox is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 
21 #include "profilelocker.h"
22 #include "src/persistence/settings.h"
23 #include <QDebug>
24 #include <QDir>
25 
26 /**
27  * @class ProfileLocker
28  * @brief Locks a Tox profile so that multiple instances can not use the same profile.
29  * Only one lock can be acquired at the same time, which means
30  * that there is little need for manually unlocking.
31  * The current lock will expire if you exit or acquire a new one.
32  */
33 
34 using namespace std;
35 
36 unique_ptr<QLockFile> ProfileLocker::lockfile;
37 QString ProfileLocker::curLockName;
38 
lockPathFromName(const QString & name)39 QString ProfileLocker::lockPathFromName(const QString& name)
40 {
41     return Settings::getInstance().getSettingsDirPath() + '/' + name + ".lock";
42 }
43 
44 /**
45  * @brief Checks if a profile is currently locked by *another* instance.
46  * If we own the lock, we consider it lockable.
47  * There is no guarantee that the result will still be valid by the
48  * time it is returned, this is provided on a best effort basis.
49  * @param profile Profile name to check.
50  * @return True, if profile locked, false otherwise.
51  */
isLockable(QString profile)52 bool ProfileLocker::isLockable(QString profile)
53 {
54     // If we already have the lock, it's definitely lockable
55     if (lockfile && curLockName == profile)
56         return true;
57 
58     QLockFile newLock(lockPathFromName(profile));
59     return newLock.tryLock();
60 }
61 
62 /**
63  * @brief Tries to acquire the lock on a profile, will not block.
64  * @param profile Profile to lock.
65  * @return Returns true if we already own the lock.
66  */
lock(QString profile)67 bool ProfileLocker::lock(QString profile)
68 {
69     if (lockfile && curLockName == profile)
70         return true;
71 
72     QLockFile* newLock = new QLockFile(lockPathFromName(profile));
73     newLock->setStaleLockTime(0);
74     if (!newLock->tryLock()) {
75         delete newLock;
76         return false;
77     }
78 
79     unlock();
80     lockfile.reset(newLock);
81     curLockName = profile;
82     return true;
83 }
84 
85 /**
86  * @brief Releases the lock on the current profile.
87  */
unlock()88 void ProfileLocker::unlock()
89 {
90     if (!lockfile)
91         return;
92 
93     lockfile->unlock();
94     lockfile.reset();
95     curLockName.clear();
96 }
97 
98 /**
99  * @brief Check that we actually own the lock.
100  * In case the file was deleted on disk, restore it.
101  * If we can't get a lock, exit qTox immediately.
102  * If we never had a lock in the first place, exit immediately.
103  */
assertLock()104 void ProfileLocker::assertLock()
105 {
106     if (!lockfile) {
107         qCritical() << "assertLock: We don't seem to own any lock!";
108         deathByBrokenLock();
109     }
110 
111     if (!QFile(lockPathFromName(curLockName)).exists()) {
112         QString tmp = curLockName;
113         unlock();
114         if (lock(tmp)) {
115             qCritical() << "assertLock: Lock file was lost, but could be restored";
116         } else {
117             qCritical() << "assertLock: Lock file was lost, and could *NOT* be restored";
118             deathByBrokenLock();
119         }
120     }
121 }
122 
123 /**
124  * @brief Print an error then exit immediately.
125  */
deathByBrokenLock()126 void ProfileLocker::deathByBrokenLock()
127 {
128     qCritical() << "Lock is *BROKEN*, exiting immediately";
129     abort();
130 }
131 
132 /**
133  * @brief Chacks, that profile locked.
134  * @return Returns true if we're currently holding a lock.
135  */
hasLock()136 bool ProfileLocker::hasLock()
137 {
138     return lockfile.operator bool();
139 }
140 
141 /**
142  * @brief Get current locked profile name.
143  * @return Return the name of the currently loaded profile, a null string if there is none.
144  */
getCurLockName()145 QString ProfileLocker::getCurLockName()
146 {
147     if (lockfile)
148         return curLockName;
149     else
150         return QString();
151 }
152