pacemaker 2.1.6-6fdc9deea29
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
mainloop.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2023 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10#include <crm_internal.h>
11
12#ifndef _GNU_SOURCE
13# define _GNU_SOURCE
14#endif
15
16#include <stdlib.h>
17#include <string.h>
18#include <signal.h>
19#include <errno.h>
20
21#include <sys/wait.h>
22
23#include <crm/crm.h>
24#include <crm/common/xml.h>
25#include <crm/common/mainloop.h>
27
28#include <qb/qbarray.h>
29
30struct mainloop_child_s {
31 pid_t pid;
32 char *desc;
33 unsigned timerid;
34 gboolean timeout;
35 void *privatedata;
36
38
39 /* Called when a process dies */
40 void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode);
41};
42
43struct trigger_s {
44 GSource source;
45 gboolean running;
46 gboolean trigger;
47 void *user_data;
48 guint id;
49
50};
51
52struct mainloop_timer_s {
53 guint id;
54 guint period_ms;
55 bool repeat;
56 char *name;
57 GSourceFunc cb;
58 void *userdata;
59};
60
61static gboolean
62crm_trigger_prepare(GSource * source, gint * timeout)
63{
64 crm_trigger_t *trig = (crm_trigger_t *) source;
65
66 /* cluster-glue's FD and IPC related sources make use of
67 * g_source_add_poll() but do not set a timeout in their prepare
68 * functions
69 *
70 * This means mainloop's poll() will block until an event for one
71 * of these sources occurs - any /other/ type of source, such as
72 * this one or g_idle_*, that doesn't use g_source_add_poll() is
73 * S-O-L and won't be processed until there is something fd-based
74 * happens.
75 *
76 * Luckily the timeout we can set here affects all sources and
77 * puts an upper limit on how long poll() can take.
78 *
79 * So unconditionally set a small-ish timeout, not too small that
80 * we're in constant motion, which will act as an upper bound on
81 * how long the signal handling might be delayed for.
82 */
83 *timeout = 500; /* Timeout in ms */
84
85 return trig->trigger;
86}
87
88static gboolean
89crm_trigger_check(GSource * source)
90{
91 crm_trigger_t *trig = (crm_trigger_t *) source;
92
93 return trig->trigger;
94}
95
106static gboolean
107crm_trigger_dispatch(GSource *source, GSourceFunc callback, gpointer userdata)
108{
109 gboolean rc = G_SOURCE_CONTINUE;
110 crm_trigger_t *trig = (crm_trigger_t *) source;
111
112 if (trig->running) {
113 /* Wait until the existing job is complete before starting the next one */
114 return G_SOURCE_CONTINUE;
115 }
116 trig->trigger = FALSE;
117
118 if (callback) {
119 int callback_rc = callback(trig->user_data);
120
121 if (callback_rc < 0) {
122 crm_trace("Trigger handler %p not yet complete", trig);
123 trig->running = TRUE;
124 } else if (callback_rc == 0) {
125 rc = G_SOURCE_REMOVE;
126 }
127 }
128 return rc;
129}
130
131static void
132crm_trigger_finalize(GSource * source)
133{
134 crm_trace("Trigger %p destroyed", source);
135}
136
137static GSourceFuncs crm_trigger_funcs = {
138 crm_trigger_prepare,
139 crm_trigger_check,
140 crm_trigger_dispatch,
141 crm_trigger_finalize,
142};
143
144static crm_trigger_t *
145mainloop_setup_trigger(GSource * source, int priority, int (*dispatch) (gpointer user_data),
146 gpointer userdata)
147{
148 crm_trigger_t *trigger = NULL;
149
150 trigger = (crm_trigger_t *) source;
151
152 trigger->id = 0;
153 trigger->trigger = FALSE;
154 trigger->user_data = userdata;
155
156 if (dispatch) {
157 g_source_set_callback(source, dispatch, trigger, NULL);
158 }
159
160 g_source_set_priority(source, priority);
161 g_source_set_can_recurse(source, FALSE);
162
163 trigger->id = g_source_attach(source, NULL);
164 return trigger;
165}
166
167void
169{
170 crm_trace("Trigger handler %p complete", trig);
171 trig->running = FALSE;
172}
173
187mainloop_add_trigger(int priority, int (*dispatch) (gpointer user_data),
188 gpointer userdata)
189{
190 GSource *source = NULL;
191
192 CRM_ASSERT(sizeof(crm_trigger_t) > sizeof(GSource));
193 source = g_source_new(&crm_trigger_funcs, sizeof(crm_trigger_t));
194 CRM_ASSERT(source != NULL);
195
196 return mainloop_setup_trigger(source, priority, dispatch, userdata);
197}
198
199void
201{
202 if(source) {
203 source->trigger = TRUE;
204 }
205}
206
207gboolean
209{
210 GSource *gs = NULL;
211
212 if(source == NULL) {
213 return TRUE;
214 }
215
216 gs = (GSource *)source;
217
218 g_source_destroy(gs); /* Remove from mainloop, ref_count-- */
219 g_source_unref(gs); /* The caller no longer carries a reference to source
220 *
221 * At this point the source should be free'd,
222 * unless we're currently processing said
223 * source, in which case mainloop holds an
224 * additional reference and it will be free'd
225 * once our processing completes
226 */
227 return TRUE;
228}
229
230// Define a custom glib source for signal handling
231
232// Data structure for custom glib source
233typedef struct signal_s {
234 crm_trigger_t trigger; // trigger that invoked source (must be first)
235 void (*handler) (int sig); // signal handler
236 int signal; // signal that was received
238
239// Table to associate signal handlers with signal numbers
240static crm_signal_t *crm_signals[NSIG];
241
253static gboolean
254crm_signal_dispatch(GSource *source, GSourceFunc callback, gpointer userdata)
255{
256 crm_signal_t *sig = (crm_signal_t *) source;
257
258 if(sig->signal != SIGCHLD) {
259 crm_notice("Caught '%s' signal "CRM_XS" %d (%s handler)",
260 strsignal(sig->signal), sig->signal,
261 (sig->handler? "invoking" : "no"));
262 }
263
264 sig->trigger.trigger = FALSE;
265 if (sig->handler) {
266 sig->handler(sig->signal);
267 }
268 return TRUE;
269}
270
280static void
281mainloop_signal_handler(int sig)
282{
283 if (sig > 0 && sig < NSIG && crm_signals[sig] != NULL) {
284 mainloop_set_trigger((crm_trigger_t *) crm_signals[sig]);
285 }
286}
287
288// Functions implementing our custom glib source for signal handling
289static GSourceFuncs crm_signal_funcs = {
290 crm_trigger_prepare,
291 crm_trigger_check,
292 crm_signal_dispatch,
293 crm_trigger_finalize,
294};
295
310{
311 sigset_t mask;
312 struct sigaction sa;
313 struct sigaction old;
314
315 if (sigemptyset(&mask) < 0) {
316 crm_err("Could not set handler for signal %d: %s",
317 sig, pcmk_rc_str(errno));
318 return SIG_ERR;
319 }
320
321 memset(&sa, 0, sizeof(struct sigaction));
322 sa.sa_handler = dispatch;
323 sa.sa_flags = SA_RESTART;
324 sa.sa_mask = mask;
325
326 if (sigaction(sig, &sa, &old) < 0) {
327 crm_err("Could not set handler for signal %d: %s",
328 sig, pcmk_rc_str(errno));
329 return SIG_ERR;
330 }
331 return old.sa_handler;
332}
333
334static void
335mainloop_destroy_signal_entry(int sig)
336{
337 crm_signal_t *tmp = crm_signals[sig];
338
339 crm_signals[sig] = NULL;
340
341 crm_trace("Destroying signal %d", sig);
343}
344
356gboolean
357mainloop_add_signal(int sig, void (*dispatch) (int sig))
358{
359 GSource *source = NULL;
360 int priority = G_PRIORITY_HIGH - 1;
361
362 if (sig == SIGTERM) {
363 /* TERM is higher priority than other signals,
364 * signals are higher priority than other ipc.
365 * Yes, minus: smaller is "higher"
366 */
367 priority--;
368 }
369
370 if (sig >= NSIG || sig < 0) {
371 crm_err("Signal %d is out of range", sig);
372 return FALSE;
373
374 } else if (crm_signals[sig] != NULL && crm_signals[sig]->handler == dispatch) {
375 crm_trace("Signal handler for %d is already installed", sig);
376 return TRUE;
377
378 } else if (crm_signals[sig] != NULL) {
379 crm_err("Different signal handler for %d is already installed", sig);
380 return FALSE;
381 }
382
383 CRM_ASSERT(sizeof(crm_signal_t) > sizeof(GSource));
384 source = g_source_new(&crm_signal_funcs, sizeof(crm_signal_t));
385
386 crm_signals[sig] = (crm_signal_t *) mainloop_setup_trigger(source, priority, NULL, NULL);
387 CRM_ASSERT(crm_signals[sig] != NULL);
388
389 crm_signals[sig]->handler = dispatch;
390 crm_signals[sig]->signal = sig;
391
392 if (crm_signal_handler(sig, mainloop_signal_handler) == SIG_ERR) {
393 mainloop_destroy_signal_entry(sig);
394 return FALSE;
395 }
396#if 0
397 /* If we want signals to interrupt mainloop's poll(), instead of waiting for
398 * the timeout, then we should call siginterrupt() below
399 *
400 * For now, just enforce a low timeout
401 */
402 if (siginterrupt(sig, 1) < 0) {
403 crm_perror(LOG_INFO, "Could not enable system call interruptions for signal %d", sig);
404 }
405#endif
406
407 return TRUE;
408}
409
410gboolean
412{
413 if (sig >= NSIG || sig < 0) {
414 crm_err("Signal %d is out of range", sig);
415 return FALSE;
416
417 } else if (crm_signal_handler(sig, NULL) == SIG_ERR) {
418 crm_perror(LOG_ERR, "Could not uninstall signal handler for signal %d", sig);
419 return FALSE;
420
421 } else if (crm_signals[sig] == NULL) {
422 return TRUE;
423 }
424 mainloop_destroy_signal_entry(sig);
425 return TRUE;
426}
427
428static qb_array_t *gio_map = NULL;
429
430void
432{
433 if (gio_map) {
434 qb_array_free(gio_map);
435 }
436
437 for (int sig = 0; sig < NSIG; ++sig) {
438 mainloop_destroy_signal_entry(sig);
439 }
440}
441
442/*
443 * libqb...
444 */
445struct gio_to_qb_poll {
446 int32_t is_used;
447 guint source;
448 int32_t events;
449 void *data;
450 qb_ipcs_dispatch_fn_t fn;
451 enum qb_loop_priority p;
452};
453
454static gboolean
455gio_read_socket(GIOChannel * gio, GIOCondition condition, gpointer data)
456{
457 struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data;
458 gint fd = g_io_channel_unix_get_fd(gio);
459
460 crm_trace("%p.%d %d", data, fd, condition);
461
462 /* if this assert get's hit, then there is a race condition between
463 * when we destroy a fd and when mainloop actually gives it up */
464 CRM_ASSERT(adaptor->is_used > 0);
465
466 return (adaptor->fn(fd, condition, adaptor->data) == 0);
467}
468
469static void
470gio_poll_destroy(gpointer data)
471{
472 struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data;
473
474 adaptor->is_used--;
475 CRM_ASSERT(adaptor->is_used >= 0);
476
477 if (adaptor->is_used == 0) {
478 crm_trace("Marking adaptor %p unused", adaptor);
479 adaptor->source = 0;
480 }
481}
482
491static gint
492conv_prio_libqb2glib(enum qb_loop_priority prio)
493{
494 switch (prio) {
495 case QB_LOOP_LOW: return G_PRIORITY_LOW;
496 case QB_LOOP_HIGH: return G_PRIORITY_HIGH;
497 default: return G_PRIORITY_DEFAULT; // QB_LOOP_MED
498 }
499}
500
510static enum qb_ipcs_rate_limit
511conv_libqb_prio2ratelimit(enum qb_loop_priority prio)
512{
513 switch (prio) {
514 case QB_LOOP_LOW: return QB_IPCS_RATE_SLOW;
515 case QB_LOOP_HIGH: return QB_IPCS_RATE_FAST;
516 default: return QB_IPCS_RATE_NORMAL; // QB_LOOP_MED
517 }
518}
519
520static int32_t
521gio_poll_dispatch_update(enum qb_loop_priority p, int32_t fd, int32_t evts,
522 void *data, qb_ipcs_dispatch_fn_t fn, int32_t add)
523{
524 struct gio_to_qb_poll *adaptor;
525 GIOChannel *channel;
526 int32_t res = 0;
527
528 res = qb_array_index(gio_map, fd, (void **)&adaptor);
529 if (res < 0) {
530 crm_err("Array lookup failed for fd=%d: %d", fd, res);
531 return res;
532 }
533
534 crm_trace("Adding fd=%d to mainloop as adaptor %p", fd, adaptor);
535
536 if (add && adaptor->source) {
537 crm_err("Adaptor for descriptor %d is still in-use", fd);
538 return -EEXIST;
539 }
540 if (!add && !adaptor->is_used) {
541 crm_err("Adaptor for descriptor %d is not in-use", fd);
542 return -ENOENT;
543 }
544
545 /* channel is created with ref_count = 1 */
546 channel = g_io_channel_unix_new(fd);
547 if (!channel) {
548 crm_err("No memory left to add fd=%d", fd);
549 return -ENOMEM;
550 }
551
552 if (adaptor->source) {
553 g_source_remove(adaptor->source);
554 adaptor->source = 0;
555 }
556
557 /* Because unlike the poll() API, glib doesn't tell us about HUPs by default */
558 evts |= (G_IO_HUP | G_IO_NVAL | G_IO_ERR);
559
560 adaptor->fn = fn;
561 adaptor->events = evts;
562 adaptor->data = data;
563 adaptor->p = p;
564 adaptor->is_used++;
565 adaptor->source =
566 g_io_add_watch_full(channel, conv_prio_libqb2glib(p), evts,
567 gio_read_socket, adaptor, gio_poll_destroy);
568
569 /* Now that mainloop now holds a reference to channel,
570 * thanks to g_io_add_watch_full(), drop ours from g_io_channel_unix_new().
571 *
572 * This means that channel will be free'd by:
573 * g_main_context_dispatch()
574 * -> g_source_destroy_internal()
575 * -> g_source_callback_unref()
576 * shortly after gio_poll_destroy() completes
577 */
578 g_io_channel_unref(channel);
579
580 crm_trace("Added to mainloop with gsource id=%d", adaptor->source);
581 if (adaptor->source > 0) {
582 return 0;
583 }
584
585 return -EINVAL;
586}
587
588static int32_t
589gio_poll_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts,
590 void *data, qb_ipcs_dispatch_fn_t fn)
591{
592 return gio_poll_dispatch_update(p, fd, evts, data, fn, QB_TRUE);
593}
594
595static int32_t
596gio_poll_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts,
597 void *data, qb_ipcs_dispatch_fn_t fn)
598{
599 return gio_poll_dispatch_update(p, fd, evts, data, fn, QB_FALSE);
600}
601
602static int32_t
603gio_poll_dispatch_del(int32_t fd)
604{
605 struct gio_to_qb_poll *adaptor;
606
607 crm_trace("Looking for fd=%d", fd);
608 if (qb_array_index(gio_map, fd, (void **)&adaptor) == 0) {
609 if (adaptor->source) {
610 g_source_remove(adaptor->source);
611 adaptor->source = 0;
612 }
613 }
614 return 0;
615}
616
617struct qb_ipcs_poll_handlers gio_poll_funcs = {
618 .job_add = NULL,
619 .dispatch_add = gio_poll_dispatch_add,
620 .dispatch_mod = gio_poll_dispatch_mod,
621 .dispatch_del = gio_poll_dispatch_del,
622};
623
624static enum qb_ipc_type
625pick_ipc_type(enum qb_ipc_type requested)
626{
627 const char *env = getenv("PCMK_ipc_type");
628
629 if (env && strcmp("shared-mem", env) == 0) {
630 return QB_IPC_SHM;
631 } else if (env && strcmp("socket", env) == 0) {
632 return QB_IPC_SOCKET;
633 } else if (env && strcmp("posix", env) == 0) {
634 return QB_IPC_POSIX_MQ;
635 } else if (env && strcmp("sysv", env) == 0) {
636 return QB_IPC_SYSV_MQ;
637 } else if (requested == QB_IPC_NATIVE) {
638 /* We prefer shared memory because the server never blocks on
639 * send. If part of a message fits into the socket, libqb
640 * needs to block until the remainder can be sent also.
641 * Otherwise the client will wait forever for the remaining
642 * bytes.
643 */
644 return QB_IPC_SHM;
645 }
646 return requested;
647}
648
649qb_ipcs_service_t *
650mainloop_add_ipc_server(const char *name, enum qb_ipc_type type,
651 struct qb_ipcs_service_handlers *callbacks)
652{
653 return mainloop_add_ipc_server_with_prio(name, type, callbacks, QB_LOOP_MED);
654}
655
656qb_ipcs_service_t *
657mainloop_add_ipc_server_with_prio(const char *name, enum qb_ipc_type type,
658 struct qb_ipcs_service_handlers *callbacks,
659 enum qb_loop_priority prio)
660{
661 int rc = 0;
662 qb_ipcs_service_t *server = NULL;
663
664 if (gio_map == NULL) {
665 gio_map = qb_array_create_2(64, sizeof(struct gio_to_qb_poll), 1);
666 }
667
668 server = qb_ipcs_create(name, 0, pick_ipc_type(type), callbacks);
669
670 if (server == NULL) {
671 crm_err("Could not create %s IPC server: %s (%d)", name, pcmk_strerror(rc), rc);
672 return NULL;
673 }
674
675 if (prio != QB_LOOP_MED) {
676 qb_ipcs_request_rate_limit(server, conv_libqb_prio2ratelimit(prio));
677 }
678
679 /* All clients should use at least ipc_buffer_max as their buffer size */
680 qb_ipcs_enforce_buffer_size(server, crm_ipc_default_buffer_size());
681 qb_ipcs_poll_handlers_set(server, &gio_poll_funcs);
682
683 rc = qb_ipcs_run(server);
684 if (rc < 0) {
685 crm_err("Could not start %s IPC server: %s (%d)", name, pcmk_strerror(rc), rc);
686 return NULL; // qb_ipcs_run() destroys server on failure
687 }
688
689 return server;
690}
691
692void
693mainloop_del_ipc_server(qb_ipcs_service_t * server)
694{
695 if (server) {
696 qb_ipcs_destroy(server);
697 }
698}
699
700struct mainloop_io_s {
701 char *name;
702 void *userdata;
703
704 int fd;
705 guint source;
706 crm_ipc_t *ipc;
707 GIOChannel *channel;
708
709 int (*dispatch_fn_ipc) (const char *buffer, ssize_t length, gpointer userdata);
710 int (*dispatch_fn_io) (gpointer userdata);
711 void (*destroy_fn) (gpointer userdata);
712
713};
714
725static gboolean
726mainloop_gio_callback(GIOChannel *gio, GIOCondition condition, gpointer data)
727{
728 gboolean rc = G_SOURCE_CONTINUE;
729 mainloop_io_t *client = data;
730
731 CRM_ASSERT(client->fd == g_io_channel_unix_get_fd(gio));
732
733 if (condition & G_IO_IN) {
734 if (client->ipc) {
735 long read_rc = 0L;
736 int max = 10;
737
738 do {
739 read_rc = crm_ipc_read(client->ipc);
740 if (read_rc <= 0) {
741 crm_trace("Could not read IPC message from %s: %s (%ld)",
742 client->name, pcmk_strerror(read_rc), read_rc);
743
744 } else if (client->dispatch_fn_ipc) {
745 const char *buffer = crm_ipc_buffer(client->ipc);
746
747 crm_trace("New %ld-byte IPC message from %s "
748 "after I/O condition %d",
749 read_rc, client->name, (int) condition);
750 if (client->dispatch_fn_ipc(buffer, read_rc, client->userdata) < 0) {
751 crm_trace("Connection to %s no longer required", client->name);
752 rc = G_SOURCE_REMOVE;
753 }
754 }
755
756 } while ((rc == G_SOURCE_CONTINUE) && (read_rc > 0) && --max > 0);
757
758 } else {
759 crm_trace("New I/O event for %s after I/O condition %d",
760 client->name, (int) condition);
761 if (client->dispatch_fn_io) {
762 if (client->dispatch_fn_io(client->userdata) < 0) {
763 crm_trace("Connection to %s no longer required", client->name);
764 rc = G_SOURCE_REMOVE;
765 }
766 }
767 }
768 }
769
770 if (client->ipc && !crm_ipc_connected(client->ipc)) {
771 crm_err("Connection to %s closed " CRM_XS "client=%p condition=%d",
772 client->name, client, condition);
773 rc = G_SOURCE_REMOVE;
774
775 } else if (condition & (G_IO_HUP | G_IO_NVAL | G_IO_ERR)) {
776 crm_trace("The connection %s[%p] has been closed (I/O condition=%d)",
777 client->name, client, condition);
778 rc = G_SOURCE_REMOVE;
779
780 } else if ((condition & G_IO_IN) == 0) {
781 /*
782 #define GLIB_SYSDEF_POLLIN =1
783 #define GLIB_SYSDEF_POLLPRI =2
784 #define GLIB_SYSDEF_POLLOUT =4
785 #define GLIB_SYSDEF_POLLERR =8
786 #define GLIB_SYSDEF_POLLHUP =16
787 #define GLIB_SYSDEF_POLLNVAL =32
788
789 typedef enum
790 {
791 G_IO_IN GLIB_SYSDEF_POLLIN,
792 G_IO_OUT GLIB_SYSDEF_POLLOUT,
793 G_IO_PRI GLIB_SYSDEF_POLLPRI,
794 G_IO_ERR GLIB_SYSDEF_POLLERR,
795 G_IO_HUP GLIB_SYSDEF_POLLHUP,
796 G_IO_NVAL GLIB_SYSDEF_POLLNVAL
797 } GIOCondition;
798
799 A bitwise combination representing a condition to watch for on an event source.
800
801 G_IO_IN There is data to read.
802 G_IO_OUT Data can be written (without blocking).
803 G_IO_PRI There is urgent data to read.
804 G_IO_ERR Error condition.
805 G_IO_HUP Hung up (the connection has been broken, usually for pipes and sockets).
806 G_IO_NVAL Invalid request. The file descriptor is not open.
807 */
808 crm_err("Strange condition: %d", condition);
809 }
810
811 /* G_SOURCE_REMOVE results in mainloop_gio_destroy() being called
812 * just before the source is removed from mainloop
813 */
814 return rc;
815}
816
817static void
818mainloop_gio_destroy(gpointer c)
819{
820 mainloop_io_t *client = c;
821 char *c_name = strdup(client->name);
822
823 /* client->source is valid but about to be destroyed (ref_count == 0) in gmain.c
824 * client->channel will still have ref_count > 0... should be == 1
825 */
826 crm_trace("Destroying client %s[%p]", c_name, c);
827
828 if (client->ipc) {
829 crm_ipc_close(client->ipc);
830 }
831
832 if (client->destroy_fn) {
833 void (*destroy_fn) (gpointer userdata) = client->destroy_fn;
834
835 client->destroy_fn = NULL;
836 destroy_fn(client->userdata);
837 }
838
839 if (client->ipc) {
840 crm_ipc_t *ipc = client->ipc;
841
842 client->ipc = NULL;
843 crm_ipc_destroy(ipc);
844 }
845
846 crm_trace("Destroyed client %s[%p]", c_name, c);
847
848 free(client->name); client->name = NULL;
849 free(client);
850
851 free(c_name);
852}
853
872int
873pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata,
874 const struct ipc_client_callbacks *callbacks,
875 mainloop_io_t **source)
876{
877 CRM_CHECK((ipc != NULL) && (callbacks != NULL), return EINVAL);
878
879 if (!crm_ipc_connect(ipc)) {
880 int rc = errno;
881 crm_debug("Connection to %s failed: %d", crm_ipc_name(ipc), errno);
882 return rc;
883 }
884 *source = mainloop_add_fd(crm_ipc_name(ipc), priority, crm_ipc_get_fd(ipc),
885 userdata, NULL);
886 if (*source == NULL) {
887 int rc = errno;
888
889 crm_ipc_close(ipc);
890 return rc;
891 }
892 (*source)->ipc = ipc;
893 (*source)->destroy_fn = callbacks->destroy;
894 (*source)->dispatch_fn_ipc = callbacks->dispatch;
895 return pcmk_rc_ok;
896}
897
905guint
907{
908 if (timer) {
909 return timer->period_ms;
910 }
911 return 0;
912}
913
915mainloop_add_ipc_client(const char *name, int priority, size_t max_size,
916 void *userdata, struct ipc_client_callbacks *callbacks)
917{
918 crm_ipc_t *ipc = crm_ipc_new(name, max_size);
919 mainloop_io_t *source = NULL;
920 int rc = pcmk__add_mainloop_ipc(ipc, priority, userdata, callbacks,
921 &source);
922
923 if (rc != pcmk_rc_ok) {
924 if (crm_log_level == LOG_STDOUT) {
925 fprintf(stderr, "Connection to %s failed: %s",
926 name, pcmk_rc_str(rc));
927 }
928 crm_ipc_destroy(ipc);
929 if (rc > 0) {
930 errno = rc;
931 } else {
932 errno = ENOTCONN;
933 }
934 return NULL;
935 }
936 return source;
937}
938
939void
944
945crm_ipc_t *
947{
948 if (client) {
949 return client->ipc;
950 }
951 return NULL;
952}
953
955mainloop_add_fd(const char *name, int priority, int fd, void *userdata,
956 struct mainloop_fd_callbacks * callbacks)
957{
958 mainloop_io_t *client = NULL;
959
960 if (fd >= 0) {
961 client = calloc(1, sizeof(mainloop_io_t));
962 if (client == NULL) {
963 return NULL;
964 }
965 client->name = strdup(name);
966 client->userdata = userdata;
967
968 if (callbacks) {
969 client->destroy_fn = callbacks->destroy;
970 client->dispatch_fn_io = callbacks->dispatch;
971 }
972
973 client->fd = fd;
974 client->channel = g_io_channel_unix_new(fd);
975 client->source =
976 g_io_add_watch_full(client->channel, priority,
977 (G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR), mainloop_gio_callback,
978 client, mainloop_gio_destroy);
979
980 /* Now that mainloop now holds a reference to channel,
981 * thanks to g_io_add_watch_full(), drop ours from g_io_channel_unix_new().
982 *
983 * This means that channel will be free'd by:
984 * g_main_context_dispatch() or g_source_remove()
985 * -> g_source_destroy_internal()
986 * -> g_source_callback_unref()
987 * shortly after mainloop_gio_destroy() completes
988 */
989 g_io_channel_unref(client->channel);
990 crm_trace("Added connection %d for %s[%p].%d", client->source, client->name, client, fd);
991 } else {
992 errno = EINVAL;
993 }
994
995 return client;
996}
997
998void
1000{
1001 if (client != NULL) {
1002 crm_trace("Removing client %s[%p]", client->name, client);
1003 if (client->source) {
1004 /* Results in mainloop_gio_destroy() being called just
1005 * before the source is removed from mainloop
1006 */
1007 g_source_remove(client->source);
1008 }
1009 }
1010}
1011
1012static GList *child_list = NULL;
1013
1014pid_t
1016{
1017 return child->pid;
1018}
1019
1020const char *
1022{
1023 return child->desc;
1024}
1025
1026int
1028{
1029 return child->timeout;
1030}
1031
1032void *
1034{
1035 return child->privatedata;
1036}
1037
1038void
1040{
1041 child->privatedata = NULL;
1042}
1043
1044/* good function name */
1045static void
1046child_free(mainloop_child_t *child)
1047{
1048 if (child->timerid != 0) {
1049 crm_trace("Removing timer %d", child->timerid);
1050 g_source_remove(child->timerid);
1051 child->timerid = 0;
1052 }
1053 free(child->desc);
1054 free(child);
1055}
1056
1057/* terrible function name */
1058static int
1059child_kill_helper(mainloop_child_t *child)
1060{
1061 int rc;
1062 if (child->flags & mainloop_leave_pid_group) {
1063 crm_debug("Kill pid %d only. leave group intact.", child->pid);
1064 rc = kill(child->pid, SIGKILL);
1065 } else {
1066 crm_debug("Kill pid %d's group", child->pid);
1067 rc = kill(-child->pid, SIGKILL);
1068 }
1069
1070 if (rc < 0) {
1071 if (errno != ESRCH) {
1072 crm_perror(LOG_ERR, "kill(%d, KILL) failed", child->pid);
1073 }
1074 return -errno;
1075 }
1076 return 0;
1077}
1078
1079static gboolean
1080child_timeout_callback(gpointer p)
1081{
1082 mainloop_child_t *child = p;
1083 int rc = 0;
1084
1085 child->timerid = 0;
1086 if (child->timeout) {
1087 crm_warn("%s process (PID %d) will not die!", child->desc, (int)child->pid);
1088 return FALSE;
1089 }
1090
1091 rc = child_kill_helper(child);
1092 if (rc == -ESRCH) {
1093 /* Nothing left to do. pid doesn't exist */
1094 return FALSE;
1095 }
1096
1097 child->timeout = TRUE;
1098 crm_debug("%s process (PID %d) timed out", child->desc, (int)child->pid);
1099
1100 child->timerid = g_timeout_add(5000, child_timeout_callback, child);
1101 return FALSE;
1102}
1103
1104static bool
1105child_waitpid(mainloop_child_t *child, int flags)
1106{
1107 int rc = 0;
1108 int core = 0;
1109 int signo = 0;
1110 int status = 0;
1111 int exitcode = 0;
1112 bool callback_needed = true;
1113
1114 rc = waitpid(child->pid, &status, flags);
1115 if (rc == 0) { // WNOHANG in flags, and child status is not available
1116 crm_trace("Child process %d (%s) still active",
1117 child->pid, child->desc);
1118 callback_needed = false;
1119
1120 } else if (rc != child->pid) {
1121 /* According to POSIX, possible conditions:
1122 * - child->pid was non-positive (process group or any child),
1123 * and rc is specific child
1124 * - errno ECHILD (pid does not exist or is not child)
1125 * - errno EINVAL (invalid flags)
1126 * - errno EINTR (caller interrupted by signal)
1127 *
1128 * @TODO Handle these cases more specifically.
1129 */
1130 signo = SIGCHLD;
1131 exitcode = 1;
1132 crm_notice("Wait for child process %d (%s) interrupted: %s",
1133 child->pid, child->desc, pcmk_rc_str(errno));
1134
1135 } else if (WIFEXITED(status)) {
1136 exitcode = WEXITSTATUS(status);
1137 crm_trace("Child process %d (%s) exited with status %d",
1138 child->pid, child->desc, exitcode);
1139
1140 } else if (WIFSIGNALED(status)) {
1141 signo = WTERMSIG(status);
1142 crm_trace("Child process %d (%s) exited with signal %d (%s)",
1143 child->pid, child->desc, signo, strsignal(signo));
1144
1145#ifdef WCOREDUMP // AIX, SunOS, maybe others
1146 } else if (WCOREDUMP(status)) {
1147 core = 1;
1148 crm_err("Child process %d (%s) dumped core",
1149 child->pid, child->desc);
1150#endif
1151
1152 } else { // flags must contain WUNTRACED and/or WCONTINUED to reach this
1153 crm_trace("Child process %d (%s) stopped or continued",
1154 child->pid, child->desc);
1155 callback_needed = false;
1156 }
1157
1158 if (callback_needed && child->callback) {
1159 child->callback(child, child->pid, core, signo, exitcode);
1160 }
1161 return callback_needed;
1162}
1163
1164static void
1165child_death_dispatch(int signal)
1166{
1167 for (GList *iter = child_list; iter; ) {
1168 GList *saved = iter;
1169 mainloop_child_t *child = iter->data;
1170
1171 iter = iter->next;
1172 if (child_waitpid(child, WNOHANG)) {
1173 crm_trace("Removing completed process %d from child list",
1174 child->pid);
1175 child_list = g_list_remove_link(child_list, saved);
1176 g_list_free(saved);
1177 child_free(child);
1178 }
1179 }
1180}
1181
1182static gboolean
1183child_signal_init(gpointer p)
1184{
1185 crm_trace("Installed SIGCHLD handler");
1186 /* Do NOT use g_child_watch_add() and friends, they rely on pthreads */
1187 mainloop_add_signal(SIGCHLD, child_death_dispatch);
1188
1189 /* In case they terminated before the signal handler was installed */
1190 child_death_dispatch(SIGCHLD);
1191 return FALSE;
1192}
1193
1194gboolean
1196{
1197 GList *iter;
1198 mainloop_child_t *child = NULL;
1199 mainloop_child_t *match = NULL;
1200 /* It is impossible to block SIGKILL, this allows us to
1201 * call waitpid without WNOHANG flag.*/
1202 int waitflags = 0, rc = 0;
1203
1204 for (iter = child_list; iter != NULL && match == NULL; iter = iter->next) {
1205 child = iter->data;
1206 if (pid == child->pid) {
1207 match = child;
1208 }
1209 }
1210
1211 if (match == NULL) {
1212 return FALSE;
1213 }
1214
1215 rc = child_kill_helper(match);
1216 if(rc == -ESRCH) {
1217 /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get
1218 * SIGCHLD and let handler clean it up as normal (so we get the correct
1219 * return code/status). The blocking alternative would be to call
1220 * child_waitpid(match, 0).
1221 */
1222 crm_trace("Waiting for signal that child process %d completed",
1223 match->pid);
1224 return TRUE;
1225
1226 } else if(rc != 0) {
1227 /* If KILL for some other reason set the WNOHANG flag since we
1228 * can't be certain what happened.
1229 */
1230 waitflags = WNOHANG;
1231 }
1232
1233 if (!child_waitpid(match, waitflags)) {
1234 /* not much we can do if this occurs */
1235 return FALSE;
1236 }
1237
1238 child_list = g_list_remove(child_list, match);
1239 child_free(match);
1240 return TRUE;
1241}
1242
1243/* Create/Log a new tracked process
1244 * To track a process group, use -pid
1245 *
1246 * @TODO Using a non-positive pid (i.e. any child, or process group) would
1247 * likely not be useful since we will free the child after the first
1248 * completed process.
1249 */
1250void
1251mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *privatedata, enum mainloop_child_flags flags,
1252 void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode))
1253{
1254 static bool need_init = TRUE;
1255 mainloop_child_t *child = calloc(1, sizeof(mainloop_child_t));
1256
1257 child->pid = pid;
1258 child->timerid = 0;
1259 child->timeout = FALSE;
1260 child->privatedata = privatedata;
1261 child->callback = callback;
1262 child->flags = flags;
1263 pcmk__str_update(&child->desc, desc);
1264
1265 if (timeout) {
1266 child->timerid = g_timeout_add(timeout, child_timeout_callback, child);
1267 }
1268
1269 child_list = g_list_append(child_list, child);
1270
1271 if(need_init) {
1272 need_init = FALSE;
1273 /* SIGCHLD processing has to be invoked from mainloop.
1274 * We do not want it to be possible to both add a child pid
1275 * to mainloop, and have the pid's exit callback invoked within
1276 * the same callstack. */
1277 g_timeout_add(1, child_signal_init, NULL);
1278 }
1279}
1280
1281void
1282mainloop_child_add(pid_t pid, int timeout, const char *desc, void *privatedata,
1283 void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode))
1284{
1285 mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, callback);
1286}
1287
1288static gboolean
1289mainloop_timer_cb(gpointer user_data)
1290{
1291 int id = 0;
1292 bool repeat = FALSE;
1293 struct mainloop_timer_s *t = user_data;
1294
1295 CRM_ASSERT(t != NULL);
1296
1297 id = t->id;
1298 t->id = 0; /* Ensure it's unset during callbacks so that
1299 * mainloop_timer_running() works as expected
1300 */
1301
1302 if(t->cb) {
1303 crm_trace("Invoking callbacks for timer %s", t->name);
1304 repeat = t->repeat;
1305 if(t->cb(t->userdata) == FALSE) {
1306 crm_trace("Timer %s complete", t->name);
1307 repeat = FALSE;
1308 }
1309 }
1310
1311 if(repeat) {
1312 /* Restore if repeating */
1313 t->id = id;
1314 }
1315
1316 return repeat;
1317}
1318
1319bool
1321{
1322 if(t && t->id != 0) {
1323 return TRUE;
1324 }
1325 return FALSE;
1326}
1327
1328void
1330{
1332 if(t && t->period_ms > 0) {
1333 crm_trace("Starting timer %s", t->name);
1334 t->id = g_timeout_add(t->period_ms, mainloop_timer_cb, t);
1335 }
1336}
1337
1338void
1340{
1341 if(t && t->id != 0) {
1342 crm_trace("Stopping timer %s", t->name);
1343 g_source_remove(t->id);
1344 t->id = 0;
1345 }
1346}
1347
1348guint
1350{
1351 guint last = 0;
1352
1353 if(t) {
1354 last = t->period_ms;
1355 t->period_ms = period_ms;
1356 }
1357
1358 if(t && t->id != 0 && last != t->period_ms) {
1360 }
1361 return last;
1362}
1363
1365mainloop_timer_add(const char *name, guint period_ms, bool repeat, GSourceFunc cb, void *userdata)
1366{
1367 mainloop_timer_t *t = calloc(1, sizeof(mainloop_timer_t));
1368
1369 if(t) {
1370 if(name) {
1371 t->name = crm_strdup_printf("%s-%u-%d", name, period_ms, repeat);
1372 } else {
1373 t->name = crm_strdup_printf("%p-%u-%d", t, period_ms, repeat);
1374 }
1375 t->id = 0;
1376 t->period_ms = period_ms;
1377 t->repeat = repeat;
1378 t->cb = cb;
1379 t->userdata = userdata;
1380 crm_trace("Created timer %s with %p %p", t->name, userdata, t->userdata);
1381 }
1382 return t;
1383}
1384
1385void
1387{
1388 if(t) {
1389 crm_trace("Destroying timer %s", t->name);
1391 free(t->name);
1392 free(t);
1393 }
1394}
1395
1396/*
1397 * Helpers to make sure certain events aren't lost at shutdown
1398 */
1399
1400static gboolean
1401drain_timeout_cb(gpointer user_data)
1402{
1403 bool *timeout_popped = (bool*) user_data;
1404
1405 *timeout_popped = TRUE;
1406 return FALSE;
1407}
1408
1415void
1416pcmk_quit_main_loop(GMainLoop *mloop, unsigned int n)
1417{
1418 if ((mloop != NULL) && g_main_loop_is_running(mloop)) {
1419 GMainContext *ctx = g_main_loop_get_context(mloop);
1420
1421 /* Drain up to n events in case some memory clean-up is pending
1422 * (helpful to reduce noise in valgrind output).
1423 */
1424 for (int i = 0; (i < n) && g_main_context_pending(ctx); ++i) {
1425 g_main_context_dispatch(ctx);
1426 }
1427 g_main_loop_quit(mloop);
1428 }
1429}
1430
1444void
1445pcmk_drain_main_loop(GMainLoop *mloop, guint timer_ms, bool (*check)(guint))
1446{
1447 bool timeout_popped = FALSE;
1448 guint timer = 0;
1449 GMainContext *ctx = NULL;
1450
1451 CRM_CHECK(mloop && check, return);
1452
1453 ctx = g_main_loop_get_context(mloop);
1454 if (ctx) {
1455 time_t start_time = time(NULL);
1456
1457 timer = g_timeout_add(timer_ms, drain_timeout_cb, &timeout_popped);
1458 while (!timeout_popped
1459 && check(timer_ms - (time(NULL) - start_time) * 1000)) {
1460 g_main_context_iteration(ctx, TRUE);
1461 }
1462 }
1463 if (!timeout_popped && (timer > 0)) {
1464 g_source_remove(timer);
1465 }
1466}
1467
1468// Deprecated functions kept only for backward API compatibility
1469// LCOV_EXCL_START
1470
1472
1473gboolean
1474crm_signal(int sig, void (*dispatch) (int sig))
1475{
1476 return crm_signal_handler(sig, dispatch) != SIG_ERR;
1477}
1478
1479// LCOV_EXCL_STOP
1480// End deprecated API
const char * name
Definition cib.c:24
uint64_t flags
Definition remote.c:3
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
enum crm_ais_msg_types type
Definition cpg.c:3
char data[0]
Definition cpg.c:10
uint32_t id
Definition cpg.c:0
uint32_t pid
Definition cpg.c:1
A dumping ground.
void crm_ipc_destroy(crm_ipc_t *client)
Definition ipc_client.c:955
const char * crm_ipc_name(crm_ipc_t *client)
long crm_ipc_read(crm_ipc_t *client)
int crm_ipc_get_fd(crm_ipc_t *client)
Definition ipc_client.c:981
unsigned int crm_ipc_default_buffer_size(void)
Return pacemaker's default IPC buffer size.
Definition ipc_common.c:62
bool crm_ipc_connected(crm_ipc_t *client)
Definition ipc_client.c:995
bool crm_ipc_connect(crm_ipc_t *client)
Establish an IPC connection to a Pacemaker component.
Definition ipc_client.c:867
void crm_ipc_close(crm_ipc_t *client)
Definition ipc_client.c:942
const char * crm_ipc_buffer(crm_ipc_t *client)
struct crm_ipc_s crm_ipc_t
Definition ipc.h:165
crm_ipc_t * crm_ipc_new(const char *name, size_t max_size)
Create a new (legacy) object for using Pacemaker daemon IPC.
Definition ipc_client.c:820
#define crm_warn(fmt, args...)
Definition logging.h:376
#define CRM_XS
Definition logging.h:55
#define LOG_STDOUT
Definition logging.h:42
#define crm_notice(fmt, args...)
Definition logging.h:377
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition logging.h:319
#define CRM_CHECK(expr, failure_action)
Definition logging.h:235
#define crm_debug(fmt, args...)
Definition logging.h:380
#define crm_err(fmt, args...)
Definition logging.h:375
unsigned int crm_log_level
Definition logging.c:45
#define crm_trace(fmt, args...)
Definition logging.h:381
void pcmk_quit_main_loop(GMainLoop *mloop, unsigned int n)
Drain some remaining main loop events then quit it.
Definition mainloop.c:1416
qb_ipcs_service_t * mainloop_add_ipc_server_with_prio(const char *name, enum qb_ipc_type type, struct qb_ipcs_service_handlers *callbacks, enum qb_loop_priority prio)
Start server-side API end-point, hooked into the internal event loop.
Definition mainloop.c:657
gboolean mainloop_add_signal(int sig, void(*dispatch)(int sig))
Definition mainloop.c:357
guint pcmk__mainloop_timer_get_period(const mainloop_timer_t *timer)
Get period for mainloop timer.
Definition mainloop.c:906
int mainloop_child_timeout(mainloop_child_t *child)
Definition mainloop.c:1027
pid_t mainloop_child_pid(mainloop_child_t *child)
Definition mainloop.c:1015
void mainloop_set_trigger(crm_trigger_t *source)
Definition mainloop.c:200
gboolean mainloop_destroy_trigger(crm_trigger_t *source)
Definition mainloop.c:208
int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source)
Connect to IPC and add it as a main loop source.
Definition mainloop.c:873
guint mainloop_timer_set_period(mainloop_timer_t *t, guint period_ms)
Definition mainloop.c:1349
void * mainloop_child_userdata(mainloop_child_t *child)
Definition mainloop.c:1033
crm_ipc_t * mainloop_get_ipc_client(mainloop_io_t *client)
Definition mainloop.c:946
bool mainloop_timer_running(mainloop_timer_t *t)
Definition mainloop.c:1320
struct qb_ipcs_poll_handlers gio_poll_funcs
Definition mainloop.c:617
qb_ipcs_service_t * mainloop_add_ipc_server(const char *name, enum qb_ipc_type type, struct qb_ipcs_service_handlers *callbacks)
Definition mainloop.c:650
mainloop_timer_t * mainloop_timer_add(const char *name, guint period_ms, bool repeat, GSourceFunc cb, void *userdata)
Definition mainloop.c:1365
sighandler_t crm_signal_handler(int sig, sighandler_t dispatch)
Definition mainloop.c:309
mainloop_io_t * mainloop_add_ipc_client(const char *name, int priority, size_t max_size, void *userdata, struct ipc_client_callbacks *callbacks)
Definition mainloop.c:915
void mainloop_trigger_complete(crm_trigger_t *trig)
Definition mainloop.c:168
void mainloop_timer_del(mainloop_timer_t *t)
Definition mainloop.c:1386
void mainloop_timer_start(mainloop_timer_t *t)
Definition mainloop.c:1329
void mainloop_del_fd(mainloop_io_t *client)
Definition mainloop.c:999
void mainloop_del_ipc_server(qb_ipcs_service_t *server)
Definition mainloop.c:693
crm_trigger_t * mainloop_add_trigger(int priority, int(*dispatch)(gpointer user_data), gpointer userdata)
Create a trigger to be used as a mainloop source.
Definition mainloop.c:187
void mainloop_cleanup(void)
Definition mainloop.c:431
void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *privatedata, void(*callback)(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode))
Definition mainloop.c:1282
gboolean crm_signal(int sig, void(*dispatch)(int sig))
Definition mainloop.c:1474
void mainloop_timer_stop(mainloop_timer_t *t)
Definition mainloop.c:1339
gboolean mainloop_child_kill(pid_t pid)
Definition mainloop.c:1195
void mainloop_del_ipc_client(mainloop_io_t *client)
Definition mainloop.c:940
void mainloop_clear_child_userdata(mainloop_child_t *child)
Definition mainloop.c:1039
struct signal_s crm_signal_t
mainloop_io_t * mainloop_add_fd(const char *name, int priority, int fd, void *userdata, struct mainloop_fd_callbacks *callbacks)
Definition mainloop.c:955
void pcmk_drain_main_loop(GMainLoop *mloop, guint timer_ms, bool(*check)(guint))
Process main loop events while a certain condition is met.
Definition mainloop.c:1445
const char * mainloop_child_name(mainloop_child_t *child)
Definition mainloop.c:1021
void mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *privatedata, enum mainloop_child_flags flags, void(*callback)(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode))
Definition mainloop.c:1251
gboolean mainloop_destroy_signal(int sig)
Definition mainloop.c:411
Wrappers for and extensions to glib mainloop.
struct mainloop_child_s mainloop_child_t
Definition mainloop.h:34
struct mainloop_timer_s mainloop_timer_t
Definition mainloop.h:35
void(* sighandler_t)(int)
Definition mainloop.h:49
struct mainloop_io_s mainloop_io_t
Definition mainloop.h:33
mainloop_child_flags
Definition mainloop.h:27
@ mainloop_leave_pid_group
Definition mainloop.h:29
struct trigger_s crm_trigger_t
Definition mainloop.h:32
Deprecated Pacemaker mainloop API.
unsigned int timeout
Definition pcmk_fence.c:32
const char * pcmk_strerror(int rc)
Definition results.c:148
#define CRM_ASSERT(expr)
Definition results.h:42
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition results.c:488
@ pcmk_rc_ok
Definition results.h:151
void pcmk__str_update(char **str, const char *value)
Definition strings.c:1193
void(* destroy)(gpointer userdata)
Destroy function for mainloop IPC connection client data.
Definition mainloop.h:91
int(* dispatch)(const char *buffer, ssize_t length, gpointer userdata)
Dispatch function for an IPC connection used as mainloop source.
Definition mainloop.h:84
int(* dispatch)(gpointer userdata)
Dispatch function for mainloop file descriptor with data ready.
Definition mainloop.h:138
void(* destroy)(gpointer userdata)
Destroy function for mainloop file descriptor client data.
Definition mainloop.h:145
Wrappers for and extensions to libxml2.