|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the Qt Mobility Components. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "qsystemreadwritelock_p.h" |
|
43 #include <QFile> |
|
44 #include <sys/sem.h> |
|
45 #include <fcntl.h> |
|
46 #include <errno.h> |
|
47 #include <unistd.h> |
|
48 #include <QRegExp> |
|
49 #include <QCryptographicHash> |
|
50 #include <QDir> |
|
51 |
|
52 QTM_BEGIN_NAMESPACE |
|
53 |
|
54 class QSystemReadWriteLockPrivate |
|
55 { |
|
56 public: |
|
57 enum SemIndex { |
|
58 ActiveReaders = 0, |
|
59 TotalWriters, |
|
60 ActiveWriterSem, |
|
61 NumInstances |
|
62 }; |
|
63 |
|
64 QSystemReadWriteLockPrivate():id(-1),semId(-1), |
|
65 key(QString()),keyFileName(QString()), |
|
66 error(QSystemReadWriteLock::NoError), |
|
67 errorString(QString()){}; |
|
68 void setError(const QString &errorString); |
|
69 |
|
70 key_t id; |
|
71 int semId; |
|
72 QString key; |
|
73 QString keyFileName; |
|
74 |
|
75 QSystemReadWriteLock::SystemReadWriteLockError error; |
|
76 QString errorString; |
|
77 }; |
|
78 |
|
79 void QSystemReadWriteLockPrivate::setError(const QString &_errorString) |
|
80 { |
|
81 errorString = _errorString; |
|
82 switch (errno) { |
|
83 case EPERM: |
|
84 case EACCES: |
|
85 error = QSystemReadWriteLock::PermissionDenied; |
|
86 break; |
|
87 case ERANGE: |
|
88 case ENOMEM: |
|
89 case ENOSPC: |
|
90 error = QSystemReadWriteLock::OutOfResources; |
|
91 break; |
|
92 case EIDRM: |
|
93 error = QSystemReadWriteLock::NotFound; |
|
94 break; |
|
95 default: |
|
96 error = QSystemReadWriteLock::UnknownError; |
|
97 } |
|
98 } |
|
99 |
|
100 /* |
|
101 This function is taken from qcore_unix_p.h(qt_safe_open) |
|
102 If/when QSystemReadWriteLock is integrated into Qt, |
|
103 this function defintion may replaced by the sharedmemory |
|
104 definition. |
|
105 */ |
|
106 static inline int safe_open(const char *pathname, int flags, mode_t mode = 0777) |
|
107 { |
|
108 #ifdef O_CLOEXEC |
|
109 flags |= O_CLOEXEC; |
|
110 #endif |
|
111 register int fd; |
|
112 |
|
113 do { |
|
114 fd = ::open(pathname, flags, mode); |
|
115 } while (fd == -1 && errno == EINTR); |
|
116 |
|
117 // unknown flags are ignored, so we have no way of verifying if |
|
118 // O_CLOEXEC was accepted |
|
119 if (fd != -1) |
|
120 ::fcntl(fd, F_SETFD, FD_CLOEXEC); |
|
121 return fd; |
|
122 } |
|
123 |
|
124 /* |
|
125 This function is taken from qsharedmemory_unix.cpp. |
|
126 If/when QSystemReadWriteLock is integrated into Qt, |
|
127 this function defintion may replaced by the sharedmemory |
|
128 definition. |
|
129 */ |
|
130 int createUnixKeyFile(const QString &fileName) |
|
131 { |
|
132 if (QFile::exists(fileName)) { |
|
133 return 0; |
|
134 } |
|
135 |
|
136 int fd = safe_open(QFile::encodeName(fileName).constData(), |
|
137 O_EXCL | O_CREAT | O_RDWR, 0640); |
|
138 |
|
139 if (-1 == fd) { |
|
140 if (errno == EEXIST) |
|
141 return 0; |
|
142 return -1; |
|
143 } else { |
|
144 close(fd); |
|
145 } |
|
146 return 1; |
|
147 } |
|
148 |
|
149 /* |
|
150 This function is taken from qsharedmemory_unix.cpp. |
|
151 If/when QSystemReadWriteLock is integrated into Qt, |
|
152 the function definition may replaced by the sharedmemory |
|
153 definition. |
|
154 */ |
|
155 QString makePlatformSafeKey(const QString &key, |
|
156 const QString &prefix) |
|
157 { |
|
158 if (key.isEmpty()) |
|
159 return QString(); |
|
160 |
|
161 QString result = prefix; |
|
162 |
|
163 QString part1 = key; |
|
164 part1.remove(QRegExp(QLatin1String("[^A-Za-z]"))); |
|
165 result.append(part1); |
|
166 |
|
167 QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex(); |
|
168 result.append(QLatin1String(hex)); |
|
169 return QDir::tempPath() + QLatin1Char('/') + result; |
|
170 } |
|
171 |
|
172 /* |
|
173 \class QSystemReadWriteLock |
|
174 |
|
175 \brief The QSystemReadWriteLock class provides read-write locking between |
|
176 processes. |
|
177 |
|
178 A read-write lock is a synchronization tool for protecting resources that can |
|
179 be accessed for reading and writing. This type of lock is useful if you want |
|
180 to allow multiple processes/threads to have simultaneous read-only access, but as soon |
|
181 as one thread wants to write to the resource, all other threads must be |
|
182 blocked until the writing is complete. |
|
183 |
|
184 QSystemReadWriteLock behaves much like the QReadWriteLock class, but it also |
|
185 works across multiple processes (although it also works perfectly well, |
|
186 albeit slightly less efficiently, in a single process). The system |
|
187 resources are cleaned up when the last QSystemReadWriteLock instance |
|
188 is destroyed. |
|
189 |
|
190 The behaviour of QSystemReadWriteLock is such that writers will |
|
191 take precedence over readers. When writers are waiting, any new |
|
192 readers must wait until all writers complete. That is, writers |
|
193 can starve readers. |
|
194 */ |
|
195 |
|
196 /* |
|
197 Implementation Details: |
|
198 |
|
199 The QSystemReadWriteLock class uses Linux kernel semaphores to synchronize |
|
200 access between readers and writers. |
|
201 |
|
202 4 semaphores are used in total. |
|
203 activeReaders - number of active readers |
|
204 TotalWriters - number of active and pending writers |
|
205 |
|
206 ActiveWriterSem - indicates whether a writer is active |
|
207 |
|
208 NumInstances - counts the number of instances of QSystemReadWriteLock |
|
209 objects there for a given key. This is needed |
|
210 to determine whether system resources are |
|
211 safe to clean up during an instance's destruction. |
|
212 |
|
213 activeReaders, TotalWriters and NumInstance are used as counters and |
|
214 not in the way typical semaphores are used. e.g.a value of 5 for |
|
215 activeReaders indicates that there are currently 5 readers. |
|
216 |
|
217 ActiveWriterSem is used as a typical semaphore. A value of 1 |
|
218 means there's an free slot for a writer to become active. |
|
219 */ |
|
220 |
|
221 /* |
|
222 Construct a system read write lock from the provided \a key. |
|
223 |
|
224 The \a mode parameter is only used in Unix systems to handle the case |
|
225 where underlying system resources such as semaphores survive a process |
|
226 crash. In this case, the next process to instatiate a lock will |
|
227 get the resources that survived the crash, and unless \mode is |
|
228 Create, the resources will not be reset. |
|
229 */ |
|
230 QSystemReadWriteLock::QSystemReadWriteLock(const QString &key, AccessMode mode) |
|
231 { |
|
232 d = new QSystemReadWriteLockPrivate; |
|
233 d->key = key; |
|
234 d->keyFileName = makePlatformSafeKey(key, QLatin1String("qipc_systemsem_")); |
|
235 |
|
236 int built = createUnixKeyFile(d->keyFileName); |
|
237 if (built == -1) { |
|
238 d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: " |
|
239 "unable to make key file for key: %1(%2)") |
|
240 .arg(key).arg(QString::fromLatin1(strerror(errno)))); |
|
241 return; |
|
242 } |
|
243 |
|
244 // Get the unix key for the created file |
|
245 d->id = ftok(QFile::encodeName(d->keyFileName).constData(), 'Q'); |
|
246 if (d->id == -1) { |
|
247 d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: " |
|
248 "ftok failed for key %1(%2)") |
|
249 .arg(key).arg(QString::fromLatin1(strerror(errno)))); |
|
250 return; |
|
251 } |
|
252 |
|
253 d->semId = ::semget(d->id, 4, IPC_CREAT | IPC_EXCL | 0666); |
|
254 bool created = false; |
|
255 if (d->semId == -1) { |
|
256 if (errno == EEXIST) { |
|
257 d->semId = ::semget(d->id, 4, |
|
258 (mode == QSystemReadWriteLock::Create)?IPC_CREAT|0666:0); |
|
259 if (d->semId == -1) { |
|
260 d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: " |
|
261 "Unable to access semaphore set for key %1(%2)") |
|
262 .arg(key).arg(QString::fromLatin1(strerror(errno)))); |
|
263 return; |
|
264 } |
|
265 } else { |
|
266 d->setError(QObject::tr("QSystemReadWriteLock:QSystemReadWriteLock: " |
|
267 "Unable to access semaphore set for key %1(%2)") |
|
268 .arg(key).arg(QString::fromLatin1(strerror(errno)))); |
|
269 return; |
|
270 } |
|
271 } else { |
|
272 created = true; |
|
273 } |
|
274 |
|
275 Q_ASSERT(d->semId != -1); |
|
276 if (created || mode == QSystemReadWriteLock::Create) { |
|
277 typedef union { |
|
278 int val; |
|
279 struct semid_ds *buf; |
|
280 ushort *array; |
|
281 }semun; |
|
282 |
|
283 semun counterInitVal; //initial value for a "counter" semaphores |
|
284 counterInitVal.val = 0; |
|
285 |
|
286 semun activeWriterInitVal;//initial value for the "active writer" semaphore |
|
287 activeWriterInitVal.val = 1; |
|
288 |
|
289 if (-1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::ActiveReaders, |
|
290 SETVAL, counterInitVal) || |
|
291 -1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::TotalWriters, |
|
292 SETVAL, counterInitVal) || |
|
293 -1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::ActiveWriterSem, |
|
294 SETVAL, activeWriterInitVal) || |
|
295 -1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::NumInstances, |
|
296 SETVAL, counterInitVal)) |
|
297 { |
|
298 d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: " |
|
299 "Unable to reset semaphore set for key %1(%2)") |
|
300 .arg(key).arg(QString::fromLatin1(strerror(errno)))); |
|
301 QFile::remove(d->keyFileName); |
|
302 ::semctl(d->semId, 0, IPC_RMID); |
|
303 d->semId = -1; |
|
304 return; |
|
305 } |
|
306 } |
|
307 |
|
308 struct sembuf op; |
|
309 op.sem_num = QSystemReadWriteLockPrivate::NumInstances; |
|
310 op.sem_op = 1; |
|
311 op.sem_flg = SEM_UNDO; |
|
312 int semoprv = ::semop(d->semId, &op, 1); |
|
313 if (semoprv == -1) { |
|
314 d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: " |
|
315 "Unable to increment NumInstances semaphore " |
|
316 "for key%1(%2)").arg(key).arg(QString::fromLatin1(strerror(errno)))); |
|
317 d->semId = -1; |
|
318 return; |
|
319 } |
|
320 } |
|
321 |
|
322 /* |
|
323 Destroy the lock instance. The last QSystemReadWriteLock instance |
|
324 for a particular key will destroy the underlying system resources. |
|
325 */ |
|
326 QSystemReadWriteLock::~QSystemReadWriteLock() |
|
327 { |
|
328 if (d->semId != -1) { |
|
329 //decrement and check that there are 0 instances |
|
330 //be aware these 2 operations occur together atomically |
|
331 struct sembuf ops[2]; |
|
332 ops[0].sem_num = QSystemReadWriteLockPrivate::NumInstances; |
|
333 ops[0].sem_op = -1; |
|
334 ops[0].sem_flg = SEM_UNDO | IPC_NOWAIT; |
|
335 |
|
336 ops[1].sem_num = QSystemReadWriteLockPrivate::NumInstances; |
|
337 ops[1].sem_op = 0; |
|
338 ops[1].sem_flg = IPC_NOWAIT; |
|
339 |
|
340 //if successful, then delete the semaphore set |
|
341 int semoprv = ::semop(d->semId, ops, 2); |
|
342 if (semoprv == 0) { |
|
343 if(::semctl(d->semId, 0, IPC_RMID) == -1) { |
|
344 qWarning("QSystemReadWriteLock::~QSystemReadWriteLock: " |
|
345 "Unable to remove semaphore %s(%s)", |
|
346 d->key.toLocal8Bit().constData(), strerror(errno)); |
|
347 } |
|
348 QFile::remove(d->keyFileName); |
|
349 } else { |
|
350 if (errno == EAGAIN) { |
|
351 //wasn't 0 instances so just decrement the NumInstances semaphore |
|
352 if (::semop(d->semId, ops, 1) == -1) { |
|
353 qWarning("QSystemReadWriteLock::~QSystemReadWriteLock: " |
|
354 "Unable to decrement NumInstances semaphore for key %s(%s)", |
|
355 d->key.toLocal8Bit().constData(), strerror(errno)); |
|
356 } |
|
357 } else { |
|
358 qWarning("QSystemReadWriteLock::~QSystemReadWriteLock: " |
|
359 "Unable to decrement and check NumInstances semaphore for key %s(%s)", |
|
360 d->key.toLocal8Bit().constData(), strerror(errno)); |
|
361 ::semop(d->semId, ops, 1);//try decrement anyway |
|
362 } |
|
363 } |
|
364 |
|
365 } |
|
366 |
|
367 Q_ASSERT(d); |
|
368 delete d; |
|
369 d = 0; |
|
370 } |
|
371 |
|
372 /* |
|
373 Return the key of the lock as passed to the constructor. |
|
374 */ |
|
375 QString QSystemReadWriteLock::key() const |
|
376 { |
|
377 return d->key; |
|
378 } |
|
379 |
|
380 /* |
|
381 Locks the lock for reading. The function will block if any thread/process |
|
382 has locked for writing. |
|
383 */ |
|
384 bool QSystemReadWriteLock::lockForRead() |
|
385 { |
|
386 if (d->semId == -1) { |
|
387 d->errorString = QObject::tr("QSystemReadWriteLock::lockForRead: " |
|
388 "Unable to lock for read for key %1" |
|
389 "(Lock had not been correctly initialized)").arg(d->key); |
|
390 d->error = UnknownError; |
|
391 return false; |
|
392 } |
|
393 |
|
394 struct sembuf ops[2]; |
|
395 |
|
396 ops[0].sem_num = QSystemReadWriteLockPrivate::ActiveReaders; |
|
397 ops[0].sem_op = 1; |
|
398 ops[0].sem_flg = SEM_UNDO; |
|
399 |
|
400 ops[1].sem_num = QSystemReadWriteLockPrivate::TotalWriters; |
|
401 ops[1].sem_op = 0; |
|
402 ops[1].sem_flg = 0; |
|
403 |
|
404 if (-1 == ::semop(d->semId, ops, 2)) { |
|
405 d->setError(QObject::tr("QSystemReadWriteLock::lockForRead: " |
|
406 "Unable to lock for read for key %1(%2)") |
|
407 .arg(d->key).arg(QString::fromLatin1(strerror(errno)))); |
|
408 return false; |
|
409 } else { |
|
410 d->errorString.clear(); |
|
411 d->error = NoError; |
|
412 return true; |
|
413 } |
|
414 } |
|
415 |
|
416 /* |
|
417 Locks the lock for writing. This function will block if another thread/process |
|
418 has locked for reading or writing. |
|
419 */ |
|
420 bool QSystemReadWriteLock::lockForWrite() |
|
421 { |
|
422 if (d->semId == -1) { |
|
423 d->errorString = QObject::tr("QSystemReadWriteLock::lockForWrite: " |
|
424 "Unable to lock for write for key %1" |
|
425 "(Lock had not been correctly initialized)").arg(d->key); |
|
426 d->error = UnknownError; |
|
427 return false; |
|
428 } |
|
429 |
|
430 struct sembuf op; |
|
431 op.sem_num = QSystemReadWriteLockPrivate::TotalWriters; |
|
432 op.sem_op = 1; |
|
433 op.sem_flg = SEM_UNDO; |
|
434 |
|
435 int semoprv = ::semop(d->semId, &op, 1); |
|
436 if (semoprv == -1) { |
|
437 d->setError(QObject::tr("QSystemReadWriteLock::lockForWrite: " |
|
438 "Could not increment TotalWriters semaphore for key %1(%2)") |
|
439 .arg(d->key).arg(QString::fromLatin1(strerror(errno)))); |
|
440 return false; |
|
441 } |
|
442 |
|
443 op.sem_num = QSystemReadWriteLockPrivate::ActiveReaders; |
|
444 op.sem_op = 0; |
|
445 op.sem_flg = 0; |
|
446 semoprv = ::semop(d->semId, &op, 1); |
|
447 if (semoprv == -1) { |
|
448 d->setError(QObject::tr("QSystemReadWriteLock::lockForWrite: " |
|
449 "Could not detect if all readers were finished for key %1(%2)") |
|
450 .arg(d->key).arg(QString::fromLatin1(strerror(errno)))); |
|
451 |
|
452 // Decrement our write lock |
|
453 op.sem_num = QSystemReadWriteLockPrivate::TotalWriters; |
|
454 op.sem_op = -1; |
|
455 op.sem_flg = SEM_UNDO; |
|
456 ::semop(d->semId, &op, 1); |
|
457 return false; |
|
458 } else { |
|
459 op.sem_num = QSystemReadWriteLockPrivate::ActiveWriterSem; |
|
460 op.sem_op = -1; |
|
461 op.sem_flg =SEM_UNDO; |
|
462 semoprv = ::semop(d->semId, &op, 1); |
|
463 |
|
464 if (semoprv == -1) { |
|
465 d->setError(QObject::tr("QSystemReadWriteLock::lockForWrite: " |
|
466 "Could not decrement ActiveWriterSem semaphore for key %1(%2)") |
|
467 .arg(d->key).arg(QString::fromLatin1(strerror(errno)))); |
|
468 |
|
469 op.sem_num = QSystemReadWriteLockPrivate::TotalWriters; |
|
470 op.sem_op = -1; |
|
471 op.sem_flg = 0; |
|
472 ::semop(d->semId, &op, 1); |
|
473 return false; |
|
474 } |
|
475 d->errorString.clear(); |
|
476 d->error = NoError; |
|
477 return true; |
|
478 } |
|
479 } |
|
480 |
|
481 /* |
|
482 Release the lock. |
|
483 */ |
|
484 void QSystemReadWriteLock::unlock() |
|
485 { |
|
486 if (d->semId == -1) { |
|
487 d->errorString = QObject::tr("QSystemReadWriteLock::unlock: " |
|
488 "Unable to unlock for key %1" |
|
489 "(Lock had not been correctly initialized)").arg(d->key); |
|
490 d->error = UnknownError; |
|
491 return; |
|
492 } |
|
493 |
|
494 struct sembuf op; |
|
495 op.sem_num = QSystemReadWriteLockPrivate::ActiveReaders; |
|
496 op.sem_op = -1; |
|
497 op.sem_flg = SEM_UNDO | IPC_NOWAIT; |
|
498 if (::semop(d->semId, &op, 1) == 0) { |
|
499 //do nothing, succeeded in decrementing number of readers |
|
500 } else if (errno == EAGAIN){ //no readers, so check for writers |
|
501 struct sembuf ops[3]; |
|
502 ops[0].sem_num = QSystemReadWriteLockPrivate::ActiveWriterSem; |
|
503 ops[0].sem_op = 0; |
|
504 ops[0].sem_flg = IPC_NOWAIT; |
|
505 |
|
506 ops[1].sem_num = QSystemReadWriteLockPrivate::ActiveWriterSem; |
|
507 ops[1].sem_op = 1; |
|
508 ops[1].sem_flg = SEM_UNDO; |
|
509 |
|
510 ops[2].sem_num = QSystemReadWriteLockPrivate::TotalWriters; |
|
511 ops[2].sem_op = -1; |
|
512 ops[2].sem_flg = SEM_UNDO; |
|
513 |
|
514 if (::semop(d->semId, ops, 3) == -1) { |
|
515 if (errno != EAGAIN) { |
|
516 d->setError(QObject::tr("QSystemSemaphoreWriteLock::unlock: " |
|
517 "Unable to check and update writer semaphores for key %1(%2)") |
|
518 .arg(d->key).arg(QString::fromLatin1(strerror(errno)))); |
|
519 return; |
|
520 } //Note: EAGAIN indicates that ActiveWriterSem is has a non zero value |
|
521 //indicating there is no current writer, so nothing needs to be done |
|
522 } |
|
523 } else { |
|
524 //error in decrementing readers |
|
525 d->setError(QObject::tr("QSystemReadWriteLock::unlock: " |
|
526 "Unable to decrement ActiveReaders semaphore for key %1(%2)") |
|
527 .arg(d->key).arg(QString::fromLatin1(strerror(errno)))); |
|
528 return; |
|
529 } |
|
530 d->errorString.clear(); |
|
531 d->error = NoError; |
|
532 } |
|
533 |
|
534 /* |
|
535 Returns the error code of the last encountered error |
|
536 */ |
|
537 QSystemReadWriteLock::SystemReadWriteLockError QSystemReadWriteLock::error() const { |
|
538 return d->error; |
|
539 } |
|
540 |
|
541 /* |
|
542 Returns a string describing the last encountered error |
|
543 */ |
|
544 QString QSystemReadWriteLock::errorString() const |
|
545 { |
|
546 return d->errorString; |
|
547 } |
|
548 |
|
549 QTM_END_NAMESPACE |
|
550 |