/src/open62541/arch/posix/eventloop_posix_interrupt.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2021, 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | */ |
7 | | |
8 | | #include "eventloop_posix.h" |
9 | | #include <signal.h> |
10 | | |
11 | | #if defined(UA_ARCHITECTURE_POSIX) && !defined(UA_ARCHITECTURE_LWIP) || defined(UA_ARCHITECTURE_WIN32) |
12 | | /* Different implementation approaches: |
13 | | * - Linux: Use signalfd |
14 | | * - Other: Use the self-pipe trick (http://cr.yp.to/docs/selfpipe.html) */ |
15 | | |
16 | | typedef struct UA_RegisteredSignal { |
17 | | #ifdef UA_HAVE_EPOLL |
18 | | /* With epoll, register each signal with a socket. |
19 | | * This has to be the first element of the struct to allow casting. */ |
20 | | UA_RegisteredFD rfd; |
21 | | #endif |
22 | | |
23 | | LIST_ENTRY(UA_RegisteredSignal) listPointers; |
24 | | |
25 | | UA_InterruptCallback signalCallback; |
26 | | void *context; |
27 | | int signal; /* POSIX identifier of the interrupt signal */ |
28 | | |
29 | | UA_Boolean active; /* Signals are only active when the EventLoop is started */ |
30 | | UA_Boolean triggered; |
31 | | } UA_RegisteredSignal; |
32 | | |
33 | | typedef struct { |
34 | | UA_InterruptManager im; |
35 | | |
36 | | LIST_HEAD(, UA_RegisteredSignal) signals; /* Registered signals */ |
37 | | |
38 | | #ifndef UA_HAVE_EPOLL |
39 | | UA_DelayedCallback dc; /* Process all triggered signals */ |
40 | | #endif |
41 | | } UA_POSIXInterruptManager; |
42 | | |
43 | | /* The following methods have to be implemented for epoll/self-pipe each. */ |
44 | | static void activateSignal(UA_RegisteredSignal *rs); |
45 | | static void deactivateSignal(UA_RegisteredSignal *rs); |
46 | | |
47 | | #ifdef UA_HAVE_EPOLL |
48 | | #include <sys/signalfd.h> |
49 | | |
50 | | static void |
51 | 0 | handlePOSIXInterruptEvent(UA_EventSource *es, UA_RegisteredFD *rfd, short event) { |
52 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
53 | 0 | (void)el; |
54 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
55 | |
|
56 | 0 | UA_RegisteredSignal *rs = (UA_RegisteredSignal*)rfd; |
57 | 0 | struct signalfd_siginfo fdsi; |
58 | 0 | ssize_t s = read(rfd->fd, &fdsi, sizeof(fdsi)); |
59 | 0 | if(s < (ssize_t)sizeof(fdsi)) { |
60 | | /* A problem occured */ |
61 | 0 | deactivateSignal(rs); |
62 | 0 | return; |
63 | 0 | } |
64 | | |
65 | | /* Signal received */ |
66 | 0 | UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
67 | 0 | "Interrupt %u\t| Received a signal %u", |
68 | 0 | (unsigned)rfd->fd, fdsi.ssi_signo); |
69 | |
|
70 | 0 | UA_UNLOCK(&el->elMutex); |
71 | 0 | rs->signalCallback((UA_InterruptManager *)es, (uintptr_t)rfd->fd, |
72 | 0 | rs->context, &UA_KEYVALUEMAP_NULL); |
73 | 0 | UA_LOCK(&el->elMutex); |
74 | 0 | } |
75 | | |
76 | | static void |
77 | 0 | activateSignal(UA_RegisteredSignal *rs) { |
78 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)rs->rfd.es->eventLoop; |
79 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
80 | |
|
81 | 0 | if(rs->active) |
82 | 0 | return; |
83 | | |
84 | | /* Block the normal signal handling */ |
85 | 0 | UA_RESET_ERRNO; |
86 | 0 | sigset_t mask; |
87 | 0 | sigemptyset(&mask); |
88 | 0 | sigaddset(&mask, rs->signal); |
89 | 0 | int res2 = sigprocmask(SIG_BLOCK, &mask, NULL); |
90 | 0 | if(res2 == -1) { |
91 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
92 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
93 | 0 | "Interrupt\t| Could not block the default " |
94 | 0 | "signal handling with an error: %s", |
95 | 0 | errno_str)); |
96 | 0 | return; |
97 | 0 | } |
98 | | |
99 | | /* Create the fd */ |
100 | 0 | UA_RESET_ERRNO; |
101 | 0 | UA_FD newfd = signalfd(-1, &mask, 0); |
102 | 0 | if(newfd < 0) { |
103 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
104 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
105 | 0 | "Interrupt\t| Could not create a signal file " |
106 | 0 | "description with error: %s", |
107 | 0 | errno_str)); |
108 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); /* restore signal */ |
109 | 0 | return; |
110 | 0 | } |
111 | | |
112 | 0 | rs->rfd.fd = newfd; |
113 | 0 | rs->rfd.eventSourceCB = handlePOSIXInterruptEvent; |
114 | 0 | rs->rfd.listenEvents = UA_FDEVENT_IN; |
115 | | |
116 | | /* Register the fd in the EventLoop */ |
117 | 0 | UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, &rs->rfd); |
118 | 0 | if(res != UA_STATUSCODE_GOOD) { |
119 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
120 | 0 | "Interrupt\t| Could not register the a signal file " |
121 | 0 | "description in the EventLoop"); |
122 | 0 | UA_close(newfd); |
123 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); /* restore signal */ |
124 | 0 | return; |
125 | 0 | } |
126 | | |
127 | 0 | rs->active = true; |
128 | 0 | } |
129 | | |
130 | | static void |
131 | 0 | deactivateSignal(UA_RegisteredSignal *rs) { |
132 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)rs->rfd.es->eventLoop; |
133 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
134 | | |
135 | | /* Only dectivate if active */ |
136 | 0 | if(!rs->active) |
137 | 0 | return; |
138 | 0 | rs->active = false; |
139 | | |
140 | | /* Stop receiving the signal on the FD */ |
141 | 0 | UA_EventLoopPOSIX_deregisterFD(el, &rs->rfd); |
142 | | |
143 | | /* Unblock the signal */ |
144 | 0 | sigset_t mask; |
145 | 0 | sigemptyset(&mask); |
146 | 0 | sigaddset(&mask, (int)rs->signal); |
147 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); |
148 | | |
149 | | /* Clean up locally */ |
150 | 0 | UA_close(rs->rfd.fd); |
151 | 0 | } |
152 | | |
153 | | #else /* !UA_HAVE_EPOLL */ |
154 | | |
155 | | /* When using signal() a global pointer to the interrupt manager is required. |
156 | | * We have no other we to get additional data with the interrupt. */ |
157 | | static UA_POSIXInterruptManager *singletonIM = NULL; |
158 | | |
159 | | /* Execute all triggered interrupts in a delayed callback by the EventLoop */ |
160 | | static void |
161 | | executeTriggeredPOSIXInterrupts(UA_POSIXInterruptManager *im, void *_) { |
162 | | im->dc.callback = NULL; /* Allow to re-arm the delayed callback */ |
163 | | |
164 | | UA_RegisteredSignal *rs, *rs_tmp; |
165 | | LIST_FOREACH_SAFE(rs, &im->signals, listPointers, rs_tmp) { |
166 | | rs->triggered = false; |
167 | | rs->signalCallback(&im->im, (uintptr_t)rs->signal, |
168 | | rs->context, &UA_KEYVALUEMAP_NULL); |
169 | | } |
170 | | } |
171 | | |
172 | | /* Mark the signal entry as triggered and make the EventLoop process it "soon" |
173 | | * with a delayed callback. This is an interrupt handler and cannot take a |
174 | | * lock. */ |
175 | | static void |
176 | | triggerPOSIXInterruptEvent(int sig) { |
177 | | UA_assert(singletonIM != NULL); |
178 | | |
179 | | /* Find the signal */ |
180 | | UA_RegisteredSignal *rs; |
181 | | LIST_FOREACH(rs, &singletonIM->signals, listPointers) { |
182 | | if(rs->signal == sig) |
183 | | break; |
184 | | } |
185 | | if(!rs || rs->triggered || !rs->active) |
186 | | return; |
187 | | |
188 | | /* Mark as triggered */ |
189 | | rs->triggered = true; |
190 | | |
191 | | #ifdef UA_ARCHITECTURE_WIN32 |
192 | | /* On WIN32 we have to re-arm the signal or it will go back to SIG_DFL */ |
193 | | signal(sig, triggerPOSIXInterruptEvent); |
194 | | #endif |
195 | | |
196 | | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)singletonIM->im.eventSource.eventLoop; |
197 | | |
198 | | /* Arm the delayed callback for processing the received signals */ |
199 | | if(!singletonIM->dc.callback) { |
200 | | singletonIM->dc.callback = (UA_Callback)executeTriggeredPOSIXInterrupts; |
201 | | singletonIM->dc.application = singletonIM; |
202 | | singletonIM->dc.context = NULL; |
203 | | UA_EventLoopPOSIX_addDelayedCallback(&el->eventLoop, &singletonIM->dc); |
204 | | } |
205 | | |
206 | | /* Cancel the EventLoop if it is currently waiting with a timeout */ |
207 | | UA_EventLoopPOSIX_cancel(el); |
208 | | } |
209 | | |
210 | | static void |
211 | | activateSignal(UA_RegisteredSignal *rs) { |
212 | | UA_assert(singletonIM != NULL); |
213 | | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)singletonIM->im.eventSource.eventLoop; |
214 | | UA_LOCK_ASSERT(&el->elMutex); |
215 | | |
216 | | /* Register the signal on the OS level */ |
217 | | if(rs->active) |
218 | | return; |
219 | | |
220 | | UA_RESET_ERRNO; |
221 | | void (*prev)(int); |
222 | | prev = signal(rs->signal, triggerPOSIXInterruptEvent); |
223 | | if(prev == SIG_ERR) { |
224 | | UA_LOG_SOCKET_ERRNO_WRAP( |
225 | | UA_LOG_WARNING(singletonIM->im.eventSource.eventLoop->logger, |
226 | | UA_LOGCATEGORY_EVENTLOOP, |
227 | | "Error registering the signal: %s", errno_str)); |
228 | | return; |
229 | | } |
230 | | |
231 | | rs->active = true; |
232 | | } |
233 | | |
234 | | static void |
235 | | deactivateSignal(UA_RegisteredSignal *rs) { |
236 | | UA_assert(singletonIM != NULL); |
237 | | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)singletonIM->im.eventSource.eventLoop; |
238 | | UA_LOCK_ASSERT(&el->elMutex); |
239 | | |
240 | | /* Only dectivate if active */ |
241 | | if(!rs->active) |
242 | | return; |
243 | | |
244 | | /* Stop receiving the signal */ |
245 | | signal(rs->signal, SIG_DFL); |
246 | | |
247 | | rs->triggered = false; |
248 | | rs->active = false; |
249 | | } |
250 | | |
251 | | #endif /* !UA_HAVE_EPOLL */ |
252 | | |
253 | | static UA_StatusCode |
254 | | registerPOSIXInterrupt(UA_InterruptManager *im, uintptr_t interruptHandle, |
255 | | const UA_KeyValueMap *params, |
256 | 0 | UA_InterruptCallback callback, void *interruptContext) { |
257 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)im->eventSource.eventLoop; |
258 | 0 | if(!UA_KeyValueMap_isEmpty(params)) { |
259 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
260 | 0 | "Interrupt\t| Supplied parameters invalid for the " |
261 | 0 | "POSIX InterruptManager"); |
262 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
263 | 0 | } |
264 | | |
265 | 0 | UA_LOCK(&el->elMutex); |
266 | | |
267 | | /* Was the signal already registered? */ |
268 | 0 | int signal = (int)interruptHandle; |
269 | 0 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)im; |
270 | 0 | UA_RegisteredSignal *rs; |
271 | 0 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
272 | 0 | if(rs->signal == signal) |
273 | 0 | break; |
274 | 0 | } |
275 | 0 | if(rs) { |
276 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
277 | 0 | "Interrupt\t| Signal %u already registered", |
278 | 0 | (unsigned)interruptHandle); |
279 | 0 | UA_UNLOCK(&el->elMutex); |
280 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
281 | 0 | } |
282 | | |
283 | | /* Create and populate the new context object */ |
284 | 0 | rs = (UA_RegisteredSignal *)UA_calloc(1, sizeof(UA_RegisteredSignal)); |
285 | 0 | if(!rs) { |
286 | 0 | UA_UNLOCK(&el->elMutex); |
287 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
288 | 0 | } |
289 | | |
290 | 0 | #ifdef UA_HAVE_EPOLL |
291 | 0 | rs->rfd.es = &im->eventSource; |
292 | 0 | #endif |
293 | 0 | rs->signal = (int)interruptHandle; |
294 | 0 | rs->signalCallback = callback; |
295 | 0 | rs->context = interruptContext; |
296 | | |
297 | | /* Add to the InterruptManager */ |
298 | 0 | LIST_INSERT_HEAD(&pim->signals, rs, listPointers); |
299 | | |
300 | | /* Activate if we are already running */ |
301 | 0 | if(pim->im.eventSource.state == UA_EVENTSOURCESTATE_STARTED) |
302 | 0 | activateSignal(rs); |
303 | |
|
304 | 0 | UA_UNLOCK(&el->elMutex); |
305 | 0 | return UA_STATUSCODE_GOOD; |
306 | 0 | } |
307 | | |
308 | | static void |
309 | 0 | deregisterPOSIXInterrupt(UA_InterruptManager *im, uintptr_t interruptHandle) { |
310 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)im->eventSource.eventLoop; |
311 | 0 | (void)el; |
312 | 0 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)im; |
313 | 0 | UA_LOCK(&el->elMutex); |
314 | |
|
315 | 0 | int signal = (int)interruptHandle; |
316 | 0 | UA_RegisteredSignal *rs; |
317 | 0 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
318 | 0 | if(rs->signal == signal) |
319 | 0 | break; |
320 | 0 | } |
321 | 0 | if(rs) { |
322 | 0 | deactivateSignal(rs); |
323 | 0 | LIST_REMOVE(rs, listPointers); |
324 | 0 | UA_free(rs); |
325 | 0 | } |
326 | |
|
327 | 0 | UA_UNLOCK(&el->elMutex); |
328 | 0 | } |
329 | | |
330 | | static UA_StatusCode |
331 | 551 | startPOSIXInterruptManager(UA_EventSource *es) { |
332 | 551 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
333 | 551 | (void)el; |
334 | 551 | UA_LOCK(&el->elMutex); |
335 | | |
336 | | /* Check the state */ |
337 | 551 | if(es->state != UA_EVENTSOURCESTATE_STOPPED) { |
338 | 0 | UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
339 | 0 | "Interrupt\t| To start the InterruptManager, " |
340 | 0 | "it has to be registered in an EventLoop and not started"); |
341 | 0 | UA_UNLOCK(&el->elMutex); |
342 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
343 | 0 | } |
344 | | |
345 | 551 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)es; |
346 | 551 | UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
347 | 551 | "Interrupt\t| Starting the InterruptManager"); |
348 | | |
349 | | /* Activate the registered signal handlers */ |
350 | 551 | UA_RegisteredSignal*rs; |
351 | 551 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
352 | 0 | activateSignal(rs); |
353 | 0 | } |
354 | | |
355 | | /* Set the EventSource to the started state */ |
356 | 551 | es->state = UA_EVENTSOURCESTATE_STARTED; |
357 | | |
358 | 551 | UA_UNLOCK(&el->elMutex); |
359 | 551 | return UA_STATUSCODE_GOOD; |
360 | 551 | } |
361 | | |
362 | | static void |
363 | 551 | stopPOSIXInterruptManager(UA_EventSource *es) { |
364 | 551 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
365 | 551 | (void)el; |
366 | 551 | UA_LOCK(&el->elMutex); |
367 | | |
368 | 551 | if(es->state != UA_EVENTSOURCESTATE_STARTED) { |
369 | 0 | UA_UNLOCK(&el->elMutex); |
370 | 0 | return; |
371 | 0 | } |
372 | | |
373 | 551 | UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
374 | 551 | "Interrupt\t| Stopping the InterruptManager"); |
375 | | |
376 | | /* Close all registered signals */ |
377 | 551 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)es; |
378 | 551 | UA_RegisteredSignal*rs; |
379 | 551 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
380 | 0 | deactivateSignal(rs); |
381 | 0 | } |
382 | | |
383 | | /* Immediately set to stopped */ |
384 | 551 | es->state = UA_EVENTSOURCESTATE_STOPPED; |
385 | | |
386 | 551 | UA_UNLOCK(&el->elMutex); |
387 | 551 | } |
388 | | |
389 | | static UA_StatusCode |
390 | 551 | freePOSIXInterruptmanager(UA_EventSource *es) { |
391 | 551 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
392 | 551 | (void)el; |
393 | 551 | UA_LOCK_ASSERT(&el->elMutex); |
394 | | |
395 | 551 | if(es->state >= UA_EVENTSOURCESTATE_STARTING) { |
396 | 0 | UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
397 | 0 | "Interrupt\t| The EventSource must be stopped " |
398 | 0 | "before it can be deleted"); |
399 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
400 | 0 | } |
401 | | |
402 | | /* Deactivate and remove all registered signals */ |
403 | 551 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)es; |
404 | 551 | UA_RegisteredSignal *rs, *rs_tmp; |
405 | 551 | LIST_FOREACH_SAFE(rs, &pim->signals, listPointers, rs_tmp) { |
406 | 0 | deactivateSignal(rs); |
407 | 0 | LIST_REMOVE(rs, listPointers); |
408 | 0 | UA_free(rs); |
409 | 0 | } |
410 | | |
411 | 551 | UA_String_clear(&es->name); |
412 | 551 | UA_free(es); |
413 | | |
414 | | #ifndef UA_HAVE_EPOLL |
415 | | singletonIM = NULL; /* Reset the global singleton pointer */ |
416 | | #endif |
417 | | |
418 | 551 | return UA_STATUSCODE_GOOD; |
419 | 551 | } |
420 | | |
421 | | UA_InterruptManager * |
422 | 551 | UA_InterruptManager_new_POSIX(const UA_String eventSourceName) { |
423 | | #ifndef UA_HAVE_EPOLL |
424 | | /* There can be only one InterruptManager if epoll is not present */ |
425 | | if(singletonIM) |
426 | | return NULL; |
427 | | #endif |
428 | | |
429 | 551 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *) |
430 | 551 | UA_calloc(1, sizeof(UA_POSIXInterruptManager)); |
431 | 551 | if(!pim) |
432 | 0 | return NULL; |
433 | | |
434 | | #ifndef UA_HAVE_EPOLL |
435 | | singletonIM = pim; /* Register the singleton singleton pointer */ |
436 | | #endif |
437 | | |
438 | 551 | UA_InterruptManager *im = &pim->im; |
439 | 551 | im->eventSource.eventSourceType = UA_EVENTSOURCETYPE_INTERRUPTMANAGER; |
440 | 551 | UA_String_copy(&eventSourceName, &im->eventSource.name); |
441 | 551 | im->eventSource.start = startPOSIXInterruptManager; |
442 | 551 | im->eventSource.stop = stopPOSIXInterruptManager; |
443 | 551 | im->eventSource.free = freePOSIXInterruptmanager; |
444 | 551 | im->registerInterrupt = registerPOSIXInterrupt; |
445 | 551 | im->deregisterInterrupt = deregisterPOSIXInterrupt; |
446 | 551 | return im; |
447 | 551 | } |
448 | | |
449 | | #endif |