1 /* AirScan (a.k.a. eSCL) backend for SANE
2 *
3 * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
4 * See LICENSE for license terms and conditions
5 *
6 * Event loop (runs in separate thread)
7 */
8
9 #include "airscan.h"
10
11 #include <avahi-common/simple-watch.h>
12 #include <avahi-common/timeval.h>
13
14 #include <errno.h>
15 #include <unistd.h>
16
17 /******************** Constants *********************/
18 #define ELOOP_START_STOP_CALLBACKS_MAX 8
19
20 /******************** Static variables *********************/
21 static AvahiSimplePoll *eloop_poll;
22 static pthread_t eloop_thread;
23 static pthread_mutex_t eloop_mutex;
24 static bool eloop_thread_running;
25 static ll_head eloop_call_pending_list;
26 static bool eloop_poll_restart;
27
28 static __thread char eloop_estring[256];
29 static void (*eloop_start_stop_callbacks[ELOOP_START_STOP_CALLBACKS_MAX]) (bool);
30 static int eloop_start_stop_callbacks_count;
31
32 /******************** Standard errors *********************/
33 error ERROR_ENOMEM = (error) "Out of memory";
34
35 /******************** Forward declarations *********************/
36 static int
37 eloop_poll_func (struct pollfd *ufds, unsigned int nfds, int timeout, void *p);
38
39 static void
40 eloop_call_execute (void);
41
42 /* Initialize event loop
43 */
44 SANE_Status
eloop_init(void)45 eloop_init (void)
46 {
47 pthread_mutexattr_t attr;
48 bool attr_initialized = false;
49 bool mutex_initialized = false;
50 SANE_Status status = SANE_STATUS_NO_MEM;
51
52 ll_init(&eloop_call_pending_list);
53 eloop_start_stop_callbacks_count = 0;
54
55 /* Initialize eloop_mutex */
56 if (pthread_mutexattr_init(&attr)) {
57 goto DONE;
58 }
59
60 attr_initialized = true;
61 if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) {
62 goto DONE;
63 }
64
65 if (pthread_mutex_init(&eloop_mutex, &attr)) {
66 goto DONE;
67 }
68
69 mutex_initialized = true;
70
71 /* Create AvahiSimplePoll */
72 eloop_poll = avahi_simple_poll_new();
73 if (eloop_poll == NULL) {
74 goto DONE;
75 }
76
77 avahi_simple_poll_set_func(eloop_poll, eloop_poll_func, NULL);
78
79 /* Update status */
80 status = SANE_STATUS_GOOD;
81
82 /* Cleanup and exit */
83 DONE:
84 if (attr_initialized) {
85 pthread_mutexattr_destroy(&attr);
86 }
87
88 if (status != SANE_STATUS_GOOD && mutex_initialized) {
89 pthread_mutex_destroy(&eloop_mutex);
90 }
91
92 return status;
93 }
94
95 /* Cleanup event loop
96 */
97 void
eloop_cleanup(void)98 eloop_cleanup (void)
99 {
100 if (eloop_poll != NULL) {
101 avahi_simple_poll_free(eloop_poll);
102 pthread_mutex_destroy(&eloop_mutex);
103 eloop_poll = NULL;
104 }
105 }
106
107 /* Add start/stop callback. This callback is called
108 * on a event loop thread context, once when event
109 * loop is started, and second time when it is stopped
110 *
111 * Start callbacks are called in the same order as
112 * they were added. Stop callbacks are called in a
113 * reverse order
114 */
115 void
eloop_add_start_stop_callback(void (* callback)(bool start))116 eloop_add_start_stop_callback (void (*callback) (bool start))
117 {
118 log_assert(NULL,
119 eloop_start_stop_callbacks_count < ELOOP_START_STOP_CALLBACKS_MAX);
120
121 eloop_start_stop_callbacks[eloop_start_stop_callbacks_count] = callback;
122 eloop_start_stop_callbacks_count ++;
123 }
124
125 /* Poll function hook
126 */
127 static int
eloop_poll_func(struct pollfd * ufds,unsigned int nfds,int timeout,void * userdata)128 eloop_poll_func (struct pollfd *ufds, unsigned int nfds, int timeout,
129 void *userdata)
130 {
131 int rc;
132
133 (void) userdata;
134
135 eloop_poll_restart = false;
136
137 pthread_mutex_unlock(&eloop_mutex);
138 rc = poll(ufds, nfds, timeout);
139 pthread_mutex_lock(&eloop_mutex);
140
141 /* Avahi multithreading support is semi-broken. Though new
142 * AvahiWatch could be added from a context of any thread
143 * (Avahi internal structures are properly interlocked, and
144 * event loop thread is properly woken by poll->watch_new()),
145 * Avahi internal indices are only rebuild in the beginning
146 * of the avahi_simple_poll_iterate(), when avahi_simple_poll_prepare()
147 * is called. So when Avahi returns from the poll() syscall
148 * and calls avahi_simple_poll_dispatch(), it asserts, if new
149 * AvahiWatch, which causes process to crash.
150 *
151 * To work around this crash, we force avahi_simple_poll_iterate()
152 * to exit before calling of avahi_simple_poll_dispatch() by
153 * returning error code from here. The subsequent call of
154 * avahi_simple_poll_iterate() fixes the situation by calling
155 * avahi_simple_poll_prepare() first.
156 *
157 * The returned error code cannot be EINTR, because interrupted
158 * system call errors are ignored by Avahi (it simply restarts
159 * the operation) and needs to be distinguished by a "normal"
160 * errors, so event loop in the eloop_thread_func() will handle
161 * it in appropriate manner.
162 *
163 * For now we use EBUSY error code for this purpose
164 */
165 if (eloop_poll_restart) {
166 /* We have to return an error other than EINTR to restart the
167 avahi loop. */
168 errno = EBUSY;
169 return -1;
170 }
171
172 return rc;
173 }
174
175 /* Event loop thread main function
176 */
177 static void*
eloop_thread_func(void * data)178 eloop_thread_func (void *data)
179 {
180 int i;
181
182 (void) data;
183
184 pthread_mutex_lock(&eloop_mutex);
185
186 for (i = 0; i < eloop_start_stop_callbacks_count; i ++) {
187 eloop_start_stop_callbacks[i](true);
188 }
189
190 __atomic_store_n(&eloop_thread_running, true, __ATOMIC_SEQ_CST);
191
192 do {
193 eloop_call_execute();
194 i = avahi_simple_poll_iterate(eloop_poll, -1);
195 } while (i == 0 || (i < 0 && (errno == EINTR || errno == EBUSY)));
196
197 for (i = eloop_start_stop_callbacks_count - 1; i >= 0; i --) {
198 eloop_start_stop_callbacks[i](false);
199 }
200
201 pthread_mutex_unlock(&eloop_mutex);
202
203 return NULL;
204 }
205
206 /* Start event loop thread.
207 */
208 void
eloop_thread_start(void)209 eloop_thread_start (void)
210 {
211 int rc;
212 useconds_t usec = 100;
213
214 rc = pthread_create(&eloop_thread, NULL, eloop_thread_func, NULL);
215 if (rc != 0) {
216 log_panic(NULL, "pthread_create: %s", strerror(rc));
217 }
218
219 /* Wait until thread is started and all start callbacks are executed */
220 while (!__atomic_load_n(&eloop_thread_running, __ATOMIC_SEQ_CST)) {
221 usleep(usec);
222 usec += usec;
223 }
224 }
225
226 /* Stop event loop thread and wait until its termination
227 */
228 void
eloop_thread_stop(void)229 eloop_thread_stop (void)
230 {
231 if (__atomic_load_n(&eloop_thread_running, __ATOMIC_SEQ_CST)) {
232 avahi_simple_poll_quit(eloop_poll);
233 pthread_join(eloop_thread, NULL);
234 __atomic_store_n(&eloop_thread_running, false, __ATOMIC_SEQ_CST);
235 }
236 }
237
238 /* Acquire event loop mutex
239 */
240 void
eloop_mutex_lock(void)241 eloop_mutex_lock (void)
242 {
243 pthread_mutex_lock(&eloop_mutex);
244 }
245
246 /* Release event loop mutex
247 */
248 void
eloop_mutex_unlock(void)249 eloop_mutex_unlock (void)
250 {
251 pthread_mutex_unlock(&eloop_mutex);
252 }
253
254 /* Wait on conditional variable under the event loop mutex
255 */
256 void
eloop_cond_wait(pthread_cond_t * cond)257 eloop_cond_wait (pthread_cond_t *cond)
258 {
259 pthread_cond_wait(cond, &eloop_mutex);
260 }
261
262 /* Get AvahiPoll that runs in event loop thread
263 */
264 const AvahiPoll*
eloop_poll_get(void)265 eloop_poll_get (void)
266 {
267 return avahi_simple_poll_get(eloop_poll);
268 }
269
270 /* eloop_call_pending represents a pending eloop_call
271 */
272 typedef struct {
273 void (*func)(void*); /* Function to be called */
274 void *data; /* It's argument */
275 uint64_t callid; /* For eloop_call_cancel() */
276 ll_node node; /* In eloop_call_pending_list */
277 } eloop_call_pending;
278
279 /* Execute function calls deferred by eloop_call()
280 */
281 static void
eloop_call_execute(void)282 eloop_call_execute (void)
283 {
284 ll_node *node;
285
286 while ((node = ll_pop_beg(&eloop_call_pending_list)) != NULL) {
287 eloop_call_pending *pending;
288
289 pending = OUTER_STRUCT(node, eloop_call_pending, node);
290 pending->func(pending->data);
291 mem_free(pending);
292 }
293 }
294
295 /* Call function on a context of event loop thread
296 * The returned value can be supplied as a `callid'
297 * parameter for the eloop_call_cancel() function
298 */
299 uint64_t
eloop_call(void (* func)(void *),void * data)300 eloop_call (void (*func)(void*), void *data)
301 {
302 eloop_call_pending *p = mem_new(eloop_call_pending, 1);
303 static uint64_t callid;
304 uint64_t ret;
305
306 p->func = func;
307 p->data = data;
308
309 pthread_mutex_lock(&eloop_mutex);
310 ret = ++ callid;
311 p->callid = ret;
312 ll_push_end(&eloop_call_pending_list, &p->node);
313 pthread_mutex_unlock(&eloop_mutex);
314
315 avahi_simple_poll_wakeup(eloop_poll);
316
317 return ret;
318 }
319
320 /* Cancel pending eloop_call
321 *
322 * This is safe to cancel already finished call (at this
323 * case nothing will happen)
324 */
325 void
eloop_call_cancel(uint64_t callid)326 eloop_call_cancel (uint64_t callid)
327 {
328 ll_node *node;
329
330 for (LL_FOR_EACH(node, &eloop_call_pending_list)) {
331 eloop_call_pending *p = OUTER_STRUCT(node, eloop_call_pending, node);
332
333 if (p->callid == callid) {
334 ll_del(&p->node);
335 mem_free(p);
336 return;
337 }
338 }
339 }
340
341 /* Event notifier. Calls user-defined function on a context
342 * of event loop thread, when event is triggered. This is
343 * safe to trigger the event from a context of any thread
344 * or even from a signal handler
345 */
346 struct eloop_event {
347 pollable *p; /* Underlying pollable event */
348 eloop_fdpoll *fdpoll; /* Underlying fdpoll */
349 void (*callback)(void*); /* user-defined callback */
350 void *data; /* callback's argument */
351 };
352
353 /* eloop_event eloop_fdpoll callback
354 */
355 static void
eloop_event_callback(int fd,void * data,ELOOP_FDPOLL_MASK mask)356 eloop_event_callback (int fd, void *data, ELOOP_FDPOLL_MASK mask)
357 {
358 eloop_event *event = data;
359
360 (void) fd;
361 (void) mask;
362
363 pollable_reset(event->p);
364 event->callback(event->data);
365 }
366
367 /* Create new event notifier. May return NULL
368 */
369 eloop_event*
eloop_event_new(void (* callback)(void *),void * data)370 eloop_event_new (void (*callback)(void *), void *data)
371 {
372 eloop_event *event;
373 pollable *p;
374
375 p = pollable_new();
376 if (p == NULL) {
377 return NULL;
378 }
379
380 event = mem_new(eloop_event, 1);
381 event->p = p;
382 event->callback = callback;
383 event->data = data;
384
385 event->fdpoll = eloop_fdpoll_new(pollable_get_fd(p),
386 eloop_event_callback, event);
387 eloop_fdpoll_set_mask(event->fdpoll, ELOOP_FDPOLL_READ);
388
389 return event;
390 }
391
392 /* Destroy event notifier
393 */
394 void
eloop_event_free(eloop_event * event)395 eloop_event_free (eloop_event *event)
396 {
397 eloop_fdpoll_free(event->fdpoll);
398 pollable_free(event->p);
399 mem_free(event);
400 }
401
402 /* Trigger an event
403 */
404 void
eloop_event_trigger(eloop_event * event)405 eloop_event_trigger (eloop_event *event)
406 {
407 pollable_signal(event->p);
408 }
409
410 /* Timer. Calls user-defined function after a specified
411 * interval
412 */
413 struct eloop_timer {
414 AvahiTimeout *timeout; /* Underlying AvahiTimeout */
415 void (*callback)(void *); /* User callback */
416 void *data; /* User data */
417 };
418
419 /* eloop_timer callback for AvahiTimeout
420 */
421 static void
eloop_timer_callback(AvahiTimeout * t,void * data)422 eloop_timer_callback (AvahiTimeout *t, void *data)
423 {
424 eloop_timer *timer = data;
425
426 (void) t;
427
428 timer->callback(timer->data);
429 eloop_timer_cancel(timer);
430 }
431
432 /* Create new timer. Timeout is in milliseconds
433 */
434 eloop_timer*
eloop_timer_new(int timeout,void (* callback)(void *),void * data)435 eloop_timer_new (int timeout, void (*callback)(void *), void *data)
436 {
437 const AvahiPoll *poll = eloop_poll_get();
438 eloop_timer *timer = mem_new(eloop_timer, 1);
439 struct timeval end;
440
441 avahi_elapse_time(&end, timeout, 0);
442
443 timer->timeout = poll->timeout_new(poll, &end, eloop_timer_callback, timer);
444 timer->callback = callback;
445 timer->data = data;
446
447 return timer;
448 }
449
450 /* Cancel a timer
451 *
452 * Caller SHOULD NOT cancel expired timer (timer with called
453 * callback) -- this is done automatically
454 */
455 void
eloop_timer_cancel(eloop_timer * timer)456 eloop_timer_cancel (eloop_timer *timer)
457 {
458 const AvahiPoll *poll = eloop_poll_get();
459
460 poll->timeout_free(timer->timeout);
461 mem_free(timer);
462 }
463
464 /* Convert ELOOP_FDPOLL_MASK to string. Used for logging.
465 */
466 const char*
eloop_fdpoll_mask_str(ELOOP_FDPOLL_MASK mask)467 eloop_fdpoll_mask_str (ELOOP_FDPOLL_MASK mask)
468 {
469 switch (mask & ELOOP_FDPOLL_BOTH) {
470 case 0: return "{--}";
471 case ELOOP_FDPOLL_READ: return "{r-}";
472 case ELOOP_FDPOLL_WRITE: return "{-w}";
473 case ELOOP_FDPOLL_BOTH: return "{rw}";
474 }
475
476 return "{??}"; /* Should never happen indeed */
477 }
478
479 /* eloop_fdpoll notifies user when file becomes
480 * readable, writable or both, depending on its
481 * event mask
482 */
483 struct eloop_fdpoll {
484 AvahiWatch *watch; /* Underlying AvahiWatch */
485 int fd; /* Underlying file descriptor */
486 ELOOP_FDPOLL_MASK mask; /* Mask of active events */
487 void (*callback)( /* User-defined callback */
488 int, void*, ELOOP_FDPOLL_MASK);
489 void *data; /* Callback's data */
490 };
491
492 /* eloop_fdpoll callback for AvahiWatch
493 */
494 static void
eloop_fdpoll_callback(AvahiWatch * w,int fd,AvahiWatchEvent event,void * data)495 eloop_fdpoll_callback (AvahiWatch *w, int fd, AvahiWatchEvent event,
496 void *data)
497 {
498 eloop_fdpoll *fdpoll = data;
499 ELOOP_FDPOLL_MASK mask = 0;
500
501 (void) w;
502
503 if ((event & AVAHI_WATCH_IN) != 0) {
504 mask |= ELOOP_FDPOLL_READ;
505 }
506
507 if ((event & AVAHI_WATCH_OUT) != 0) {
508 mask |= ELOOP_FDPOLL_WRITE;
509 }
510
511 fdpoll->callback(fd, fdpoll->data, mask);
512 }
513
514 /* Create eloop_fdpoll
515 *
516 * Callback will be called, when file will be ready for read/write/both,
517 * depending on mask
518 *
519 * Initial mask value is 0, and it can be changed, using
520 * eloop_fdpoll_set_mask() function
521 */
522 eloop_fdpoll*
eloop_fdpoll_new(int fd,void (* callback)(int,void *,ELOOP_FDPOLL_MASK),void * data)523 eloop_fdpoll_new (int fd,
524 void (*callback) (int, void*, ELOOP_FDPOLL_MASK), void *data)
525 {
526 const AvahiPoll *poll = eloop_poll_get();
527 eloop_fdpoll *fdpoll = mem_new(eloop_fdpoll, 1);
528
529 fdpoll->fd = fd;
530 fdpoll->callback = callback;
531 fdpoll->data = data;
532
533 eloop_poll_restart = true;
534 fdpoll->watch = poll->watch_new(poll, fd, 0, eloop_fdpoll_callback, fdpoll);
535
536 return fdpoll;
537 }
538
539 /* Destroy eloop_fdpoll
540 */
541 void
eloop_fdpoll_free(eloop_fdpoll * fdpoll)542 eloop_fdpoll_free (eloop_fdpoll *fdpoll)
543 {
544 const AvahiPoll *poll = eloop_poll_get();
545
546 poll->watch_free(fdpoll->watch);
547 mem_free(fdpoll);
548 }
549
550 /* Set eloop_fdpoll event mask. It returns a previous value of event mask
551 */
552 ELOOP_FDPOLL_MASK
eloop_fdpoll_set_mask(eloop_fdpoll * fdpoll,ELOOP_FDPOLL_MASK mask)553 eloop_fdpoll_set_mask (eloop_fdpoll *fdpoll, ELOOP_FDPOLL_MASK mask)
554 {
555 ELOOP_FDPOLL_MASK old_mask = fdpoll->mask;
556
557 if (old_mask != mask) {
558 const AvahiPoll *poll = eloop_poll_get();
559 AvahiWatchEvent events = 0;
560
561 if ((mask & ELOOP_FDPOLL_READ) != 0) {
562 events |= AVAHI_WATCH_IN;
563 }
564
565 if ((mask & ELOOP_FDPOLL_WRITE) != 0) {
566 events |= AVAHI_WATCH_OUT;
567 }
568
569 fdpoll->mask = mask;
570 poll->watch_update(fdpoll->watch, events);
571 }
572
573 return old_mask;
574 }
575
576 /* Format error string, as printf() does and save result
577 * in the memory, owned by the event loop
578 *
579 * Caller should not free returned string. This is safe
580 * to use the returned string as an argument to the
581 * subsequent eloop_eprintf() call.
582 *
583 * The returned string remains valid until next call
584 * to eloop_eprintf(), which makes it usable to
585 * report errors up by the stack. However, it should
586 * not be assumed, that the string will remain valid
587 * on a next eloop roll, so don't save this string
588 * anywhere, if you need to do so, create a copy!
589 */
590 error
eloop_eprintf(const char * fmt,...)591 eloop_eprintf(const char *fmt, ...)
592 {
593 va_list ap;
594 char buf[sizeof(eloop_estring)];
595
596 va_start(ap, fmt);
597 vsnprintf(buf, sizeof(buf), fmt, ap);
598 strcpy(eloop_estring, buf);
599 va_end(ap);
600
601 return ERROR(eloop_estring);
602 }
603
604 /* vim:ts=8:sw=4:et
605 */
606