qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
ipc.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2014-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 #include "src/ipc.h"
21 #include <QCoreApplication>
22 #include <QDebug>
23 #include <QThread>
24 
25 #include <chrono>
26 #include <ctime>
27 #include <random>
28 #include <stdlib.h>
29 #ifndef _MSC_VER
30 #include <unistd.h>
31 #endif
32 
33 namespace
34 {
35 #ifdef Q_OS_WIN
36  const char* getCurUsername()
37  {
38  return getenv("USERNAME");
39  }
40 #else
41  const char* getCurUsername()
42  {
43  return getenv("USER");
44  }
45 #endif
46 
47  QString getIpcKey()
48  {
49  auto* user = getCurUsername();
50  if (!user)
51  {
52  qWarning() << "Failed to get current username. Will use a global IPC.";
53  user = "";
54  }
55  return QString("qtox-" IPC_PROTOCOL_VERSION "-") + user;
56  }
57 } // namespace
58 
72 IPC::IPC(uint32_t profileId)
73  : profileId{profileId}
74  , globalMemory{getIpcKey()}
75 {
76  qRegisterMetaType<IPCEventHandler>("IPCEventHandler");
77 
78  timer.setInterval(EVENT_TIMER_MS);
79  timer.setSingleShot(true);
80  connect(&timer, &QTimer::timeout, this, &IPC::processEvents);
81 
82  // The first started instance gets to manage the shared memory by taking ownership
83  // Every time it processes events it updates the global shared timestamp "lastProcessed"
84  // If the timestamp isn't updated, that's a timeout and someone else can take ownership
85  // This is a safety measure, in case one of the clients crashes
86  // If the owner exits normally, it can set the timestamp to 0 first to immediately give
87  // ownership
88 
89  // use the clock rather than std::random_device because std::random_device may return constant values, and does
90  // under mingw on Windows. We don't actually need cryptographic guarantees, so using the clock in all cases.
91  static std::mt19937 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count());
92  std::uniform_int_distribution<uint64_t> distribution;
93  globalId = distribution(rng);
94  qDebug() << "Our global IPC ID is " << globalId;
95  if (globalMemory.create(sizeof(IPCMemory))) {
96  if (globalMemory.lock()) {
97  IPCMemory* mem = global();
98  memset(mem, 0, sizeof(IPCMemory));
99  mem->globalId = globalId;
100  mem->lastProcessed = time(nullptr);
101  globalMemory.unlock();
102  } else {
103  qWarning() << "Couldn't lock to take ownership";
104  }
105  } else if (globalMemory.attach()) {
106  qDebug() << "Attaching to the global shared memory";
107  } else {
108  qDebug() << "Failed to attach to the global shared memory, giving up. Error:"
109  << globalMemory.error();
110  return; // We won't be able to do any IPC without being attached, let's get outta here
111  }
112 
113  processEvents();
114 }
115 
117 {
118  if (!globalMemory.lock()) {
119  qWarning() << "Failed to lock in ~IPC";
120  return;
121  }
122 
123  if (isCurrentOwnerNoLock()) {
124  global()->globalId = 0;
125  }
126  globalMemory.unlock();
127 }
128 
136 time_t IPC::postEvent(const QString& name, const QByteArray& data, uint32_t dest)
137 {
138  QByteArray binName = name.toUtf8();
139  if (binName.length() > static_cast<int32_t>(sizeof(IPCEvent::name))) {
140  return 0;
141  }
142 
143  if (data.length() > static_cast<int32_t>(sizeof(IPCEvent::data))) {
144  return 0;
145  }
146 
147  if (!globalMemory.lock()) {
148  qDebug() << "Failed to lock in postEvent()";
149  return 0;
150  }
151 
152  IPCEvent* evt = nullptr;
153  IPCMemory* mem = global();
154  time_t result = 0;
155 
156  for (uint32_t i = 0; !evt && i < EVENT_QUEUE_SIZE; ++i) {
157  if (mem->events[i].posted == 0) {
158  evt = &mem->events[i];
159  }
160  }
161 
162  if (evt) {
163  memset(evt, 0, sizeof(IPCEvent));
164  memcpy(evt->name, binName.constData(), binName.length());
165  memcpy(evt->data, data.constData(), data.length());
166  mem->lastEvent = evt->posted = result = qMax(mem->lastEvent + 1, time(nullptr));
167  evt->dest = dest;
168  evt->sender = getpid();
169  qDebug() << "postEvent " << name << "to" << dest;
170  }
171 
172  globalMemory.unlock();
173  return result;
174 }
175 
177 {
178  if (globalMemory.lock()) {
179  const bool isOwner = isCurrentOwnerNoLock();
180  globalMemory.unlock();
181  return isOwner;
182  } else {
183  qWarning() << "isCurrentOwner failed to lock, returning false";
184  return false;
185  }
186 }
187 
192 void IPC::registerEventHandler(const QString& name, IPCEventHandler handler)
193 {
194  eventHandlers[name] = handler;
195 }
196 
197 bool IPC::isEventAccepted(time_t time)
198 {
199  bool result = false;
200  if (!globalMemory.lock()) {
201  return result;
202  }
203 
204  if (difftime(global()->lastProcessed, time) > 0) {
205  IPCMemory* mem = global();
206  for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
207  if (mem->events[i].posted == time && mem->events[i].processed) {
208  result = mem->events[i].accepted;
209  break;
210  }
211  }
212  }
213  globalMemory.unlock();
214 
215  return result;
216 }
217 
218 bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout /*=-1*/)
219 {
220  bool result = false;
221  time_t start = time(nullptr);
222  forever
223  {
224  result = isEventAccepted(postTime);
225  if (result || (timeout > 0 && difftime(time(nullptr), start) >= timeout)) {
226  break;
227  }
228 
229  qApp->processEvents();
230  QThread::msleep(0);
231  }
232  return result;
233 }
234 
235 bool IPC::isAttached() const
236 {
237  return globalMemory.isAttached();
238 }
239 
240 void IPC::setProfileId(uint32_t profileId)
241 {
242  this->profileId = profileId;
243 }
244 
250 {
251  IPCMemory* mem = global();
252  for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
253  IPCEvent* evt = &mem->events[i];
254 
255  // Garbage-collect events that were not processed in EVENT_GC_TIMEOUT
256  // and events that were processed and EVENT_GC_TIMEOUT passed after
257  // so sending instance has time to react to those events.
258  if ((evt->processed && difftime(time(nullptr), evt->processed) > EVENT_GC_TIMEOUT)
259  || (!evt->processed && difftime(time(nullptr), evt->posted) > EVENT_GC_TIMEOUT)) {
260  memset(evt, 0, sizeof(IPCEvent));
261  }
262 
263  if (evt->posted && !evt->processed && evt->sender != getpid()
264  && (evt->dest == profileId || (evt->dest == 0 && isCurrentOwnerNoLock()))) {
265  return evt;
266  }
267  }
268 
269  return nullptr;
270 }
271 
272 bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
273 {
274  bool result = false;
275  if (QThread::currentThread() == qApp->thread()) {
276  result = handler(arg);
277  } else {
278  QMetaObject::invokeMethod(this, "runEventHandler", Qt::BlockingQueuedConnection,
279  Q_RETURN_ARG(bool, result), Q_ARG(IPCEventHandler, handler),
280  Q_ARG(const QByteArray&, arg));
281  }
282 
283  return result;
284 }
285 
287 {
288  if (!globalMemory.lock()) {
289  timer.start();
290  return;
291  }
292 
293  IPCMemory* mem = global();
294 
295  if (mem->globalId == globalId) {
296  // We're the owner, let's process those events
297  mem->lastProcessed = time(nullptr);
298  } else {
299  // Only the owner processes events. But if the previous owner's dead, we can take
300  // ownership now
301  if (difftime(time(nullptr), mem->lastProcessed) >= OWNERSHIP_TIMEOUT_S) {
302  qDebug() << "Previous owner timed out, taking ownership" << mem->globalId << "->"
303  << globalId;
304  // Ignore events that were not meant for this instance
305  memset(mem, 0, sizeof(IPCMemory));
306  mem->globalId = globalId;
307  mem->lastProcessed = time(nullptr);
308  }
309  // Non-main instance is limited to events destined for specific profile it runs
310  }
311 
312  while (IPCEvent* evt = fetchEvent()) {
313  QString name = QString::fromUtf8(evt->name);
314  auto it = eventHandlers.find(name);
315  if (it != eventHandlers.end()) {
316  evt->accepted = runEventHandler(it.value(), evt->data);
317  qDebug() << "Processed event:" << name << "posted:" << evt->posted
318  << "accepted:" << evt->accepted;
319  if (evt->dest == 0) {
320  // Global events should be processed only by instance that accepted event.
321  // Otherwise global
322  // event would be consumed by very first instance that gets to check it.
323  if (evt->accepted) {
324  evt->processed = time(nullptr);
325  }
326  } else {
327  evt->processed = time(nullptr);
328  }
329  } else {
330  qDebug() << "Received event:" << name << "without handler";
331  qDebug() << "Available handlers:" << eventHandlers.keys();
332  }
333  }
334 
335  globalMemory.unlock();
336  timer.start();
337 }
338 
344 {
345  const void* const data = globalMemory.data();
346  if (!data) {
347  qWarning() << "isCurrentOwnerNoLock failed to access the memory, returning false";
348  return false;
349  }
350  return (*static_cast<const uint64_t*>(data) == globalId);
351 }
352 
354 {
355  return static_cast<IPCMemory*>(globalMemory.data());
356 }
IPC::IPCEvent::processed
time_t processed
Definition: ipc.h:55
IPC::isCurrentOwner
bool isCurrentOwner()
Definition: ipc.cpp:176
IPC::IPCMemory::events
IPCEvent events[IPC::EVENT_QUEUE_SIZE]
Definition: ipc.h:66
IPC::IPCEvent::accepted
bool accepted
Definition: ipc.h:57
IPC::isAttached
bool isAttached() const
Definition: ipc.cpp:235
IPC::globalMemory
QSharedMemory globalMemory
Definition: ipc.h:90
IPC::IPCEvent
Definition: ipc.h:48
IPC::IPC
IPC(uint32_t profileId)
Definition: ipc.cpp:72
IPC::setProfileId
void setProfileId(uint32_t profileId)
Definition: ipc.cpp:240
IPC::IPCMemory
Definition: ipc.h:61
IPC::runEventHandler
bool runEventHandler(IPCEventHandler handler, const QByteArray &arg)
Definition: ipc.cpp:272
IPC::OWNERSHIP_TIMEOUT_S
static const int OWNERSHIP_TIMEOUT_S
Definition: ipc.h:42
IPC::IPCEvent::posted
time_t posted
Definition: ipc.h:54
IPC::IPCEvent::name
char name[16]
Definition: ipc.h:52
IPC::timer
QTimer timer
Definition: ipc.h:87
IPC::waitUntilAccepted
bool waitUntilAccepted(time_t time, int32_t timeout=-1)
Definition: ipc.cpp:218
IPC::processEvents
void processEvents()
Definition: ipc.cpp:286
IPC::IPCMemory::lastProcessed
time_t lastProcessed
Definition: ipc.h:65
IPC::fetchEvent
IPCEvent * fetchEvent()
Only called when global memory IS LOCKED.
Definition: ipc.cpp:249
IPC::registerEventHandler
void registerEventHandler(const QString &name, IPCEventHandler handler)
Register a handler for an IPC event.
Definition: ipc.cpp:192
IPC::IPCMemory::lastEvent
time_t lastEvent
Definition: ipc.h:64
ipc.h
IPC::global
IPCMemory * global()
Definition: ipc.cpp:353
IPC::profileId
uint32_t profileId
Definition: ipc.h:89
IPC::IPCMemory::globalId
uint64_t globalId
Definition: ipc.h:63
IPC::isEventAccepted
bool isEventAccepted(time_t time)
Definition: ipc.cpp:197
IPC::EVENT_GC_TIMEOUT
static const int EVENT_GC_TIMEOUT
Definition: ipc.h:40
IPC::~IPC
~IPC()
Definition: ipc.cpp:116
IPC::isCurrentOwnerNoLock
bool isCurrentOwnerNoLock()
Only called when global memory IS LOCKED.
Definition: ipc.cpp:343
IPC::eventHandlers
QMap< QString, IPCEventHandler > eventHandlers
Definition: ipc.h:91
IPC::IPCEvent::dest
uint32_t dest
Definition: ipc.h:50
IPC::IPCEvent::sender
int32_t sender
Definition: ipc.h:51
IPC::postEvent
time_t postEvent(const QString &name, const QByteArray &data=QByteArray(), uint32_t dest=0)
Post IPC event.
Definition: ipc.cpp:136
IPC::IPCEvent::data
char data[128]
Definition: ipc.h:53
IPC::EVENT_QUEUE_SIZE
static const int EVENT_QUEUE_SIZE
Definition: ipc.h:41
IPC::globalId
uint64_t globalId
Definition: ipc.h:88
IPC_PROTOCOL_VERSION
#define IPC_PROTOCOL_VERSION
Definition: ipc.h:32
IPCEventHandler
std::function< bool(const QByteArray &)> IPCEventHandler
Definition: ipc.h:30