1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qsystemsemaphore.h"
43 #include "qsystemsemaphore_p.h"
44 
45 #include <qcoreapplication.h>
46 #include <qdebug.h>
47 #include <qfile.h>
48 
49 #ifndef QT_NO_SYSTEMSEMAPHORE
50 
51 #include <sys/types.h>
52 #include <sys/ipc.h>
53 #ifndef QT_POSIX_IPC
54 #include <sys/sem.h>
55 #endif
56 #include <fcntl.h>
57 #include <errno.h>
58 
59 #include "private/qcore_unix_p.h"
60 
61 // OpenBSD 4.2 doesn't define EIDRM, see BUGS section:
62 // http://www.openbsd.org/cgi-bin/man.cgi?query=semop&manpath=OpenBSD+4.2
63 #if defined(Q_OS_OPENBSD) && !defined(EIDRM)
64 #define EIDRM EINVAL
65 #endif
66 
67 //#define QSYSTEMSEMAPHORE_DEBUG
68 
69 QT_BEGIN_NAMESPACE
70 
QSystemSemaphorePrivate()71 QSystemSemaphorePrivate::QSystemSemaphorePrivate() :
72 #ifndef QT_POSIX_IPC
73     unix_key(-1), semaphore(-1), createdFile(false),
74 #else
75     semaphore(SEM_FAILED),
76 #endif
77     createdSemaphore(false), error(QSystemSemaphore::NoError)
78 {
79 }
80 
setErrorString(const QString & function)81 void QSystemSemaphorePrivate::setErrorString(const QString &function)
82 {
83     // EINVAL is handled in functions so they can give better error strings
84     switch (errno) {
85     case EPERM:
86     case EACCES:
87         errorString = QCoreApplication::translate("QSystemSemaphore", "%1: permission denied").arg(function);
88         error = QSystemSemaphore::PermissionDenied;
89         break;
90     case EEXIST:
91         errorString = QCoreApplication::translate("QSystemSemaphore", "%1: already exists").arg(function);
92         error = QSystemSemaphore::AlreadyExists;
93         break;
94     case ENOENT:
95         errorString = QCoreApplication::translate("QSystemSemaphore", "%1: does not exist").arg(function);
96         error = QSystemSemaphore::NotFound;
97         break;
98     case ERANGE:
99     case ENOMEM:
100     case ENOSPC:
101     case EMFILE:
102     case ENFILE:
103     case EOVERFLOW:
104         errorString = QCoreApplication::translate("QSystemSemaphore", "%1: out of resources").arg(function);
105         error = QSystemSemaphore::OutOfResources;
106         break;
107     case ENAMETOOLONG:
108         errorString = QCoreApplication::translate("QSystemSemaphore", "%1: name error").arg(function);
109         error = QSystemSemaphore::KeyError;
110         break;
111     default:
112         errorString = QCoreApplication::translate("QSystemSemaphore", "%1: unknown error %2").arg(function).arg(errno);
113         error = QSystemSemaphore::UnknownError;
114 #ifdef QSYSTEMSEMAPHORE_DEBUG
115         qDebug() << errorString << "key" << key << "errno" << errno << EINVAL;
116 #endif
117         break;
118     }
119 }
120 
121 /*!
122     \internal
123 
124     Initialise the semaphore
125 */
126 #ifndef QT_POSIX_IPC
handle(QSystemSemaphore::AccessMode mode)127 key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode)
128 {
129     if (-1 != unix_key)
130         return unix_key;
131 
132     if (key.isEmpty()) {
133         errorString = QCoreApplication::tr("%1: key is empty", "QSystemSemaphore").arg(QLatin1String("QSystemSemaphore::handle"));
134         error = QSystemSemaphore::KeyError;
135         return -1;
136     }
137 
138     // ftok requires that an actual file exists somewhere
139     int built = QSharedMemoryPrivate::createUnixKeyFile(fileName);
140     if (-1 == built) {
141         errorString = QCoreApplication::tr("%1: unable to make key", "QSystemSemaphore").arg(QLatin1String("QSystemSemaphore::handle"));
142         error = QSystemSemaphore::KeyError;
143         return -1;
144     }
145     createdFile = (1 == built);
146 
147     // Get the unix key for the created file
148     unix_key = ftok(QFile::encodeName(fileName).constData(), 'Q');
149     if (-1 == unix_key) {
150         errorString = QCoreApplication::tr("%1: ftok failed", "QSystemSemaphore").arg(QLatin1String("QSystemSemaphore::handle"));
151         error = QSystemSemaphore::KeyError;
152         return -1;
153     }
154 
155     // Get semaphore
156     semaphore = semget(unix_key, 1, 0600 | IPC_CREAT | IPC_EXCL);
157     if (-1 == semaphore) {
158         if (errno == EEXIST)
159             semaphore = semget(unix_key, 1, 0600 | IPC_CREAT);
160         if (-1 == semaphore) {
161             setErrorString(QLatin1String("QSystemSemaphore::handle"));
162             cleanHandle();
163             return -1;
164         }
165         if (mode == QSystemSemaphore::Create) {
166             createdSemaphore = true;
167             createdFile = true;
168         }
169     } else {
170         createdSemaphore = true;
171         // Force cleanup of file, it is possible that it can be left over from a crash
172         createdFile = true;
173     }
174 
175     // Created semaphore so initialize its value.
176     if (createdSemaphore && initialValue >= 0) {
177         qt_semun init_op;
178         init_op.val = initialValue;
179         if (-1 == semctl(semaphore, 0, SETVAL, init_op)) {
180             setErrorString(QLatin1String("QSystemSemaphore::handle"));
181             cleanHandle();
182             return -1;
183         }
184     }
185 
186     return unix_key;
187 }
188 #else
handle(QSystemSemaphore::AccessMode mode)189 bool QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode)
190 {
191     if (semaphore != SEM_FAILED)
192         return true;  // we already have a semaphore
193 
194     if (fileName.isEmpty()) {
195         errorString = QCoreApplication::tr("%1: key is empty", "QSystemSemaphore").arg(QLatin1String("QSystemSemaphore::handle"));
196         error = QSystemSemaphore::KeyError;
197         return false;
198     }
199 
200     QByteArray semName = QFile::encodeName(fileName);
201 
202     // Always try with O_EXCL so we know whether we created the semaphore.
203     int oflag = O_CREAT | O_EXCL;
204     for (int tryNum = 0, maxTries = 1; tryNum < maxTries; ++tryNum) {
205         do {
206             semaphore = sem_open(semName.constData(), oflag, 0666, initialValue);
207         } while (semaphore == SEM_FAILED && errno == EINTR);
208         if (semaphore == SEM_FAILED && errno == EEXIST) {
209             if (mode == QSystemSemaphore::Create) {
210                 if (sem_unlink(semName.constData()) == -1 && errno != ENOENT) {
211                     setErrorString(QLatin1String("QSystemSemaphore::handle (sem_unlink)"));
212                     return false;
213                 }
214                 // Race condition: the semaphore might be recreated before
215                 // we call sem_open again, so we'll retry several times.
216                 maxTries = 3;
217             } else {
218                 // Race condition: if it no longer exists at the next sem_open
219                 // call, we won't realize we created it, so we'll leak it later.
220                 oflag &= ~O_EXCL;
221                 maxTries = 2;
222             }
223         } else {
224             break;
225         }
226     }
227     if (semaphore == SEM_FAILED) {
228         setErrorString(QLatin1String("QSystemSemaphore::handle"));
229         return false;
230     }
231 
232     createdSemaphore = (oflag & O_EXCL) != 0;
233     return true;
234 }
235 #endif // QT_POSIX_IPC
236 
237 /*!
238     \internal
239 
240     Clean up the semaphore
241 */
cleanHandle()242 void QSystemSemaphorePrivate::cleanHandle()
243 {
244 #ifndef QT_POSIX_IPC
245     unix_key = -1;
246 
247     // remove the file if we made it
248     if (createdFile) {
249         QFile::remove(fileName);
250         createdFile = false;
251     }
252 
253     if (createdSemaphore) {
254         if (-1 != semaphore) {
255             if (-1 == semctl(semaphore, 0, IPC_RMID, 0)) {
256                 setErrorString(QLatin1String("QSystemSemaphore::cleanHandle"));
257 #ifdef QSYSTEMSEMAPHORE_DEBUG
258                 qDebug("QSystemSemaphore::cleanHandle semctl failed.");
259 #endif
260             }
261             semaphore = -1;
262         }
263         createdSemaphore = false;
264     }
265 #else
266     if (semaphore != SEM_FAILED) {
267         if (sem_close(semaphore) == -1) {
268             setErrorString(QLatin1String("QSystemSemaphore::cleanHandle (sem_close)"));
269 #ifdef QSYSTEMSEMAPHORE_DEBUG
270             qDebug() << QLatin1String("QSystemSemaphore::cleanHandle sem_close failed.");
271 #endif
272         }
273         semaphore = SEM_FAILED;
274     }
275 
276     if (createdSemaphore) {
277         if (sem_unlink(QFile::encodeName(fileName).constData()) == -1 && errno != ENOENT) {
278             setErrorString(QLatin1String("QSystemSemaphore::cleanHandle (sem_unlink)"));
279 #ifdef QSYSTEMSEMAPHORE_DEBUG
280             qDebug() << QLatin1String("QSystemSemaphore::cleanHandle sem_unlink failed.");
281 #endif
282         }
283         createdSemaphore = false;
284     }
285 #endif // QT_POSIX_IPC
286 }
287 
288 /*!
289     \internal
290 */
modifySemaphore(int count)291 bool QSystemSemaphorePrivate::modifySemaphore(int count)
292 {
293 #ifndef QT_POSIX_IPC
294     if (-1 == handle())
295         return false;
296 
297     struct sembuf operation;
298     operation.sem_num = 0;
299     operation.sem_op = count;
300     operation.sem_flg = SEM_UNDO;
301 
302     int res;
303     EINTR_LOOP(res, semop(semaphore, &operation, 1));
304     if (-1 == res) {
305         // If the semaphore was removed be nice and create it and then modifySemaphore again
306         if (errno == EINVAL || errno == EIDRM) {
307             semaphore = -1;
308             cleanHandle();
309             handle();
310             return modifySemaphore(count);
311         }
312         setErrorString(QLatin1String("QSystemSemaphore::modifySemaphore"));
313 #ifdef QSYSTEMSEMAPHORE_DEBUG
314         qDebug() << QLatin1String("QSystemSemaphore::modify failed") << count << semctl(semaphore, 0, GETVAL) << errno << EIDRM << EINVAL;
315 #endif
316         return false;
317     }
318 #else
319     if (!handle())
320         return false;
321 
322     if (count > 0) {
323         int cnt = count;
324         do {
325             if (sem_post(semaphore) == -1) {
326                 setErrorString(QLatin1String("QSystemSemaphore::modifySemaphore (sem_post)"));
327 #ifdef QSYSTEMSEMAPHORE_DEBUG
328                 qDebug() << QLatin1String("QSystemSemaphore::modify sem_post failed") << count << errno;
329 #endif
330                 // rollback changes to preserve the SysV semaphore behavior
331                 for ( ; cnt < count; ++cnt) {
332                     register int res;
333                     EINTR_LOOP(res, sem_wait(semaphore));
334                 }
335                 return false;
336             }
337             --cnt;
338         } while (cnt > 0);
339     } else {
340         register int res;
341         EINTR_LOOP(res, sem_wait(semaphore));
342         if (res == -1) {
343             // If the semaphore was removed be nice and create it and then modifySemaphore again
344             if (errno == EINVAL || errno == EIDRM) {
345                 semaphore = SEM_FAILED;
346                 return modifySemaphore(count);
347             }
348             setErrorString(QLatin1String("QSystemSemaphore::modifySemaphore (sem_wait)"));
349 #ifdef QSYSTEMSEMAPHORE_DEBUG
350             qDebug() << QLatin1String("QSystemSemaphore::modify sem_wait failed") << count << errno;
351 #endif
352             return false;
353         }
354     }
355 #endif // QT_POSIX_IPC
356 
357     return true;
358 }
359 
360 QT_END_NAMESPACE
361 
362 #endif // QT_NO_SYSTEMSEMAPHORE
363