/src/open62541/arch/posix/eventloop_posix_interrupt.c
Line | Count | Source |
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 | (void)el; |
80 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
81 | |
|
82 | 0 | if(rs->active) |
83 | 0 | return; |
84 | | |
85 | | /* Block the normal signal handling */ |
86 | 0 | UA_RESET_ERRNO; |
87 | 0 | sigset_t mask; |
88 | 0 | sigemptyset(&mask); |
89 | 0 | sigaddset(&mask, rs->signal); |
90 | 0 | int res2 = sigprocmask(SIG_BLOCK, &mask, NULL); |
91 | 0 | if(res2 == -1) { |
92 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
93 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
94 | 0 | "Interrupt\t| Could not block the default " |
95 | 0 | "signal handling with an error: %s", |
96 | 0 | errno_str)); |
97 | 0 | return; |
98 | 0 | } |
99 | | |
100 | | /* Create the fd */ |
101 | 0 | UA_RESET_ERRNO; |
102 | 0 | UA_FD newfd = signalfd(-1, &mask, 0); |
103 | 0 | if(newfd < 0) { |
104 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
105 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
106 | 0 | "Interrupt\t| Could not create a signal file " |
107 | 0 | "description with error: %s", |
108 | 0 | errno_str)); |
109 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); /* restore signal */ |
110 | 0 | return; |
111 | 0 | } |
112 | | |
113 | 0 | rs->rfd.fd = newfd; |
114 | 0 | rs->rfd.eventSourceCB = handlePOSIXInterruptEvent; |
115 | 0 | rs->rfd.listenEvents = UA_FDEVENT_IN; |
116 | | |
117 | | /* Register the fd in the EventLoop */ |
118 | 0 | UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, &rs->rfd); |
119 | 0 | if(res != UA_STATUSCODE_GOOD) { |
120 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
121 | 0 | "Interrupt\t| Could not register the a signal file " |
122 | 0 | "description in the EventLoop"); |
123 | 0 | UA_close(newfd); |
124 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); /* restore signal */ |
125 | 0 | return; |
126 | 0 | } |
127 | | |
128 | 0 | rs->active = true; |
129 | 0 | } |
130 | | |
131 | | static void |
132 | 0 | deactivateSignal(UA_RegisteredSignal *rs) { |
133 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)rs->rfd.es->eventLoop; |
134 | 0 | (void)el; |
135 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
136 | | |
137 | | /* Only dectivate if active */ |
138 | 0 | if(!rs->active) |
139 | 0 | return; |
140 | 0 | rs->active = false; |
141 | | |
142 | | /* Stop receiving the signal on the FD */ |
143 | 0 | UA_EventLoopPOSIX_deregisterFD(el, &rs->rfd); |
144 | | |
145 | | /* Unblock the signal */ |
146 | 0 | sigset_t mask; |
147 | 0 | sigemptyset(&mask); |
148 | 0 | sigaddset(&mask, (int)rs->signal); |
149 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); |
150 | | |
151 | | /* Clean up locally */ |
152 | 0 | UA_close(rs->rfd.fd); |
153 | 0 | } |
154 | | |
155 | | #else /* !UA_HAVE_EPOLL */ |
156 | | |
157 | | /* When using signal() a global pointer to the interrupt manager is required. |
158 | | * We have no other we to get additional data with the interrupt. */ |
159 | | static UA_POSIXInterruptManager *singletonIM = NULL; |
160 | | |
161 | | /* Execute all triggered interrupts in a delayed callback by the EventLoop */ |
162 | | static void |
163 | | executeTriggeredPOSIXInterrupts(UA_POSIXInterruptManager *im, void *_) { |
164 | | im->dc.callback = NULL; /* Allow to re-arm the delayed callback */ |
165 | | |
166 | | UA_RegisteredSignal *rs, *rs_tmp; |
167 | | LIST_FOREACH_SAFE(rs, &im->signals, listPointers, rs_tmp) { |
168 | | rs->triggered = false; |
169 | | rs->signalCallback(&im->im, (uintptr_t)rs->signal, |
170 | | rs->context, &UA_KEYVALUEMAP_NULL); |
171 | | } |
172 | | } |
173 | | |
174 | | /* Mark the signal entry as triggered and make the EventLoop process it "soon" |
175 | | * with a delayed callback. This is an interrupt handler and cannot take a |
176 | | * lock. */ |
177 | | static void |
178 | | triggerPOSIXInterruptEvent(int sig) { |
179 | | UA_assert(singletonIM != NULL); |
180 | | |
181 | | /* Find the signal */ |
182 | | UA_RegisteredSignal *rs; |
183 | | LIST_FOREACH(rs, &singletonIM->signals, listPointers) { |
184 | | if(rs->signal == sig) |
185 | | break; |
186 | | } |
187 | | if(!rs || rs->triggered || !rs->active) |
188 | | return; |
189 | | |
190 | | /* Mark as triggered */ |
191 | | rs->triggered = true; |
192 | | |
193 | | #ifdef UA_ARCHITECTURE_WIN32 |
194 | | /* On WIN32 we have to re-arm the signal or it will go back to SIG_DFL */ |
195 | | signal(sig, triggerPOSIXInterruptEvent); |
196 | | #endif |
197 | | |
198 | | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)singletonIM->im.eventSource.eventLoop; |
199 | | |
200 | | /* Arm the delayed callback for processing the received signals */ |
201 | | if(!singletonIM->dc.callback) { |
202 | | singletonIM->dc.callback = (UA_Callback)executeTriggeredPOSIXInterrupts; |
203 | | singletonIM->dc.application = singletonIM; |
204 | | singletonIM->dc.context = NULL; |
205 | | UA_EventLoopPOSIX_addDelayedCallback(&el->eventLoop, &singletonIM->dc); |
206 | | } |
207 | | |
208 | | /* Cancel the EventLoop if it is currently waiting with a timeout */ |
209 | | UA_EventLoopPOSIX_cancel(el); |
210 | | } |
211 | | |
212 | | static void |
213 | | activateSignal(UA_RegisteredSignal *rs) { |
214 | | UA_assert(singletonIM != NULL); |
215 | | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)singletonIM->im.eventSource.eventLoop; |
216 | | (void)el; |
217 | | UA_LOCK_ASSERT(&el->elMutex); |
218 | | |
219 | | /* Register the signal on the OS level */ |
220 | | if(rs->active) |
221 | | return; |
222 | | |
223 | | UA_RESET_ERRNO; |
224 | | void (*prev)(int); |
225 | | prev = signal(rs->signal, triggerPOSIXInterruptEvent); |
226 | | if(prev == SIG_ERR) { |
227 | | UA_LOG_SOCKET_ERRNO_WRAP( |
228 | | UA_LOG_WARNING(singletonIM->im.eventSource.eventLoop->logger, |
229 | | UA_LOGCATEGORY_EVENTLOOP, |
230 | | "Error registering the signal: %s", errno_str)); |
231 | | return; |
232 | | } |
233 | | |
234 | | rs->active = true; |
235 | | } |
236 | | |
237 | | static void |
238 | | deactivateSignal(UA_RegisteredSignal *rs) { |
239 | | UA_assert(singletonIM != NULL); |
240 | | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)singletonIM->im.eventSource.eventLoop; |
241 | | (void)el; |
242 | | UA_LOCK_ASSERT(&el->elMutex); |
243 | | |
244 | | /* Only dectivate if active */ |
245 | | if(!rs->active) |
246 | | return; |
247 | | |
248 | | /* Stop receiving the signal */ |
249 | | signal(rs->signal, SIG_DFL); |
250 | | |
251 | | rs->triggered = false; |
252 | | rs->active = false; |
253 | | } |
254 | | |
255 | | #endif /* !UA_HAVE_EPOLL */ |
256 | | |
257 | | static UA_StatusCode |
258 | | registerPOSIXInterrupt(UA_InterruptManager *im, uintptr_t interruptHandle, |
259 | | const UA_KeyValueMap *params, |
260 | 0 | UA_InterruptCallback callback, void *interruptContext) { |
261 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)im->eventSource.eventLoop; |
262 | 0 | if(!UA_KeyValueMap_isEmpty(params)) { |
263 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
264 | 0 | "Interrupt\t| Supplied parameters invalid for the " |
265 | 0 | "POSIX InterruptManager"); |
266 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
267 | 0 | } |
268 | | |
269 | 0 | UA_LOCK(&el->elMutex); |
270 | | |
271 | | /* Was the signal already registered? */ |
272 | 0 | int signal = (int)interruptHandle; |
273 | 0 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)im; |
274 | 0 | UA_RegisteredSignal *rs; |
275 | 0 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
276 | 0 | if(rs->signal == signal) |
277 | 0 | break; |
278 | 0 | } |
279 | 0 | if(rs) { |
280 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
281 | 0 | "Interrupt\t| Signal %u already registered", |
282 | 0 | (unsigned)interruptHandle); |
283 | 0 | UA_UNLOCK(&el->elMutex); |
284 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
285 | 0 | } |
286 | | |
287 | | /* Create and populate the new context object */ |
288 | 0 | rs = (UA_RegisteredSignal *)UA_calloc(1, sizeof(UA_RegisteredSignal)); |
289 | 0 | if(!rs) { |
290 | 0 | UA_UNLOCK(&el->elMutex); |
291 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
292 | 0 | } |
293 | | |
294 | 0 | #ifdef UA_HAVE_EPOLL |
295 | 0 | rs->rfd.es = &im->eventSource; |
296 | 0 | #endif |
297 | 0 | rs->signal = (int)interruptHandle; |
298 | 0 | rs->signalCallback = callback; |
299 | 0 | rs->context = interruptContext; |
300 | | |
301 | | /* Add to the InterruptManager */ |
302 | 0 | LIST_INSERT_HEAD(&pim->signals, rs, listPointers); |
303 | | |
304 | | /* Activate if we are already running */ |
305 | 0 | if(pim->im.eventSource.state == UA_EVENTSOURCESTATE_STARTED) |
306 | 0 | activateSignal(rs); |
307 | |
|
308 | 0 | UA_UNLOCK(&el->elMutex); |
309 | 0 | return UA_STATUSCODE_GOOD; |
310 | 0 | } |
311 | | |
312 | | static void |
313 | 0 | deregisterPOSIXInterrupt(UA_InterruptManager *im, uintptr_t interruptHandle) { |
314 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)im->eventSource.eventLoop; |
315 | 0 | (void)el; |
316 | 0 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)im; |
317 | 0 | UA_LOCK(&el->elMutex); |
318 | |
|
319 | 0 | int signal = (int)interruptHandle; |
320 | 0 | UA_RegisteredSignal *rs; |
321 | 0 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
322 | 0 | if(rs->signal == signal) |
323 | 0 | break; |
324 | 0 | } |
325 | 0 | if(rs) { |
326 | 0 | deactivateSignal(rs); |
327 | 0 | LIST_REMOVE(rs, listPointers); |
328 | 0 | UA_free(rs); |
329 | 0 | } |
330 | |
|
331 | 0 | UA_UNLOCK(&el->elMutex); |
332 | 0 | } |
333 | | |
334 | | static UA_StatusCode |
335 | 553 | startPOSIXInterruptManager(UA_EventSource *es) { |
336 | 553 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
337 | 553 | (void)el; |
338 | 553 | UA_LOCK(&el->elMutex); |
339 | | |
340 | | /* Check the state */ |
341 | 553 | if(es->state != UA_EVENTSOURCESTATE_STOPPED) { |
342 | 0 | UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
343 | 0 | "Interrupt\t| To start the InterruptManager, " |
344 | 0 | "it has to be registered in an EventLoop and not started"); |
345 | 0 | UA_UNLOCK(&el->elMutex); |
346 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
347 | 0 | } |
348 | | |
349 | 553 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)es; |
350 | 553 | UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
351 | 553 | "Interrupt\t| Starting the InterruptManager"); |
352 | | |
353 | | /* Activate the registered signal handlers */ |
354 | 553 | UA_RegisteredSignal*rs; |
355 | 553 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
356 | 0 | activateSignal(rs); |
357 | 0 | } |
358 | | |
359 | | /* Set the EventSource to the started state */ |
360 | 553 | es->state = UA_EVENTSOURCESTATE_STARTED; |
361 | | |
362 | 553 | UA_UNLOCK(&el->elMutex); |
363 | 553 | return UA_STATUSCODE_GOOD; |
364 | 553 | } |
365 | | |
366 | | static void |
367 | 553 | stopPOSIXInterruptManager(UA_EventSource *es) { |
368 | 553 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
369 | 553 | (void)el; |
370 | 553 | UA_LOCK(&el->elMutex); |
371 | | |
372 | 553 | if(es->state != UA_EVENTSOURCESTATE_STARTED) { |
373 | 0 | UA_UNLOCK(&el->elMutex); |
374 | 0 | return; |
375 | 0 | } |
376 | | |
377 | 553 | UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
378 | 553 | "Interrupt\t| Stopping the InterruptManager"); |
379 | | |
380 | | /* Close all registered signals */ |
381 | 553 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)es; |
382 | 553 | UA_RegisteredSignal*rs; |
383 | 553 | LIST_FOREACH(rs, &pim->signals, listPointers) { |
384 | 0 | deactivateSignal(rs); |
385 | 0 | } |
386 | | |
387 | | /* Immediately set to stopped */ |
388 | 553 | es->state = UA_EVENTSOURCESTATE_STOPPED; |
389 | | |
390 | 553 | UA_UNLOCK(&el->elMutex); |
391 | 553 | } |
392 | | |
393 | | static UA_StatusCode |
394 | 553 | freePOSIXInterruptmanager(UA_EventSource *es) { |
395 | 553 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; |
396 | 553 | (void)el; |
397 | 553 | UA_LOCK_ASSERT(&el->elMutex); |
398 | | |
399 | 553 | if(es->state >= UA_EVENTSOURCESTATE_STARTING) { |
400 | 0 | UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
401 | 0 | "Interrupt\t| The EventSource must be stopped " |
402 | 0 | "before it can be deleted"); |
403 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
404 | 0 | } |
405 | | |
406 | | /* Deactivate and remove all registered signals */ |
407 | 553 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *)es; |
408 | 553 | UA_RegisteredSignal *rs, *rs_tmp; |
409 | 553 | LIST_FOREACH_SAFE(rs, &pim->signals, listPointers, rs_tmp) { |
410 | 0 | deactivateSignal(rs); |
411 | 0 | LIST_REMOVE(rs, listPointers); |
412 | 0 | UA_free(rs); |
413 | 0 | } |
414 | | |
415 | 553 | UA_String_clear(&es->name); |
416 | 553 | UA_free(es); |
417 | | |
418 | | #ifndef UA_HAVE_EPOLL |
419 | | singletonIM = NULL; /* Reset the global singleton pointer */ |
420 | | #endif |
421 | | |
422 | 553 | return UA_STATUSCODE_GOOD; |
423 | 553 | } |
424 | | |
425 | | UA_InterruptManager * |
426 | 553 | UA_InterruptManager_new_POSIX(const UA_String eventSourceName) { |
427 | | #ifndef UA_HAVE_EPOLL |
428 | | /* There can be only one InterruptManager if epoll is not present */ |
429 | | if(singletonIM) |
430 | | return NULL; |
431 | | #endif |
432 | | |
433 | 553 | UA_POSIXInterruptManager *pim = (UA_POSIXInterruptManager *) |
434 | 553 | UA_calloc(1, sizeof(UA_POSIXInterruptManager)); |
435 | 553 | if(!pim) |
436 | 0 | return NULL; |
437 | | |
438 | | #ifndef UA_HAVE_EPOLL |
439 | | singletonIM = pim; /* Register the singleton singleton pointer */ |
440 | | #endif |
441 | | |
442 | 553 | UA_InterruptManager *im = &pim->im; |
443 | 553 | im->eventSource.eventSourceType = UA_EVENTSOURCETYPE_INTERRUPTMANAGER; |
444 | 553 | UA_String_copy(&eventSourceName, &im->eventSource.name); |
445 | 553 | im->eventSource.start = startPOSIXInterruptManager; |
446 | 553 | im->eventSource.stop = stopPOSIXInterruptManager; |
447 | 553 | im->eventSource.free = freePOSIXInterruptmanager; |
448 | 553 | im->registerInterrupt = registerPOSIXInterrupt; |
449 | 553 | im->deregisterInterrupt = deregisterPOSIXInterrupt; |
450 | 553 | return im; |
451 | 553 | } |
452 | | |
453 | | #endif |