diff --git a/.gitignore b/.gitignore index cb6bb309a6..772dd7f0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,217 +1,218 @@ # Common \#* .\#* GPATH GRTAGS GTAGS TAGS Makefile Makefile.in .deps .dirstamp .libs *.pc *.pyc *.bz2 *.tar.gz *.rpm *.la *.lo *.o *~ *.gcda *.gcno # Autobuild aclocal.m4 autoconf autoheader autom4te.cache/ automake build.counter compile config.guess config.log config.status config.sub configure depcomp install-sh include/stamp-* libtool libtool.m4 ltdl.m4 libltdl ltmain.sh missing py-compile /m4/argz.m4 /m4/ltargz.m4 /m4/ltoptions.m4 /m4/ltsugar.m4 /m4/ltversion.m4 /m4/lt~obsolete.m4 test-driver ylwrap # Configure targets /cts/CTS.py /cts/CTSlab.py /cts/CTSvars.py /cts/LSBDummy /cts/OCFIPraTest.py /cts/benchmark/clubench /cts/cluster_test /cts/cts /cts/cts-cli /cts/cts-coverage /cts/cts-exec /cts/cts-fencing /cts/cts-log-watcher /cts/cts-regression /cts/cts-scheduler /cts/cts-support /cts/fence_dummy /cts/lxc_autogen.sh /cts/pacemaker-cts-dummyd /cts/pacemaker-cts-dummyd@.service /daemons/execd/pacemaker_remote /daemons/execd/pacemaker_remote.service /daemons/fenced/fence_legacy /daemons/pacemakerd/pacemaker /daemons/pacemakerd/pacemaker.combined.upstart /daemons/pacemakerd/pacemaker.service /daemons/pacemakerd/pacemaker.upstart /doc/Doxyfile /extra/logrotate/pacemaker /extra/resources/ClusterMon /extra/resources/HealthSMART /extra/resources/SysInfo /extra/resources/ifspeed /extra/resources/o2cb include/config.h include/config.h.in include/crm_config.h publican.cfg /tools/cibsecret /tools/crm_error /tools/crm_failcount /tools/crm_master /tools/crm_mon.service /tools/crm_mon.upstart /tools/crm_report /tools/crm_rule /tools/crm_standby /tools/report.collector /tools/report.common # Build targets *.7 *.7.xml *.7.html *.8 *.8.xml *.8.html /daemons/attrd/pacemaker-attrd /daemons/based/pacemaker-based /daemons/based/cibmon /daemons/controld/pacemaker-controld /daemons/execd/cts-exec-helper /daemons/execd/pacemaker-execd /daemons/execd/pacemaker-remoted /daemons/fenced/cts-fence-helper /daemons/fenced/pacemaker-fenced /daemons/fenced/pacemaker-fenced.xml /daemons/pacemakerd/pacemakerd /daemons/schedulerd/pacemaker-schedulerd /daemons/schedulerd/pacemaker-schedulerd.xml /doc/*/tmp/** /doc/*/publish /doc/*.build /doc/*/en-US/Ap-*.xml /doc/*/en-US/Ch-*.xml /doc/.ABI-build /doc/HTML /doc/abi_dumps /doc/abi-check /doc/acls.html /doc/api/* /doc/compat_reports /doc/crm_fencing.html /doc/publican-catalog* /doc/shared/en-US/*.xml /doc/shared/en-US/images/pcmk-*.png /doc/shared/en-US/images/Policy-Engine-*.png /maint/testcc +/maint/mocked/based scratch /tools/attrd_updater /tools/cibadmin /tools/crmadmin /tools/crm_attribute /tools/crm_diff /tools/crm_mon /tools/crm_node /tools/crm_resource /tools/crm_shadow /tools/crm_simulate /tools/crm_ticket /tools/crm_verify /tools/iso8601 /tools/stonith_admin xml/crm.dtd xml/pacemaker*.rng xml/versions.rng xml/api/api-result*.rng lib/gnu/libgnu.a lib/gnu/stdalign.h *.coverity # Test detritus /cts/.regression.failed.diff /cts/scheduler/*.ref /cts/scheduler/*.up /cts/scheduler/*.up.err /cts/scheduler/bug-rh-1097457.log /cts/scheduler/bug-rh-1097457.trs /cts/scheduler/shadow.* /cts/test-suite.log /xml/test-*/*.up /xml/test-*/*.up.err /xml/assets/*.rng /xml/assets/diffview.js /xml/assets/xmlcatalog # Release maintenance detritus /maint/gnulib # Formerly built files (helps when jumping back and forth in checkout) /.ABI-build /Doxyfile /HTML /abi_dumps /abi-check /compat_reports /attrd /cib /coverage.sh /crmd /cts/HBDummy /doc/Clusters_from_Scratch.txt /doc/Pacemaker_Explained.txt /fencing /lrmd /mcp /pengine #Other mock pacemaker*.spec coverity-* logs *.patch *.diff *.sed *.orig *.rej *.swp diff --git a/doc/Pacemaker_Development/en-US/Ch-Evolution.txt b/doc/Pacemaker_Development/en-US/Ch-Evolution.txt index 4c1e657729..d597ed9144 100644 --- a/doc/Pacemaker_Development/en-US/Ch-Evolution.txt +++ b/doc/Pacemaker_Development/en-US/Ch-Evolution.txt @@ -1,104 +1,105 @@ :compat-mode: legacy = Evolution of the project = anchor:ch-evolution[Chapter 5. Evolution] +[id="evolution-foreword"] == Foreword == This chapter is currently not meant as a definite summary of how Pacemaker got into where it stands now, but rather to provide few valuable pointers an entusiasts (presumably software archeologist type of person) may find useful. Moreover, well-intentioned contributors to Pacemaker project may want to review them occasionally since, as the famous quote has it, "those who do not learn history are doomed to repeat it". For anything more talkative with less emphasis on actual code, other places will serve better for the time being (and if not, should not be too hard to volunteer extensions to those writeups): * https://wiki.clusterlabs.org/wiki/Pacemaker[main entry at ClusterLabs community wiki] * https://en.wikipedia.org/wiki/Pacemaker_(software)[entry at wikipedia.org] * https://www.alteeve.com/w/AN!Cluster_Tutorial_2#What_about_Pacemaker.3F[ brief section dedicated to Pacemaker in Digimer's tutorial regarding setting up the cluster with the old Red Hat Cluster Suite like stack] == Common ancestor: 'heartbeat' project == Pacemaker can be considered as a spin-off from `heartbeat', original comprehensive HA suite started by Alan Robertson, and some portions of code are shared, at least on the conceptual level if not verbatim, till today, even if the effective percentage continually declines. Note that till Pacemaker 2.0, it also used to stand for one (and initially the only) of supported messaging back-ends (removal of this support made for one such notable drop of reused code), see also https://github.com/ClusterLabs/pacemaker/commit/55ab749bf0f0143bd1cd050c1bbe302aecb3898e[ pre-2.0 commit 55ab749bf]. The codebase for heartbeat used to be hosted at http://hg.linux-ha.org, but since that does not appear reliably available recently, an archive checkout from 2016 is shared at https://gitlab.com/poki/archived-heartbeat[ as a dedicated read-only repository], and anchored there, the most notable commits are: * https://gitlab.com/poki/archived-heartbeat/commit/bb48551be418291c46980511aa31c7c2df3a85e4[ initial check-in of what turned up to be the basis for Pacemaker later on] * https://gitlab.com/poki/archived-heartbeat/commit/74573ac6182785820d765ec76c5d70086381931a[ drop of now-detached Pacemaker code] Regarding the Pacemaker's split from heartbeat, it evolved stepwise (as opposed to one-off cut), and the last step of full dependency is depicted in https://www.kernel.org/doc/ols/2008/ols2008v1-pages-85-100.pdf#page=14[ The Corosync Cluster Engine] paper, fig. 10. This article also provides a good reference regarding wider historical context of the tangentially (and deeper in some cases) meeting components around that time. === Influence of 'heartbeat' on Pacemaker === On a closer look, we can identify these things in common: * extensive use of data types and functions of https://wiki.gnome.org/Projects/GLib[GLib] * Cluster Testing System (CTS) is inherited from initial implementation by Alan Robertson * ... == Notable Restructuring Steps in the Codebase == File renames may not appear as notable ... unless one runs into complicated +git blame+ and +git log+ scenarios, so some more massive ones may be stated as well. * watchdog/'sbd' functionality spin-off: ** https://github.com/ClusterLabs/pacemaker/commit/eb7cce2a172a026336f4ba6c441dedce42f41092[ start separating, eb7cce2a1] ** https://github.com/ClusterLabs/pacemaker/commit/5884db78080941cdc4e77499bc76677676729484[ finish separating, 5884db780] * daemons' rename for 2.0 (in chronological order) ** https://github.com/ClusterLabs/pacemaker/commit/318a2e003d2369caf10a450fe7a7616eb7ffb264[ start of moving daemon sources from their top-level directories under new +/daemons+ hierarchy, 318a2e003] ** https://github.com/ClusterLabs/pacemaker/commit/01563cf2637040e9d725b777f0c42efa8ab075c7[ +attrd+ -> +pacemaker-attrd+, 01563cf26] ** https://github.com/ClusterLabs/pacemaker/commit/36a00e2376fd50d52c2ccc49483e235a974b161c[ +lrmd+ -> +pacemaker-execd+, 36a00e237] ** https://github.com/ClusterLabs/pacemaker/commit/e4f4a0d64c8b6bbc4961810f2a41383f52eaa116[ +pacemaker_remoted+ -> +pacemaker-remoted+, e4f4a0d64] ** https://github.com/ClusterLabs/pacemaker/commit/db5536e40c77cdfdf1011b837f18e4ad9df45442[ +crmd+ -> +pacemaker-controld+, db5536e40] ** https://github.com/ClusterLabs/pacemaker/commit/e2fdc2baccc3ae07652aac622a83f317597608cd[ +pengine+ -> +pacemaker-schedulerd+, e2fdc2bac] ** https://github.com/ClusterLabs/pacemaker/commit/038c465e2380c5349fb30ea96c8a7eb6184452e0[ +stonithd+ -> +pacemaker-fenced+, 038c465e2] ** https://github.com/ClusterLabs/pacemaker/commit/50584c234e48cd8b99d355ca9349b0dfb9503987[ +cib daemon+ -> +pacemaker-based+, 50584c234] //// TBD: - standalone tengine -> part of crmd/pacemaker-controld //// diff --git a/doc/Pacemaker_Development/en-US/Ch-Hacking.txt b/doc/Pacemaker_Development/en-US/Ch-Hacking.txt new file mode 100644 index 0000000000..f26cf6284f --- /dev/null +++ b/doc/Pacemaker_Development/en-US/Ch-Hacking.txt @@ -0,0 +1,68 @@ +:compat-mode: legacy += Advanced Hacking on the Project = + +anchor:ch-hacking[Chapter 6. Hacking on Pacemaker] + +[id="hacking-foreword"] +== Foreword == + +This chapter aims to be a gentle introduction (or perhaps, rather +a summarization of advanced techniques we developed for backreferences) +to how deal with the Pacemaker internals effectively. +for instance, how to: + +* debug with an ease +* verify various interesting interaction-based properties + +or simply put, all that is in the interest of the core contributors +on the project to know, master, and (preferably) also evolve +-- way beyond what is in the presumed repertoire of a generic +contributor role, which is detailed in other chapters of this guide. + +Therefore, if you think you will not benefit from any such details +in the scope of this chapter, feel free to skip it. + +== Debugging == + +In the GNU userland tradition, preferred way of debugging is based +on 'gdb' (directly or via specific frontends atop) that is widely +available on platforms (semi)supported with Pacemaker itself. + +To make some advanced debugging easier, we maintain a script defining +some useful helpers in `extra/gdb/gdbhelpers` file, which you can make +available in the debugging session easily when invoking it as +`gdb -x ...`. + +From within the debugger, you can then invoke the new `pcmk` command +that will guide you regarding other helper functions available, so +we won't replicate that here. + +== Working with mocked daemons == + +Since the Pacemaker run-time consists of multiple co-operating daemons +as detailed elsewhere, tracking down the interaction details amongst +them can be rather cumbersome. Since rebuilding existing daemons in +a more modular way as opposed to clusters of mutually dependent +functions, we elected to grow separate bare-bones counterparts built +evolutionary as skeletons just to get the basic (long-term stabilized) +communication with typical daemon clients going, and to add new modules +in their outer circles (plus minimalistic hook support at those cores) +on a demand-driven basis. + +The code for these is located at `maint/mocked`; for instance, +`based-notifyfenced.c` module of `based.c` skeleton mocking +`pacemaker-based` daemon was exactly to fulfill investigation helper +role (the case at hand was also an impulse to kick off this very +sort of maintenance support material, to begin with). + +Non-trivial knowledge of Pacemaker internals and other skills are +needed to use such devised helpers, but given the other way around, +some sorts of investigation may be even heftier, it may be the least +effort choice. And when that's the case, advanced contributors are +expected to contribute their own extensions they used to validate +the reproducibility/actual correctness of the fix along the actual +code modifications. This way, the rest of the development teams is +not required to deal with elaborate preconditions, be at guess, or +even forced to use a blind faith regarding the causes, consequences +and validity regarding the raised issues/fixes, for the greater +benefit of all. diff --git a/doc/Pacemaker_Development/en-US/Pacemaker_Development.xml b/doc/Pacemaker_Development/en-US/Pacemaker_Development.xml index c02d705c90..888696dc20 100644 --- a/doc/Pacemaker_Development/en-US/Pacemaker_Development.xml +++ b/doc/Pacemaker_Development/en-US/Pacemaker_Development.xml @@ -1,15 +1,16 @@ %BOOK_ENTITIES; ]> - - - - - - - + + + + + + + + diff --git a/doc/Pacemaker_Development/en-US/Revision_History.xml b/doc/Pacemaker_Development/en-US/Revision_History.xml index b9c1dea6ae..32b37341ce 100644 --- a/doc/Pacemaker_Development/en-US/Revision_History.xml +++ b/doc/Pacemaker_Development/en-US/Revision_History.xml @@ -1,95 +1,108 @@ %BOOK_ENTITIES; ]> Revision History 1-0 Tue Jul 26 2016 KenGaillot kgaillot@redhat.com Convert coding guidelines and developer FAQ to Publican document 1-1 Mon Aug 29 2016 KenGaillot kgaillot@redhat.com Add Python coding guidelines, and more about licensing 2-0 Fri Jan 12 2018 KenGaillot kgaillot@redhat.com Drop support for Python 2.6 2-1 Tue Sep 18 2018 JanPokorný poki@redhat.com Start documenting notable evolutionary points 2-2 Fri Dec 7 2018 KenGaillot kgaillot@redhat.com Update FAQ and C guidelines 2-3 Mon May 13 2019 KenGaillot kgaillot@redhat.com JanPokorný poki@redhat.com Update copyright notice policy, and some external references + + 2-4 + Fri 17 May 2019 + + JanPokorný + poki@redhat.com + + + Start capturing hacking howto + for advanced contributors + + + diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 85da1cd661..2cfb63e84f 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -1,135 +1,159 @@ /* * Copyright 2009-2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM_COMMON_MAINLOOP__H # define CRM_COMMON_MAINLOOP__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Wrappers for and extensions to glib mainloop * \ingroup core */ # include // sighandler_t # include enum mainloop_child_flags { /* don't kill pid group on timeout, only kill the pid */ mainloop_leave_pid_group = 0x01, }; typedef struct trigger_s crm_trigger_t; typedef struct mainloop_io_s mainloop_io_t; typedef struct mainloop_child_s mainloop_child_t; typedef struct mainloop_timer_s mainloop_timer_t; void mainloop_cleanup(void); crm_trigger_t *mainloop_add_trigger(int priority, int (*dispatch) (gpointer user_data), gpointer userdata); void mainloop_set_trigger(crm_trigger_t * source); void mainloop_trigger_complete(crm_trigger_t * trig); gboolean mainloop_destroy_trigger(crm_trigger_t * source); # ifndef HAVE_SIGHANDLER_T typedef void (*sighandler_t)(int); # endif sighandler_t crm_signal_handler(int sig, sighandler_t dispatch); gboolean crm_signal(int sig, void (*dispatch) (int sig)); // deprecated gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)); gboolean mainloop_destroy_signal(int sig); bool mainloop_timer_running(mainloop_timer_t *t); void mainloop_timer_start(mainloop_timer_t *t); void mainloop_timer_stop(mainloop_timer_t *t); guint mainloop_timer_set_period(mainloop_timer_t *t, guint period_ms); mainloop_timer_t *mainloop_timer_add(const char *name, guint period_ms, bool repeat, GSourceFunc cb, void *userdata); void mainloop_timer_del(mainloop_timer_t *t); # include # include struct ipc_client_callbacks { int (*dispatch) (const char *buffer, ssize_t length, gpointer userdata); void (*destroy) (gpointer); }; qb_ipcs_service_t *mainloop_add_ipc_server(const char *name, enum qb_ipc_type type, struct qb_ipcs_service_handlers *callbacks); +/*! + * \brief Start server-side API end-point, hooked into the internal event loop + * + * \param[in] name name of the IPC end-point ("address" for the client) + * \param[in] type selects libqb's IPC back-end (or use #QB_IPC_NATIVE) + * \param[in] callbacks defines libqb's IPC service-level handlers + * \param[in] priority priority relative to other events handled in the + * abstract handling loop, use #QB_LOOP_MED when unsure + * + * \return libqb's opaque handle to the created service abstraction + * + * \note For portability concerns, do not use this function if you keep + * \p priority as #QB_LOOP_MED, stick with #mainloop_add_ipc_server + * (with exactly such semantics) instead (once you link with this new + * symbol employed, you can't downgrade the library freely anymore). + * + * \note The intended effect will only get fully reflected when run-time + * linked to patched libqb: https://github.com/ClusterLabs/libqb/pull/352 + */ +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); + void mainloop_del_ipc_server(qb_ipcs_service_t * server); mainloop_io_t *mainloop_add_ipc_client(const char *name, int priority, size_t max_size, void *userdata, struct ipc_client_callbacks *callbacks); void mainloop_del_ipc_client(mainloop_io_t * client); crm_ipc_t *mainloop_get_ipc_client(mainloop_io_t * client); struct mainloop_fd_callbacks { int (*dispatch) (gpointer userdata); void (*destroy) (gpointer userdata); }; mainloop_io_t *mainloop_add_fd(const char *name, int priority, int fd, void *userdata, struct mainloop_fd_callbacks *callbacks); void mainloop_del_fd(mainloop_io_t * client); /* * Create a new tracked process * To track a process group, use -pid */ void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *userdata, void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)); void mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *userdata, enum mainloop_child_flags, void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)); void *mainloop_child_userdata(mainloop_child_t * child); int mainloop_child_timeout(mainloop_child_t * child); const char *mainloop_child_name(mainloop_child_t * child); pid_t mainloop_child_pid(mainloop_child_t * child); void mainloop_clear_child_userdata(mainloop_child_t * child); gboolean mainloop_child_kill(pid_t pid); void pcmk_drain_main_loop(GMainLoop *mloop, guint timer_ms, bool (*check)(guint)); # define G_PRIORITY_MEDIUM (G_PRIORITY_HIGH/2) #ifdef __cplusplus } #endif #endif diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 18f7014af1..17e69f0a87 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1,1351 +1,1427 @@ /* * Copyright 2004-2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include struct mainloop_child_s { pid_t pid; char *desc; unsigned timerid; gboolean timeout; void *privatedata; enum mainloop_child_flags flags; /* Called when a process dies */ void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode); }; struct trigger_s { GSource source; gboolean running; gboolean trigger; void *user_data; guint id; }; static gboolean crm_trigger_prepare(GSource * source, gint * timeout) { crm_trigger_t *trig = (crm_trigger_t *) source; /* cluster-glue's FD and IPC related sources make use of * g_source_add_poll() but do not set a timeout in their prepare * functions * * This means mainloop's poll() will block until an event for one * of these sources occurs - any /other/ type of source, such as * this one or g_idle_*, that doesn't use g_source_add_poll() is * S-O-L and won't be processed until there is something fd-based * happens. * * Luckily the timeout we can set here affects all sources and * puts an upper limit on how long poll() can take. * * So unconditionally set a small-ish timeout, not too small that * we're in constant motion, which will act as an upper bound on * how long the signal handling might be delayed for. */ *timeout = 500; /* Timeout in ms */ return trig->trigger; } static gboolean crm_trigger_check(GSource * source) { crm_trigger_t *trig = (crm_trigger_t *) source; return trig->trigger; } static gboolean crm_trigger_dispatch(GSource * source, GSourceFunc callback, gpointer userdata) { int rc = TRUE; crm_trigger_t *trig = (crm_trigger_t *) source; if (trig->running) { /* Wait until the existing job is complete before starting the next one */ return TRUE; } trig->trigger = FALSE; if (callback) { rc = callback(trig->user_data); if (rc < 0) { crm_trace("Trigger handler %p not yet complete", trig); trig->running = TRUE; rc = TRUE; } } return rc; } static void crm_trigger_finalize(GSource * source) { crm_trace("Trigger %p destroyed", source); } #if 0 struct _GSourceCopy { gpointer callback_data; GSourceCallbackFuncs *callback_funcs; const GSourceFuncs *source_funcs; guint ref_count; GMainContext *context; gint priority; guint flags; guint source_id; GSList *poll_fds; GSource *prev; GSource *next; char *name; void *priv; }; static int g_source_refcount(GSource * source) { /* Duplicating the contents of private header files is a necessary evil */ if (source) { struct _GSourceCopy *evil = (struct _GSourceCopy*)source; return evil->ref_count; } return 0; } #else static int g_source_refcount(GSource * source) { return 0; } #endif static GSourceFuncs crm_trigger_funcs = { crm_trigger_prepare, crm_trigger_check, crm_trigger_dispatch, crm_trigger_finalize, }; static crm_trigger_t * mainloop_setup_trigger(GSource * source, int priority, int (*dispatch) (gpointer user_data), gpointer userdata) { crm_trigger_t *trigger = NULL; trigger = (crm_trigger_t *) source; trigger->id = 0; trigger->trigger = FALSE; trigger->user_data = userdata; if (dispatch) { g_source_set_callback(source, dispatch, trigger, NULL); } g_source_set_priority(source, priority); g_source_set_can_recurse(source, FALSE); crm_trace("Setup %p with ref-count=%u", source, g_source_refcount(source)); trigger->id = g_source_attach(source, NULL); crm_trace("Attached %p with ref-count=%u", source, g_source_refcount(source)); return trigger; } void mainloop_trigger_complete(crm_trigger_t * trig) { crm_trace("Trigger handler %p complete", trig); trig->running = FALSE; } /* If dispatch returns: * -1: Job running but not complete * 0: Remove the trigger from mainloop * 1: Leave the trigger in mainloop */ crm_trigger_t * mainloop_add_trigger(int priority, int (*dispatch) (gpointer user_data), gpointer userdata) { GSource *source = NULL; CRM_ASSERT(sizeof(crm_trigger_t) > sizeof(GSource)); source = g_source_new(&crm_trigger_funcs, sizeof(crm_trigger_t)); CRM_ASSERT(source != NULL); return mainloop_setup_trigger(source, priority, dispatch, userdata); } void mainloop_set_trigger(crm_trigger_t * source) { if(source) { source->trigger = TRUE; } } gboolean mainloop_destroy_trigger(crm_trigger_t * source) { GSource *gs = NULL; if(source == NULL) { return TRUE; } gs = (GSource *)source; if(g_source_refcount(gs) > 2) { crm_info("Trigger %p is still referenced %u times", gs, g_source_refcount(gs)); } g_source_destroy(gs); /* Remove from mainloop, ref_count-- */ g_source_unref(gs); /* The caller no longer carries a reference to source * * At this point the source should be free'd, * unless we're currently processing said * source, in which case mainloop holds an * additional reference and it will be free'd * once our processing completes */ return TRUE; } // Define a custom glib source for signal handling // Data structure for custom glib source typedef struct signal_s { crm_trigger_t trigger; // trigger that invoked source (must be first) void (*handler) (int sig); // signal handler int signal; // signal that was received } crm_signal_t; // Table to associate signal handlers with signal numbers static crm_signal_t *crm_signals[NSIG]; /*! * \internal * \brief Dispatch an event from custom glib source for signals * * Given an signal event, clear the event trigger and call any registered * signal handler. * * \param[in] source glib source that triggered this dispatch * \param[in] callback (ignored) * \param[in] userdata (ignored) */ static gboolean crm_signal_dispatch(GSource * source, GSourceFunc callback, gpointer userdata) { crm_signal_t *sig = (crm_signal_t *) source; if(sig->signal != SIGCHLD) { crm_notice("Caught '%s' signal "CRM_XS" %d (%s handler)", strsignal(sig->signal), sig->signal, (sig->handler? "invoking" : "no")); } sig->trigger.trigger = FALSE; if (sig->handler) { sig->handler(sig->signal); } return TRUE; } /*! * \internal * \brief Handle a signal by setting a trigger for signal source * * \param[in] sig Signal number that was received * * \note This is the true signal handler for the mainloop signal source, and * must be async-safe. */ static void mainloop_signal_handler(int sig) { if (sig > 0 && sig < NSIG && crm_signals[sig] != NULL) { mainloop_set_trigger((crm_trigger_t *) crm_signals[sig]); } } // Functions implementing our custom glib source for signal handling static GSourceFuncs crm_signal_funcs = { crm_trigger_prepare, crm_trigger_check, crm_signal_dispatch, crm_trigger_finalize, }; /*! * \internal * \brief Set a true signal handler * * signal()-like interface to sigaction() * * \param[in] sig Signal number to register handler for * \param[in] dispatch Signal handler * * \return The previous value of the signal handler, or SIG_ERR on error * \note The dispatch function must be async-safe. */ sighandler_t crm_signal_handler(int sig, sighandler_t dispatch) { sigset_t mask; struct sigaction sa; struct sigaction old; if (sigemptyset(&mask) < 0) { crm_err("Could not set handler for signal %d: %s", sig, pcmk_strerror(errno)); return SIG_ERR; } memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = dispatch; sa.sa_flags = SA_RESTART; sa.sa_mask = mask; if (sigaction(sig, &sa, &old) < 0) { crm_err("Could not set handler for signal %d: %s", sig, pcmk_strerror(errno)); return SIG_ERR; } return old.sa_handler; } /* * \brief Use crm_signal_handler() instead * \deprecated */ gboolean crm_signal(int sig, void (*dispatch) (int sig)) { return crm_signal_handler(sig, dispatch) != SIG_ERR; } static void mainloop_destroy_signal_entry(int sig) { crm_signal_t *tmp = crm_signals[sig]; crm_signals[sig] = NULL; crm_trace("Destroying signal %d", sig); mainloop_destroy_trigger((crm_trigger_t *) tmp); } /*! * \internal * \brief Add a signal handler to a mainloop * * \param[in] sig Signal number to handle * \param[in] dispatch Signal handler function * * \note The true signal handler merely sets a mainloop trigger to call this * dispatch function via the mainloop. Therefore, the dispatch function * does not need to be async-safe. */ gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)) { GSource *source = NULL; int priority = G_PRIORITY_HIGH - 1; if (sig == SIGTERM) { /* TERM is higher priority than other signals, * signals are higher priority than other ipc. * Yes, minus: smaller is "higher" */ priority--; } if (sig >= NSIG || sig < 0) { crm_err("Signal %d is out of range", sig); return FALSE; } else if (crm_signals[sig] != NULL && crm_signals[sig]->handler == dispatch) { crm_trace("Signal handler for %d is already installed", sig); return TRUE; } else if (crm_signals[sig] != NULL) { crm_err("Different signal handler for %d is already installed", sig); return FALSE; } CRM_ASSERT(sizeof(crm_signal_t) > sizeof(GSource)); source = g_source_new(&crm_signal_funcs, sizeof(crm_signal_t)); crm_signals[sig] = (crm_signal_t *) mainloop_setup_trigger(source, priority, NULL, NULL); CRM_ASSERT(crm_signals[sig] != NULL); crm_signals[sig]->handler = dispatch; crm_signals[sig]->signal = sig; if (crm_signal_handler(sig, mainloop_signal_handler) == SIG_ERR) { mainloop_destroy_signal_entry(sig); return FALSE; } #if 0 /* If we want signals to interrupt mainloop's poll(), instead of waiting for * the timeout, then we should call siginterrupt() below * * For now, just enforce a low timeout */ if (siginterrupt(sig, 1) < 0) { crm_perror(LOG_INFO, "Could not enable system call interruptions for signal %d", sig); } #endif return TRUE; } gboolean mainloop_destroy_signal(int sig) { if (sig >= NSIG || sig < 0) { crm_err("Signal %d is out of range", sig); return FALSE; } else if (crm_signal_handler(sig, NULL) == SIG_ERR) { crm_perror(LOG_ERR, "Could not uninstall signal handler for signal %d", sig); return FALSE; } else if (crm_signals[sig] == NULL) { return TRUE; } mainloop_destroy_signal_entry(sig); return TRUE; } static qb_array_t *gio_map = NULL; void mainloop_cleanup(void) { if (gio_map) { qb_array_free(gio_map); } for (int sig = 0; sig < NSIG; ++sig) { mainloop_destroy_signal_entry(sig); } } /* * libqb... */ struct gio_to_qb_poll { int32_t is_used; guint source; int32_t events; void *data; qb_ipcs_dispatch_fn_t fn; enum qb_loop_priority p; }; static gboolean gio_read_socket(GIOChannel * gio, GIOCondition condition, gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; gint fd = g_io_channel_unix_get_fd(gio); crm_trace("%p.%d %d", data, fd, condition); /* if this assert get's hit, then there is a race condition between * when we destroy a fd and when mainloop actually gives it up */ CRM_ASSERT(adaptor->is_used > 0); return (adaptor->fn(fd, condition, adaptor->data) == 0); } static void gio_poll_destroy(gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; adaptor->is_used--; CRM_ASSERT(adaptor->is_used >= 0); if (adaptor->is_used == 0) { crm_trace("Marking adaptor %p unused", adaptor); adaptor->source = 0; } } +/*! + * \internal + * \brief Convert libqb's poll priority into GLib's one + * + * \param[in] prio libqb's poll priority (#QB_LOOP_MED assumed as fallback) + * + * \return best matching GLib's priority + */ +static gint +conv_prio_libqb2glib(enum qb_loop_priority prio) +{ + gint ret = G_PRIORITY_DEFAULT; + switch (prio) { + case QB_LOOP_LOW: + ret = G_PRIORITY_LOW; + break; + case QB_LOOP_HIGH: + ret = G_PRIORITY_HIGH; + break; + default: + crm_trace("Invalid libqb's loop priority %d, assuming QB_LOOP_MED", + prio); + /* fall-through */ + case QB_LOOP_MED: + break; + } + return ret; +} + +/*! + * \internal + * \brief Convert libqb's poll priority to rate limiting spec + * + * \param[in] prio libqb's poll priority (#QB_LOOP_MED assumed as fallback) + * + * \return best matching rate limiting spec + */ +static enum qb_ipcs_rate_limit +conv_libqb_prio2ratelimit(enum qb_loop_priority prio) +{ + /* this is an inversion of what libqb's qb_ipcs_request_rate_limit does */ + enum qb_ipcs_rate_limit ret = QB_IPCS_RATE_NORMAL; + switch (prio) { + case QB_LOOP_LOW: + ret = QB_IPCS_RATE_SLOW; + break; + case QB_LOOP_HIGH: + ret = QB_IPCS_RATE_FAST; + break; + default: + crm_trace("Invalid libqb's loop priority %d, assuming QB_LOOP_MED", + prio); + /* fall-through */ + case QB_LOOP_MED: + break; + } + return ret; +} + static int32_t gio_poll_dispatch_update(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn, int32_t add) { struct gio_to_qb_poll *adaptor; GIOChannel *channel; int32_t res = 0; res = qb_array_index(gio_map, fd, (void **)&adaptor); if (res < 0) { crm_err("Array lookup failed for fd=%d: %d", fd, res); return res; } crm_trace("Adding fd=%d to mainloop as adaptor %p", fd, adaptor); if (add && adaptor->source) { crm_err("Adaptor for descriptor %d is still in-use", fd); return -EEXIST; } if (!add && !adaptor->is_used) { crm_err("Adaptor for descriptor %d is not in-use", fd); return -ENOENT; } /* channel is created with ref_count = 1 */ channel = g_io_channel_unix_new(fd); if (!channel) { crm_err("No memory left to add fd=%d", fd); return -ENOMEM; } if (adaptor->source) { g_source_remove(adaptor->source); adaptor->source = 0; } /* Because unlike the poll() API, glib doesn't tell us about HUPs by default */ evts |= (G_IO_HUP | G_IO_NVAL | G_IO_ERR); adaptor->fn = fn; adaptor->events = evts; adaptor->data = data; adaptor->p = p; adaptor->is_used++; adaptor->source = - g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, evts, gio_read_socket, adaptor, - gio_poll_destroy); + g_io_add_watch_full(channel, conv_prio_libqb2glib(p), evts, + gio_read_socket, adaptor, gio_poll_destroy); /* Now that mainloop now holds a reference to channel, * thanks to g_io_add_watch_full(), drop ours from g_io_channel_unix_new(). * * This means that channel will be free'd by: * g_main_context_dispatch() * -> g_source_destroy_internal() * -> g_source_callback_unref() * shortly after gio_poll_destroy() completes */ g_io_channel_unref(channel); crm_trace("Added to mainloop with gsource id=%d", adaptor->source); if (adaptor->source > 0) { return 0; } return -EINVAL; } static int32_t gio_poll_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return gio_poll_dispatch_update(p, fd, evts, data, fn, QB_TRUE); } static int32_t gio_poll_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return gio_poll_dispatch_update(p, fd, evts, data, fn, QB_FALSE); } static int32_t gio_poll_dispatch_del(int32_t fd) { struct gio_to_qb_poll *adaptor; crm_trace("Looking for fd=%d", fd); if (qb_array_index(gio_map, fd, (void **)&adaptor) == 0) { if (adaptor->source) { g_source_remove(adaptor->source); adaptor->source = 0; } } return 0; } struct qb_ipcs_poll_handlers gio_poll_funcs = { .job_add = NULL, .dispatch_add = gio_poll_dispatch_add, .dispatch_mod = gio_poll_dispatch_mod, .dispatch_del = gio_poll_dispatch_del, }; static enum qb_ipc_type pick_ipc_type(enum qb_ipc_type requested) { const char *env = getenv("PCMK_ipc_type"); if (env && strcmp("shared-mem", env) == 0) { return QB_IPC_SHM; } else if (env && strcmp("socket", env) == 0) { return QB_IPC_SOCKET; } else if (env && strcmp("posix", env) == 0) { return QB_IPC_POSIX_MQ; } else if (env && strcmp("sysv", env) == 0) { return QB_IPC_SYSV_MQ; } else if (requested == QB_IPC_NATIVE) { /* We prefer shared memory because the server never blocks on * send. If part of a message fits into the socket, libqb * needs to block until the remainder can be sent also. * Otherwise the client will wait forever for the remaining * bytes. */ return QB_IPC_SHM; } return requested; } qb_ipcs_service_t * mainloop_add_ipc_server(const char *name, enum qb_ipc_type type, - struct qb_ipcs_service_handlers * callbacks) + struct qb_ipcs_service_handlers *callbacks) +{ + return mainloop_add_ipc_server_with_prio(name, type, callbacks, QB_LOOP_MED); +} + +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) { int rc = 0; qb_ipcs_service_t *server = NULL; if (gio_map == NULL) { gio_map = qb_array_create_2(64, sizeof(struct gio_to_qb_poll), 1); } crm_client_init(); server = qb_ipcs_create(name, 0, pick_ipc_type(type), callbacks); + if (server == NULL) { + crm_err("Could not create %s IPC server: %s (%d)", name, pcmk_strerror(rc), rc); + return NULL; + } + + if (prio != QB_LOOP_MED) { + qb_ipcs_request_rate_limit(server, conv_libqb_prio2ratelimit(prio)); + } + #ifdef HAVE_IPCS_GET_BUFFER_SIZE /* All clients should use at least ipc_buffer_max as their buffer size */ qb_ipcs_enforce_buffer_size(server, crm_ipc_default_buffer_size()); #endif qb_ipcs_poll_handlers_set(server, &gio_poll_funcs); rc = qb_ipcs_run(server); if (rc < 0) { crm_err("Could not start %s IPC server: %s (%d)", name, pcmk_strerror(rc), rc); return NULL; } return server; } void mainloop_del_ipc_server(qb_ipcs_service_t * server) { if (server) { qb_ipcs_destroy(server); } } struct mainloop_io_s { char *name; void *userdata; int fd; guint source; crm_ipc_t *ipc; GIOChannel *channel; int (*dispatch_fn_ipc) (const char *buffer, ssize_t length, gpointer userdata); int (*dispatch_fn_io) (gpointer userdata); void (*destroy_fn) (gpointer userdata); }; static gboolean mainloop_gio_callback(GIOChannel * gio, GIOCondition condition, gpointer data) { gboolean keep = TRUE; mainloop_io_t *client = data; CRM_ASSERT(client->fd == g_io_channel_unix_get_fd(gio)); if (condition & G_IO_IN) { if (client->ipc) { long rc = 0; int max = 10; do { rc = crm_ipc_read(client->ipc); if (rc <= 0) { crm_trace("Message acquisition from %s[%p] failed: %s (%ld)", client->name, client, pcmk_strerror(rc), rc); } else if (client->dispatch_fn_ipc) { const char *buffer = crm_ipc_buffer(client->ipc); crm_trace("New message from %s[%p] = %ld (I/O condition=%d)", client->name, client, rc, condition); if (client->dispatch_fn_ipc(buffer, rc, client->userdata) < 0) { crm_trace("Connection to %s no longer required", client->name); keep = FALSE; } } } while (keep && rc > 0 && --max > 0); } else { crm_trace("New message from %s[%p] %u", client->name, client, condition); if (client->dispatch_fn_io) { if (client->dispatch_fn_io(client->userdata) < 0) { crm_trace("Connection to %s no longer required", client->name); keep = FALSE; } } } } if (client->ipc && crm_ipc_connected(client->ipc) == FALSE) { crm_err("Connection to %s closed " CRM_XS "client=%p condition=%d", client->name, client, condition); keep = FALSE; } else if (condition & (G_IO_HUP | G_IO_NVAL | G_IO_ERR)) { crm_trace("The connection %s[%p] has been closed (I/O condition=%d)", client->name, client, condition); keep = FALSE; } else if ((condition & G_IO_IN) == 0) { /* #define GLIB_SYSDEF_POLLIN =1 #define GLIB_SYSDEF_POLLPRI =2 #define GLIB_SYSDEF_POLLOUT =4 #define GLIB_SYSDEF_POLLERR =8 #define GLIB_SYSDEF_POLLHUP =16 #define GLIB_SYSDEF_POLLNVAL =32 typedef enum { G_IO_IN GLIB_SYSDEF_POLLIN, G_IO_OUT GLIB_SYSDEF_POLLOUT, G_IO_PRI GLIB_SYSDEF_POLLPRI, G_IO_ERR GLIB_SYSDEF_POLLERR, G_IO_HUP GLIB_SYSDEF_POLLHUP, G_IO_NVAL GLIB_SYSDEF_POLLNVAL } GIOCondition; A bitwise combination representing a condition to watch for on an event source. G_IO_IN There is data to read. G_IO_OUT Data can be written (without blocking). G_IO_PRI There is urgent data to read. G_IO_ERR Error condition. G_IO_HUP Hung up (the connection has been broken, usually for pipes and sockets). G_IO_NVAL Invalid request. The file descriptor is not open. */ crm_err("Strange condition: %d", condition); } /* keep == FALSE results in mainloop_gio_destroy() being called * just before the source is removed from mainloop */ return keep; } static void mainloop_gio_destroy(gpointer c) { mainloop_io_t *client = c; char *c_name = strdup(client->name); /* client->source is valid but about to be destroyed (ref_count == 0) in gmain.c * client->channel will still have ref_count > 0... should be == 1 */ crm_trace("Destroying client %s[%p]", c_name, c); if (client->ipc) { crm_ipc_close(client->ipc); } if (client->destroy_fn) { void (*destroy_fn) (gpointer userdata) = client->destroy_fn; client->destroy_fn = NULL; destroy_fn(client->userdata); } if (client->ipc) { crm_ipc_t *ipc = client->ipc; client->ipc = NULL; crm_ipc_destroy(ipc); } crm_trace("Destroyed client %s[%p]", c_name, c); free(client->name); client->name = NULL; free(client); free(c_name); } mainloop_io_t * mainloop_add_ipc_client(const char *name, int priority, size_t max_size, void *userdata, struct ipc_client_callbacks *callbacks) { mainloop_io_t *client = NULL; crm_ipc_t *conn = crm_ipc_new(name, max_size); if (conn && crm_ipc_connect(conn)) { int32_t fd = crm_ipc_get_fd(conn); client = mainloop_add_fd(name, priority, fd, userdata, NULL); } if (client == NULL) { crm_perror(LOG_TRACE, "Connection to %s failed", name); if (conn) { crm_ipc_close(conn); crm_ipc_destroy(conn); } return NULL; } client->ipc = conn; client->destroy_fn = callbacks->destroy; client->dispatch_fn_ipc = callbacks->dispatch; return client; } void mainloop_del_ipc_client(mainloop_io_t * client) { mainloop_del_fd(client); } crm_ipc_t * mainloop_get_ipc_client(mainloop_io_t * client) { if (client) { return client->ipc; } return NULL; } mainloop_io_t * mainloop_add_fd(const char *name, int priority, int fd, void *userdata, struct mainloop_fd_callbacks * callbacks) { mainloop_io_t *client = NULL; if (fd >= 0) { client = calloc(1, sizeof(mainloop_io_t)); if (client == NULL) { return NULL; } client->name = strdup(name); client->userdata = userdata; if (callbacks) { client->destroy_fn = callbacks->destroy; client->dispatch_fn_io = callbacks->dispatch; } client->fd = fd; client->channel = g_io_channel_unix_new(fd); client->source = g_io_add_watch_full(client->channel, priority, (G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR), mainloop_gio_callback, client, mainloop_gio_destroy); /* Now that mainloop now holds a reference to channel, * thanks to g_io_add_watch_full(), drop ours from g_io_channel_unix_new(). * * This means that channel will be free'd by: * g_main_context_dispatch() or g_source_remove() * -> g_source_destroy_internal() * -> g_source_callback_unref() * shortly after mainloop_gio_destroy() completes */ g_io_channel_unref(client->channel); crm_trace("Added connection %d for %s[%p].%d", client->source, client->name, client, fd); } else { errno = EINVAL; } return client; } void mainloop_del_fd(mainloop_io_t * client) { if (client != NULL) { crm_trace("Removing client %s[%p]", client->name, client); if (client->source) { /* Results in mainloop_gio_destroy() being called just * before the source is removed from mainloop */ g_source_remove(client->source); } } } static GListPtr child_list = NULL; pid_t mainloop_child_pid(mainloop_child_t * child) { return child->pid; } const char * mainloop_child_name(mainloop_child_t * child) { return child->desc; } int mainloop_child_timeout(mainloop_child_t * child) { return child->timeout; } void * mainloop_child_userdata(mainloop_child_t * child) { return child->privatedata; } void mainloop_clear_child_userdata(mainloop_child_t * child) { child->privatedata = NULL; } /* good function name */ static void child_free(mainloop_child_t *child) { if (child->timerid != 0) { crm_trace("Removing timer %d", child->timerid); g_source_remove(child->timerid); child->timerid = 0; } free(child->desc); free(child); } /* terrible function name */ static int child_kill_helper(mainloop_child_t *child) { int rc; if (child->flags & mainloop_leave_pid_group) { crm_debug("Kill pid %d only. leave group intact.", child->pid); rc = kill(child->pid, SIGKILL); } else { crm_debug("Kill pid %d's group", child->pid); rc = kill(-child->pid, SIGKILL); } if (rc < 0) { if (errno != ESRCH) { crm_perror(LOG_ERR, "kill(%d, KILL) failed", child->pid); } return -errno; } return 0; } static gboolean child_timeout_callback(gpointer p) { mainloop_child_t *child = p; int rc = 0; child->timerid = 0; if (child->timeout) { crm_crit("%s process (PID %d) will not die!", child->desc, (int)child->pid); return FALSE; } rc = child_kill_helper(child); if (rc == ESRCH) { /* Nothing left to do. pid doesn't exist */ return FALSE; } child->timeout = TRUE; crm_warn("%s process (PID %d) timed out", child->desc, (int)child->pid); child->timerid = g_timeout_add(5000, child_timeout_callback, child); return FALSE; } static gboolean child_waitpid(mainloop_child_t *child, int flags) { int rc = 0; int core = 0; int signo = 0; int status = 0; int exitcode = 0; rc = waitpid(child->pid, &status, flags); if(rc == 0) { crm_perror(LOG_DEBUG, "wait(%d) = %d", child->pid, rc); return FALSE; } else if(rc != child->pid) { signo = SIGCHLD; exitcode = 1; status = 1; crm_perror(LOG_ERR, "Call to waitpid(%d) failed", child->pid); } else { crm_trace("Managed process %d exited: %p", child->pid, child); if (WIFEXITED(status)) { exitcode = WEXITSTATUS(status); crm_trace("Managed process %d (%s) exited with rc=%d", child->pid, child->desc, exitcode); } else if (WIFSIGNALED(status)) { signo = WTERMSIG(status); crm_trace("Managed process %d (%s) exited with signal=%d", child->pid, child->desc, signo); } #ifdef WCOREDUMP if (WCOREDUMP(status)) { core = 1; crm_err("Managed process %d (%s) dumped core", child->pid, child->desc); } #endif } if (child->callback) { child->callback(child, child->pid, core, signo, exitcode); } return TRUE; } static void child_death_dispatch(int signal) { GListPtr iter = child_list; gboolean exited; while(iter) { GListPtr saved = NULL; mainloop_child_t *child = iter->data; exited = child_waitpid(child, WNOHANG); saved = iter; iter = iter->next; if (exited == FALSE) { continue; } crm_trace("Removing process entry %p for %d", child, child->pid); child_list = g_list_remove_link(child_list, saved); g_list_free(saved); child_free(child); } } static gboolean child_signal_init(gpointer p) { crm_trace("Installed SIGCHLD handler"); /* Do NOT use g_child_watch_add() and friends, they rely on pthreads */ mainloop_add_signal(SIGCHLD, child_death_dispatch); /* In case they terminated before the signal handler was installed */ child_death_dispatch(SIGCHLD); return FALSE; } int mainloop_child_kill(pid_t pid) { GListPtr iter; mainloop_child_t *child = NULL; mainloop_child_t *match = NULL; /* It is impossible to block SIGKILL, this allows us to * call waitpid without WNOHANG flag.*/ int waitflags = 0, rc = 0; for (iter = child_list; iter != NULL && match == NULL; iter = iter->next) { child = iter->data; if (pid == child->pid) { match = child; } } if (match == NULL) { return FALSE; } rc = child_kill_helper(match); if(rc == -ESRCH) { /* It's gone, but hasn't shown up in waitpid() yet * * Wait until we get SIGCHLD and let child_death_dispatch() * clean it up as normal (so we get the correct return * code/status) * * The blocking alternative would be to call: * child_waitpid(match, 0); */ crm_trace("Waiting for child %d to be reaped by child_death_dispatch()", match->pid); return TRUE; } else if(rc != 0) { /* If KILL for some other reason set the WNOHANG flag since we * can't be certain what happened. */ waitflags = WNOHANG; } if (child_waitpid(match, waitflags) == FALSE) { /* not much we can do if this occurs */ return FALSE; } child_list = g_list_remove(child_list, match); child_free(match); return TRUE; } /* Create/Log a new tracked process * To track a process group, use -pid */ 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)) { static bool need_init = TRUE; mainloop_child_t *child = g_new(mainloop_child_t, 1); child->pid = pid; child->timerid = 0; child->timeout = FALSE; child->privatedata = privatedata; child->callback = callback; child->flags = flags; if(desc) { child->desc = strdup(desc); } if (timeout) { child->timerid = g_timeout_add(timeout, child_timeout_callback, child); } child_list = g_list_append(child_list, child); if(need_init) { need_init = FALSE; /* SIGCHLD processing has to be invoked from mainloop. * We do not want it to be possible to both add a child pid * to mainloop, and have the pid's exit callback invoked within * the same callstack. */ g_timeout_add(1, child_signal_init, NULL); } } 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)) { mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, callback); } struct mainloop_timer_s { guint id; guint period_ms; bool repeat; char *name; GSourceFunc cb; void *userdata; }; static gboolean mainloop_timer_cb(gpointer user_data) { int id = 0; bool repeat = FALSE; struct mainloop_timer_s *t = user_data; CRM_ASSERT(t != NULL); id = t->id; t->id = 0; /* Ensure it's unset during callbacks so that * mainloop_timer_running() works as expected */ if(t->cb) { crm_trace("Invoking callbacks for timer %s", t->name); repeat = t->repeat; if(t->cb(t->userdata) == FALSE) { crm_trace("Timer %s complete", t->name); repeat = FALSE; } } if(repeat) { /* Restore if repeating */ t->id = id; } return repeat; } bool mainloop_timer_running(mainloop_timer_t *t) { if(t && t->id != 0) { return TRUE; } return FALSE; } void mainloop_timer_start(mainloop_timer_t *t) { mainloop_timer_stop(t); if(t && t->period_ms > 0) { crm_trace("Starting timer %s", t->name); t->id = g_timeout_add(t->period_ms, mainloop_timer_cb, t); } } void mainloop_timer_stop(mainloop_timer_t *t) { if(t && t->id != 0) { crm_trace("Stopping timer %s", t->name); g_source_remove(t->id); t->id = 0; } } guint mainloop_timer_set_period(mainloop_timer_t *t, guint period_ms) { guint last = 0; if(t) { last = t->period_ms; t->period_ms = period_ms; } if(t && t->id != 0 && last != t->period_ms) { mainloop_timer_start(t); } return last; } mainloop_timer_t * mainloop_timer_add(const char *name, guint period_ms, bool repeat, GSourceFunc cb, void *userdata) { mainloop_timer_t *t = calloc(1, sizeof(mainloop_timer_t)); if(t) { if(name) { t->name = crm_strdup_printf("%s-%u-%d", name, period_ms, repeat); } else { t->name = crm_strdup_printf("%p-%u-%d", t, period_ms, repeat); } t->id = 0; t->period_ms = period_ms; t->repeat = repeat; t->cb = cb; t->userdata = userdata; crm_trace("Created timer %s with %p %p", t->name, userdata, t->userdata); } return t; } void mainloop_timer_del(mainloop_timer_t *t) { if(t) { crm_trace("Destroying timer %s", t->name); mainloop_timer_stop(t); free(t->name); free(t); } } /* * Helpers to make sure certain events aren't lost at shutdown */ static gboolean drain_timeout_cb(gpointer user_data) { bool *timeout_popped = (bool*) user_data; *timeout_popped = TRUE; return FALSE; } /*! * \brief Process main loop events while a certain condition is met * * \param[in] mloop Main loop to process * \param[in] timer_ms Don't process longer than this amount of time * \param[in] check Function that returns TRUE if events should be processed * * \note This function is intended to be called at shutdown if certain important * events should not be missed. The caller would likely quit the main loop * or exit after calling this function. The check() function will be * passed the remaining timeout in milliseconds. */ void pcmk_drain_main_loop(GMainLoop *mloop, guint timer_ms, bool (*check)(guint)) { bool timeout_popped = FALSE; guint timer = 0; GMainContext *ctx = NULL; CRM_CHECK(mloop && check, return); ctx = g_main_loop_get_context(mloop); if (ctx) { time_t start_time = time(NULL); timer = g_timeout_add(timer_ms, drain_timeout_cb, &timeout_popped); while (!timeout_popped && check(timer_ms - (time(NULL) - start_time) * 1000)) { g_main_context_iteration(ctx, TRUE); } } if (!timeout_popped && (timer > 0)) { g_source_remove(timer); } } diff --git a/lib/common/utils.c b/lib/common/utils.c index 758eb1b460..d1c3e267f4 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,1200 +1,1201 @@ /* * Copyright 2004-2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MAXLINE # define MAXLINE 512 #endif #ifdef HAVE_GETOPT_H # include #endif #ifndef PW_BUFFER_LEN # define PW_BUFFER_LEN 500 #endif CRM_TRACE_INIT_DATA(common); gboolean crm_config_error = FALSE; gboolean crm_config_warning = FALSE; char *crm_system_name = NULL; int node_score_red = 0; int node_score_green = 0; int node_score_yellow = 0; static struct crm_option *crm_long_options = NULL; static const char *crm_app_description = NULL; static char *crm_short_options = NULL; static const char *crm_app_usage = NULL; gboolean check_time(const char *value) { if (crm_get_msec(value) < 5000) { return FALSE; } return TRUE; } gboolean check_timer(const char *value) { if (crm_get_msec(value) < 0) { return FALSE; } return TRUE; } gboolean check_boolean(const char *value) { int tmp = FALSE; if (crm_str_to_boolean(value, &tmp) != 1) { return FALSE; } return TRUE; } gboolean check_number(const char *value) { errno = 0; if (value == NULL) { return FALSE; } else if (safe_str_eq(value, CRM_MINUS_INFINITY_S)) { } else if (safe_str_eq(value, CRM_INFINITY_S)) { } else { crm_int_helper(value, NULL); } if (errno != 0) { return FALSE; } return TRUE; } gboolean check_positive_number(const char* value) { if (safe_str_eq(value, CRM_INFINITY_S) || (crm_int_helper(value, NULL))) { return TRUE; } return FALSE; } gboolean check_quorum(const char *value) { if (safe_str_eq(value, "stop")) { return TRUE; } else if (safe_str_eq(value, "freeze")) { return TRUE; } else if (safe_str_eq(value, "ignore")) { return TRUE; } else if (safe_str_eq(value, "suicide")) { return TRUE; } return FALSE; } gboolean check_script(const char *value) { struct stat st; if(safe_str_eq(value, "/dev/null")) { return TRUE; } if(stat(value, &st) != 0) { crm_err("Script %s does not exist", value); return FALSE; } if(S_ISREG(st.st_mode) == 0) { crm_err("Script %s is not a regular file", value); return FALSE; } if( (st.st_mode & (S_IXUSR | S_IXGRP )) == 0) { crm_err("Script %s is not executable", value); return FALSE; } return TRUE; } gboolean check_utilization(const char *value) { char *end = NULL; long number = strtol(value, &end, 10); if(end && end[0] != '%') { return FALSE; } else if(number < 0) { return FALSE; } return TRUE; } void crm_args_fini() { free(crm_short_options); crm_short_options = NULL; } int char2score(const char *score) { int score_f = 0; if (score == NULL) { } else if (safe_str_eq(score, CRM_MINUS_INFINITY_S)) { score_f = -CRM_SCORE_INFINITY; } else if (safe_str_eq(score, CRM_INFINITY_S)) { score_f = CRM_SCORE_INFINITY; } else if (safe_str_eq(score, CRM_PLUS_INFINITY_S)) { score_f = CRM_SCORE_INFINITY; } else if (safe_str_eq(score, "red")) { score_f = node_score_red; } else if (safe_str_eq(score, "yellow")) { score_f = node_score_yellow; } else if (safe_str_eq(score, "green")) { score_f = node_score_green; } else { score_f = crm_parse_int(score, NULL); if (score_f > 0 && score_f > CRM_SCORE_INFINITY) { score_f = CRM_SCORE_INFINITY; } else if (score_f < 0 && score_f < -CRM_SCORE_INFINITY) { score_f = -CRM_SCORE_INFINITY; } } return score_f; } char * score2char_stack(int score, char *buf, size_t len) { if (score >= CRM_SCORE_INFINITY) { strncpy(buf, CRM_INFINITY_S, 9); } else if (score <= -CRM_SCORE_INFINITY) { strncpy(buf, CRM_MINUS_INFINITY_S , 10); } else { return crm_itoa_stack(score, buf, len); } return buf; } char * score2char(int score) { if (score >= CRM_SCORE_INFINITY) { return strdup(CRM_INFINITY_S); } else if (score <= -CRM_SCORE_INFINITY) { return strdup(CRM_MINUS_INFINITY_S); } return crm_itoa(score); } const char * cluster_option(GHashTable * options, gboolean(*validate) (const char *), const char *name, const char *old_name, const char *def_value) { const char *value = NULL; char *new_value = NULL; CRM_ASSERT(name != NULL); if (options) { value = g_hash_table_lookup(options, name); if ((value == NULL) && old_name) { value = g_hash_table_lookup(options, old_name); if (value != NULL) { crm_config_warn("Support for legacy name '%s' for cluster option '%s'" " is deprecated and will be removed in a future release", old_name, name); // Inserting copy with current name ensures we only warn once new_value = strdup(value); g_hash_table_insert(options, strdup(name), new_value); value = new_value; } } if (value && validate && (validate(value) == FALSE)) { crm_config_err("Resetting cluster option '%s' to default: value '%s' is invalid", name, value); value = NULL; } if (value) { return value; } } // No value found, use default value = def_value; if (value == NULL) { crm_trace("No value or default provided for cluster option '%s'", name); return NULL; } if (validate) { CRM_CHECK(validate(value) != FALSE, crm_err("Bug: default value for cluster option '%s' is invalid", name); return NULL); } crm_trace("Using default value '%s' for cluster option '%s'", value, name); if (options) { new_value = strdup(value); g_hash_table_insert(options, strdup(name), new_value); value = new_value; } return value; } const char * get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, const char *name) { const char *value = NULL; for (int lpc = 0; lpc < len; lpc++) { if (safe_str_eq(name, option_list[lpc].name)) { value = cluster_option(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); return value; } } CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name)); return NULL; } void config_metadata(const char *name, const char *version, const char *desc_short, const char *desc_long, pe_cluster_option * option_list, int len) { int lpc = 0; fprintf(stdout, "" "\n" "\n" " %s\n" " %s\n" " %s\n" " \n", name, version, desc_long, desc_short); for (lpc = 0; lpc < len; lpc++) { if (option_list[lpc].description_long == NULL && option_list[lpc].description_short == NULL) { continue; } fprintf(stdout, " \n" " %s\n" " \n" " %s%s%s\n" " \n", option_list[lpc].name, option_list[lpc].description_short, option_list[lpc].type, option_list[lpc].default_value, option_list[lpc].description_long ? option_list[lpc]. description_long : option_list[lpc].description_short, option_list[lpc].values ? " Allowed values: " : "", option_list[lpc].values ? option_list[lpc].values : ""); } fprintf(stdout, " \n\n"); } void verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len) { int lpc = 0; for (lpc = 0; lpc < len; lpc++) { cluster_option(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); } } char * generate_hash_key(const char *crm_msg_reference, const char *sys) { char *hash_key = crm_concat(sys ? sys : "none", crm_msg_reference, '_'); crm_trace("created hash key: (%s)", hash_key); return hash_key; } int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid) { int rc = pcmk_ok; char *buffer = NULL; struct passwd pwd; struct passwd *pwentry = NULL; buffer = calloc(1, PW_BUFFER_LEN); rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry); if (pwentry) { if (uid) { *uid = pwentry->pw_uid; } if (gid) { *gid = pwentry->pw_gid; } crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid); } else { rc = rc? -rc : -EINVAL; crm_info("User %s lookup: %s", name, pcmk_strerror(rc)); } free(buffer); return rc; } static int crm_version_helper(const char *text, const char **end_text) { int atoi_result = -1; CRM_ASSERT(end_text != NULL); errno = 0; if (text != NULL && text[0] != 0) { /* seemingly sacrificing const-correctness -- because while strtol doesn't modify the input, it doesn't want to artificially taint the "end_text" pointer-to-pointer-to-first-char-in-string with constness in case the input wasn't actually constant -- by semantic definition not a single character will get modified so it shall be perfectly safe to make compiler happy with dropping "const" qualifier here */ atoi_result = (int) strtol(text, (char **) end_text, 10); if (errno == EINVAL) { crm_err("Conversion of '%s' %c failed", text, text[0]); atoi_result = -1; } } return atoi_result; } /* * version1 < version2 : -1 * version1 = version2 : 0 * version1 > version2 : 1 */ int compare_version(const char *version1, const char *version2) { int rc = 0; int lpc = 0; const char *ver1_iter, *ver2_iter; if (version1 == NULL && version2 == NULL) { return 0; } else if (version1 == NULL) { return -1; } else if (version2 == NULL) { return 1; } ver1_iter = version1; ver2_iter = version2; while (1) { int digit1 = 0; int digit2 = 0; lpc++; if (ver1_iter == ver2_iter) { break; } if (ver1_iter != NULL) { digit1 = crm_version_helper(ver1_iter, &ver1_iter); } if (ver2_iter != NULL) { digit2 = crm_version_helper(ver2_iter, &ver2_iter); } if (digit1 < digit2) { rc = -1; break; } else if (digit1 > digit2) { rc = 1; break; } if (ver1_iter != NULL && *ver1_iter == '.') { ver1_iter++; } if (ver1_iter != NULL && *ver1_iter == '\0') { ver1_iter = NULL; } if (ver2_iter != NULL && *ver2_iter == '.') { ver2_iter++; } if (ver2_iter != NULL && *ver2_iter == 0) { ver2_iter = NULL; } } if (rc == 0) { crm_trace("%s == %s (%d)", version1, version2, lpc); } else if (rc < 0) { crm_trace("%s < %s (%d)", version1, version2, lpc); } else if (rc > 0) { crm_trace("%s > %s (%d)", version1, version2, lpc); } return rc; } gboolean do_stderr = FALSE; #ifndef NUMCHARS # define NUMCHARS "0123456789." #endif #ifndef WHITESPACE # define WHITESPACE " \t\n\r\f" #endif guint crm_parse_interval_spec(const char *input) { long long msec = 0; if (input == NULL) { return 0; } else if (input[0] != 'P') { long long tmp = crm_get_msec(input); if(tmp > 0) { msec = tmp; } } else { crm_time_t *period_s = crm_time_parse_duration(input); msec = 1000 * crm_time_get_seconds(period_s); crm_time_free(period_s); } return (msec <= 0)? 0 : ((msec >= G_MAXUINT)? G_MAXUINT : (guint) msec); } long long crm_get_msec(const char *input) { const char *cp = input; const char *units; long long multiplier = 1000; long long divisor = 1; long long msec = -1; char *end_text = NULL; /* double dret; */ if (input == NULL) { return msec; } cp += strspn(cp, WHITESPACE); units = cp + strspn(cp, NUMCHARS); units += strspn(units, WHITESPACE); if (strchr(NUMCHARS, *cp) == NULL) { return msec; } if (strncasecmp(units, "ms", 2) == 0 || strncasecmp(units, "msec", 4) == 0) { multiplier = 1; divisor = 1; } else if (strncasecmp(units, "us", 2) == 0 || strncasecmp(units, "usec", 4) == 0) { multiplier = 1; divisor = 1000; } else if (strncasecmp(units, "s", 1) == 0 || strncasecmp(units, "sec", 3) == 0) { multiplier = 1000; divisor = 1; } else if (strncasecmp(units, "m", 1) == 0 || strncasecmp(units, "min", 3) == 0) { multiplier = 60 * 1000; divisor = 1; } else if (strncasecmp(units, "h", 1) == 0 || strncasecmp(units, "hr", 2) == 0) { multiplier = 60 * 60 * 1000; divisor = 1; } else if (*units != EOS && *units != '\n' && *units != '\r') { return msec; } msec = crm_int_helper(cp, &end_text); if (msec > LLONG_MAX/multiplier) { /* arithmetics overflow while multiplier/divisor mutually exclusive */ return LLONG_MAX; } msec *= multiplier; msec /= divisor; /* dret += 0.5; */ /* msec = (long long)dret; */ return msec; } extern bool crm_is_daemon; /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork) { int rc = 0; int pid = 0; int status = 0; /* Implied by the parent's error logging below */ /* crm_write_blackbox(0); */ if(crm_is_daemon == FALSE) { /* This is a command line tool - do not fork */ /* crm_add_logfile(NULL); * Record it to a file? */ crm_enable_stderr(TRUE); /* Make sure stderr is enabled so we can tell the caller */ do_fork = FALSE; /* Just crash if needed */ } if (do_core == FALSE) { crm_err("%s: Triggered assert at %s:%d : %s", function, file, line, assert_condition); return; } else if (do_fork) { pid = fork(); } else { crm_err("%s: Triggered fatal assert at %s:%d : %s", function, file, line, assert_condition); } if (pid == -1) { crm_crit("%s: Cannot create core for non-fatal assert at %s:%d : %s", function, file, line, assert_condition); return; } else if(pid == 0) { /* Child process */ abort(); return; } /* Parent process */ crm_err("%s: Forked child %d to record non-fatal assert at %s:%d : %s", function, pid, file, line, assert_condition); crm_write_blackbox(SIGTRAP, NULL); do { rc = waitpid(pid, &status, 0); if(rc == pid) { return; /* Job done */ } } while(errno == EINTR); if (errno == ECHILD) { /* crm_mon does this */ crm_trace("Cannot wait on forked child %d - SIGCHLD is probably set to SIG_IGN", pid); return; } crm_perror(LOG_ERR, "Cannot wait on forked child %d", pid); } void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile) { int rc; long pid; const char *devnull = "/dev/null"; if (daemonize == FALSE) { return; } /* Check before we even try... */ rc = crm_pidfile_inuse(pidfile, 1, name); if(rc < pcmk_ok && rc != -ENOENT) { pid = crm_read_pidfile(pidfile); crm_err("%s: already running [pid %ld in %s]", name, pid, pidfile); printf("%s: already running [pid %ld in %s]\n", name, pid, pidfile); crm_exit(CRM_EX_ERROR); } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); crm_perror(LOG_ERR, "fork"); crm_exit(CRM_EX_OSERR); } else if (pid > 0) { crm_exit(CRM_EX_OK); } rc = crm_lock_pidfile(pidfile, name); if(rc < pcmk_ok) { crm_err("Could not lock '%s' for %s: %s (%d)", pidfile, name, pcmk_strerror(rc), rc); printf("Could not lock '%s' for %s: %s (%d)\n", pidfile, name, pcmk_strerror(rc), rc); crm_exit(CRM_EX_ERROR); } umask(S_IWGRP | S_IWOTH | S_IROTH); close(STDIN_FILENO); (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ close(STDOUT_FILENO); (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ close(STDERR_FILENO); (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ } char * crm_meta_name(const char *field) { int lpc = 0; int max = 0; char *crm_name = NULL; CRM_CHECK(field != NULL, return NULL); crm_name = crm_concat(CRM_META, field, '_'); /* Massage the names so they can be used as shell variables */ max = strlen(crm_name); for (; lpc < max; lpc++) { switch (crm_name[lpc]) { case '-': crm_name[lpc] = '_'; break; } } return crm_name; } const char * crm_meta_value(GHashTable * hash, const char *field) { char *key = NULL; const char *value = NULL; key = crm_meta_name(field); if (key) { value = g_hash_table_lookup(hash, key); free(key); } return value; } static struct option * crm_create_long_opts(struct crm_option *long_options) { struct option *long_opts = NULL; #ifdef HAVE_GETOPT_H int index = 0, lpc = 0; /* * A previous, possibly poor, choice of '?' as the short form of --help * means that getopt_long() returns '?' for both --help and for "unknown option" * * This dummy entry allows us to differentiate between the two in crm_get_option() * and exit with the correct error code */ long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); long_opts[index].name = "__dummmy__"; long_opts[index].has_arg = 0; long_opts[index].flag = 0; long_opts[index].val = '_'; index++; for (lpc = 0; long_options[lpc].name != NULL; lpc++) { if (long_options[lpc].name[0] == '-') { continue; } long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); /*fprintf(stderr, "Creating %d %s = %c\n", index, * long_options[lpc].name, long_options[lpc].val); */ long_opts[index].name = long_options[lpc].name; long_opts[index].has_arg = long_options[lpc].has_arg; long_opts[index].flag = long_options[lpc].flag; long_opts[index].val = long_options[lpc].val; index++; } /* Now create the list terminator */ long_opts = realloc_safe(long_opts, (index + 1) * sizeof(struct option)); long_opts[index].name = NULL; long_opts[index].has_arg = 0; long_opts[index].flag = 0; long_opts[index].val = 0; #endif return long_opts; } void crm_set_options(const char *short_options, const char *app_usage, struct crm_option *long_options, const char *app_desc) { if (short_options) { crm_short_options = strdup(short_options); } else if (long_options) { int lpc = 0; int opt_string_len = 0; char *local_short_options = NULL; for (lpc = 0; long_options[lpc].name != NULL; lpc++) { if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) { local_short_options = realloc_safe(local_short_options, opt_string_len + 4); local_short_options[opt_string_len++] = long_options[lpc].val; /* getopt(3) says: Two colons mean an option takes an optional arg; */ if (long_options[lpc].has_arg == optional_argument) { local_short_options[opt_string_len++] = ':'; } if (long_options[lpc].has_arg >= required_argument) { local_short_options[opt_string_len++] = ':'; } local_short_options[opt_string_len] = 0; } } crm_short_options = local_short_options; crm_trace("Generated short option string: '%s'", local_short_options); } if (long_options) { crm_long_options = long_options; } if (app_desc) { crm_app_description = app_desc; } if (app_usage) { crm_app_usage = app_usage; } } int crm_get_option(int argc, char **argv, int *index) { return crm_get_option_long(argc, argv, index, NULL); } int crm_get_option_long(int argc, char **argv, int *index, const char **longname) { #ifdef HAVE_GETOPT_H static struct option *long_opts = NULL; if (long_opts == NULL && crm_long_options) { long_opts = crm_create_long_opts(crm_long_options); } *index = 0; if (long_opts) { int flag = getopt_long(argc, argv, crm_short_options, long_opts, index); switch (flag) { case 0: if (long_opts[*index].val) { return long_opts[*index].val; } else if (longname) { *longname = long_opts[*index].name; } else { crm_notice("Unhandled option --%s", long_opts[*index].name); return flag; } case -1: /* End of option processing */ break; case ':': crm_trace("Missing argument"); crm_help('?', CRM_EX_USAGE); break; case '?': crm_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE)); break; } return flag; } #endif if (crm_short_options) { return getopt(argc, argv, crm_short_options); } return -1; } void crm_help(char cmd, crm_exit_t exit_code) { int i = 0; FILE *stream = (exit_code ? stderr : stdout); if (cmd == 'v' || cmd == '$') { fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION); fprintf(stream, "Written by Andrew Beekhof\n"); goto out; } if (cmd == '!') { fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); goto out; } fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description); if (crm_app_usage) { fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage); } if (crm_long_options) { fprintf(stream, "Options:\n"); for (i = 0; crm_long_options[i].name != NULL; i++) { if (crm_long_options[i].flags & pcmk_option_hidden) { } else if (crm_long_options[i].flags & pcmk_option_paragraph) { fprintf(stream, "%s\n\n", crm_long_options[i].desc); } else if (crm_long_options[i].flags & pcmk_option_example) { fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc); } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) { fprintf(stream, "%s\n", crm_long_options[i].desc); } else { /* is val printable as char ? */ if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) { fprintf(stream, " -%c,", crm_long_options[i].val); } else { fputs(" ", stream); } fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name, crm_long_options[i].has_arg == optional_argument ? "[=value]" : crm_long_options[i].has_arg == required_argument ? "=value" : "", crm_long_options[i].desc ? crm_long_options[i].desc : ""); } } } else if (crm_short_options) { fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description); for (i = 0; crm_short_options[i] != 0; i++) { int has_arg = no_argument /* 0 */; if (crm_short_options[i + 1] == ':') { if (crm_short_options[i + 2] == ':') has_arg = optional_argument /* 2 */; else has_arg = required_argument /* 1 */; } fprintf(stream, " -%c %s\n", crm_short_options[i], has_arg == optional_argument ? "[value]" : has_arg == required_argument ? "{value}" : ""); i += has_arg; } } fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT); out: crm_exit(exit_code); while(1); // above does not return } void cib_ipc_servers_init(qb_ipcs_service_t **ipcs_ro, qb_ipcs_service_t **ipcs_rw, qb_ipcs_service_t **ipcs_shm, struct qb_ipcs_service_handlers *ro_cb, struct qb_ipcs_service_handlers *rw_cb) { *ipcs_ro = mainloop_add_ipc_server(CIB_CHANNEL_RO, QB_IPC_NATIVE, ro_cb); *ipcs_rw = mainloop_add_ipc_server(CIB_CHANNEL_RW, QB_IPC_NATIVE, rw_cb); *ipcs_shm = mainloop_add_ipc_server(CIB_CHANNEL_SHM, QB_IPC_SHM, rw_cb); if (*ipcs_ro == NULL || *ipcs_rw == NULL || *ipcs_shm == NULL) { crm_err("Failed to create the CIB manager: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled"); crm_exit(CRM_EX_FATAL); } } void cib_ipc_servers_destroy(qb_ipcs_service_t *ipcs_ro, qb_ipcs_service_t *ipcs_rw, qb_ipcs_service_t *ipcs_shm) { qb_ipcs_destroy(ipcs_ro); qb_ipcs_destroy(ipcs_rw); qb_ipcs_destroy(ipcs_shm); } qb_ipcs_service_t * crmd_ipc_server_init(struct qb_ipcs_service_handlers *cb) { return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_NATIVE, cb); } void attrd_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { *ipcs = mainloop_add_ipc_server(T_ATTRD, QB_IPC_NATIVE, cb); if (*ipcs == NULL) { crm_err("Failed to create pacemaker-attrd server: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); crm_exit(CRM_EX_FATAL); } } void stonith_ipc_server_init(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { - *ipcs = mainloop_add_ipc_server("stonith-ng", QB_IPC_NATIVE, cb); + *ipcs = mainloop_add_ipc_server_with_prio("stonith-ng", QB_IPC_NATIVE, cb, + QB_LOOP_HIGH); if (*ipcs == NULL) { crm_err("Failed to create fencer: exiting and inhibiting respawn."); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); crm_exit(CRM_EX_FATAL); } } void * find_library_function(void **handle, const char *lib, const char *fn, gboolean fatal) { char *error; void *a_function; if (*handle == NULL) { *handle = dlopen(lib, RTLD_LAZY); } if (!(*handle)) { crm_err("%sCould not open %s: %s", fatal ? "Fatal: " : "", lib, dlerror()); if (fatal) { crm_exit(CRM_EX_FATAL); } return NULL; } a_function = dlsym(*handle, fn); if (a_function == NULL) { error = dlerror(); crm_err("%sCould not find %s in %s: %s", fatal ? "Fatal: " : "", fn, lib, error); if (fatal) { crm_exit(CRM_EX_FATAL); } } return a_function; } #ifdef HAVE_UUID_UUID_H # include #endif char * crm_generate_uuid(void) { unsigned char uuid[16]; char *buffer = malloc(37); /* Including NUL byte */ uuid_generate(uuid); uuid_unparse(uuid, buffer); return buffer; } /*! * \brief Get name to be used as identifier for cluster messages * * \param[in] name Actual system name to check * * \return Non-NULL cluster message identifier corresponding to name * * \note The Pacemaker daemons were renamed in version 2.0.0, but the old names * must continue to be used as the identifier for cluster messages, so * that mixed-version clusters are possible during a rolling upgrade. */ const char * pcmk_message_name(const char *name) { if (name == NULL) { return "unknown"; } else if (!strcmp(name, "pacemaker-attrd")) { return "attrd"; } else if (!strcmp(name, "pacemaker-based")) { return CRM_SYSTEM_CIB; } else if (!strcmp(name, "pacemaker-controld")) { return CRM_SYSTEM_CRMD; } else if (!strcmp(name, "pacemaker-execd")) { return CRM_SYSTEM_LRMD; } else if (!strcmp(name, "pacemaker-fenced")) { return "stonith-ng"; } else if (!strcmp(name, "pacemaker-schedulerd")) { return CRM_SYSTEM_PENGINE; } else { return name; } } /*! * \brief Check whether a string represents a cluster daemon name * * \param[in] name String to check * * \return TRUE if name is standard client name used by daemons, FALSE otherwise */ bool crm_is_daemon_name(const char *name) { name = pcmk_message_name(name); return (!strcmp(name, CRM_SYSTEM_CRMD) || !strcmp(name, CRM_SYSTEM_STONITHD) || !strcmp(name, "stonith-ng") || !strcmp(name, "attrd") || !strcmp(name, CRM_SYSTEM_CIB) || !strcmp(name, CRM_SYSTEM_MCP) || !strcmp(name, CRM_SYSTEM_DC) || !strcmp(name, CRM_SYSTEM_TENGINE) || !strcmp(name, CRM_SYSTEM_LRMD)); } #include char * crm_md5sum(const char *buffer) { int lpc = 0, len = 0; char *digest = NULL; unsigned char raw_digest[MD5_DIGEST_SIZE]; if (buffer == NULL) { buffer = ""; } len = strlen(buffer); crm_trace("Beginning digest of %d bytes", len); digest = malloc(2 * MD5_DIGEST_SIZE + 1); if(digest) { md5_buffer(buffer, len, raw_digest); for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) { sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]); } digest[(2 * MD5_DIGEST_SIZE)] = 0; crm_trace("Digest %s.", digest); } else { crm_err("Could not create digest"); } return digest; } #ifdef HAVE_GNUTLS_GNUTLS_H void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); } #endif /*! * \brief Get the local hostname * * \return Newly allocated string with name, or NULL (and set errno) on error */ char * pcmk_hostname() { struct utsname hostinfo; return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename); } diff --git a/maint/mocked/Makefile b/maint/mocked/Makefile new file mode 100644 index 0000000000..1b729c9d52 --- /dev/null +++ b/maint/mocked/Makefile @@ -0,0 +1,44 @@ +# +# Copyright 2019 the Pacemaker project contributors +# +# The version control history for this file may have further details. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. +# + +#BASED_LDFLAGS = $$(pkgconf -libs glib-2.0) \ +# $$(pkgconf -libs libxml-2.0) \ +# $$(pkgconf -libs libqb) \ +# $$(pkgconf -libs pacemaker) +BASED_LDFLAGS = $$(pkgconf -libs glib-2.0) \ + $$(pkgconf -libs libxml-2.0) \ + $$(pkgconf -libs libqb) \ + -Wl,-rpath=$(CURDIR)/../../lib/common/.libs \ + -L../../lib/common/.libs -lcrmcommon \ + -L../../lib/pacemaker/.libs -lpacemaker + +BASED_CPPFLAGS = $$(pkgconf -cflags glib-2.0) \ + $$(pkgconf -cflags libxml-2.0) \ + $$(pkgconf -cflags libqb) \ + -I ../.. -I ../../include -g + +PROGRAMS = based + +BASED_OBJECTS = based.o + +# include or not the modules as you wish +BASED_OBJECTS += based-notifyfenced.o + +all: ${PROGRAMS} + +based: $(BASED_OBJECTS) + $(CC) $(BASED_LDFLAGS) $^ -o $@ + +$(BASED_OBJECTS): %.o: %.c + $(CC) $(BASED_CPPFLAGS) $(BASED_LDFLAGS) -c $< -o $@ + +clean: + rm ${PROGRAMS} $(BASED_OBJECTS) diff --git a/maint/mocked/based-notifyfenced.c b/maint/mocked/based-notifyfenced.c new file mode 100644 index 0000000000..a07bf2a3e4 --- /dev/null +++ b/maint/mocked/based-notifyfenced.c @@ -0,0 +1,243 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * Licensed under the GNU General Public License version 2 or later (GPLv2+). + */ + +/* + * Intended demo use case: + * + * - as root, start corosync + * - start "./based -N"; hint: + * su -s /bin/sh -c './based -N' hacluster + * - start pacemaker-fenced; hint: + * su -c 'env PCMK_logpriority=crit ../../daemons/fenced/pacemaker-fenced' + * - wait a bit (5 < seconds < 20) + * - as haclient group (or root), run "stonith admin --list-registered" + * - observe whether such invocation is blocked or not + */ + + +#include /* printf, perror */ + +#include "crm/cib.h" /* cib_zero_copy */ +#include "crm/cib/internal.h" /* CIB_OP_CREATE */ +#include "crm/msg_xml.h" /* F_SUBTYPE */ +#include "daemons/based/pacemaker-based.h" /* cib_notify_diff */ + +#include "based.h" + + +#define OPTCHAR 'N' +static size_t module_handle; + + +struct cib_notification_s { + xmlNode *msg; + struct iovec *iov; + int32_t iov_size; +}; + +/* see based/based_notify.c:cib_notify_send_one */ +static bool +mock_based_cib_notify_send_one(crm_client_t *client, xmlNode *xml) +{ + const char *type = NULL; + bool do_send = false; + + struct iovec *iov; + ssize_t rc = crm_ipc_prepare(0, xml, &iov, 0); + struct cib_notification_s update = { + .msg = xml, + .iov = iov, + .iov_size = rc, + }; + + CRM_CHECK(client != NULL, return true); + if (client->ipcs == NULL && client->remote == NULL) { + crm_warn("Skipping client with NULL channel"); + return FALSE; + } + + type = crm_element_value(update.msg, F_SUBTYPE); + CRM_LOG_ASSERT(type != NULL); + if (is_set(client->options, cib_notify_diff) + && safe_str_eq(type, T_CIB_DIFF_NOTIFY)) { + + if (crm_ipcs_sendv(client, update.iov, crm_ipc_server_event) < 0) + crm_warn("Notification of client %s/%s failed", client->name, client->id); + + } + pcmk_free_ipc_event(iov); + + return FALSE; +} + +/* see based/based_notify.c:do_cib_notify + cib_notify_send */ +void +do_cib_notify(crm_client_t *cib_client, int options, const char *op, + xmlNode *update, int result, xmlNode *result_data, + const char *msg_type) +{ + xmlNode *update_msg = NULL; + const char *id = NULL; + + update_msg = create_xml_node(NULL, "notify"); + + + crm_xml_add(update_msg, F_TYPE, T_CIB_NOTIFY); + crm_xml_add(update_msg, F_SUBTYPE, msg_type); + crm_xml_add(update_msg, F_CIB_OPERATION, op); + crm_xml_add_int(update_msg, F_CIB_RC, result); + + if (result_data != NULL) { + id = crm_element_value(result_data, XML_ATTR_ID); + if (id != NULL) + crm_xml_add(update_msg, F_CIB_OBJID, id); + } + + if (update != NULL) { + crm_trace("Setting type to update->name: %s", crm_element_name(update)); + crm_xml_add(update_msg, F_CIB_OBJTYPE, crm_element_name(update)); + + } else if (result_data != NULL) { + crm_trace("Setting type to new_obj->name: %s", crm_element_name(result_data)); + crm_xml_add(update_msg, F_CIB_OBJTYPE, crm_element_name(result_data)); + + } else { + crm_trace("Not Setting type"); + } + +#if 0 + attach_cib_generation(update_msg, "cib_generation", the_cib); +#endif + + if (update != NULL) { + add_message_xml(update_msg, F_CIB_UPDATE, update); + } + if (result_data != NULL) { + add_message_xml(update_msg, F_CIB_UPDATE_RESULT, result_data); + } + + mock_based_cib_notify_send_one(cib_client, update_msg); + free_xml(update_msg); +} + +static gboolean +mock_based_notifyfencedmer_callback_worker(gpointer data) +{ + crm_client_t *cib_client = (crm_client_t *) data; + + xmlNode *result_data; + xmlNode *input, *update; + int options; + char update_str[4096]; + + options |= cib_zero_copy; + + + input = create_xml_node(NULL, "cib"); + + /* spam it */ +#if 0 + for (size_t i = 0; i < SIZE_MAX - 1; i++) { +#else + for (size_t i = 0; i < 10000; i++) { +#endif + /* NOTE: we need to trigger fenced attention, add new fence device */ + snprintf(update_str, sizeof(update_str), +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n", i, i+1); + update = xmlReadMemory(update_str, sizeof(update_str), + "file:///tmp/update", NULL, 0)->children; + do_cib_notify(cib_client, options, CIB_OP_CREATE, input, pcmk_ok, + update, T_CIB_DIFF_NOTIFY); + free_xml(update); + }; + + free_xml(input); +} + +static void +mock_based_notifyfenced_cib_notify_hook(crm_client_t *cib_client) +{ + + /* MOCK: client asked for upcoming diff's, let's + spam it a bit after a while... */ + crm_info("Going to spam %s (%s) in 5 seconds...", + cib_client->name, cib_client->id); + mainloop_timer_start(mainloop_timer_add("spammer", 5000, FALSE, + mock_based_notifyfencedmer_callback_worker, + cib_client)); +} + +/* * */ + +static int +mock_based_notifyfenced_argparse_hook(struct mock_based_context_s *ctxt, + bool usage, int argc_to_go, + const char *argv_to_go[]) +{ + const char *opt = *argv_to_go; +restart: + switch(*opt) { + case '-': + if (opt == *argv_to_go) { + opt++; + goto restart; + } + break; + case OPTCHAR: + if (usage) { + printf("spam the \"cib diff\" notification client" + " (targeting pacemaker-fenced in particular)\n"); + + } else { +#if 0 + ctxt->modules[module_handle]->priv = + malloc(sizeof(mock_based_notifyfenced_priv_t)); + if (ctxt->modules[module_handle]->priv == NULL) { + perror("malloc"); + return -1; + } +#endif + } + return 1; + } + return 0; +} + +#if 0 +static void +mock_based_notifyfenced_destroy_hook(module_t *mod) { + free(mod->priv); +} +#endif + +__attribute__((__constructor__)) +void +mock_based_notifyfenced_init(void) { + module_handle = mock_based_register_module((module_t){ + .shortopt = OPTCHAR, + .hooks = { + .argparse = mock_based_notifyfenced_argparse_hook, + //.destroy = mock_based_notifyfenced_destroy_hook, + /* specialized hooks */ + .cib_notify = mock_based_notifyfenced_cib_notify_hook, + } + }); +} diff --git a/maint/mocked/based.c b/maint/mocked/based.c new file mode 100644 index 0000000000..70ac64042d --- /dev/null +++ b/maint/mocked/based.c @@ -0,0 +1,335 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * Licensed under the GNU General Public License version 2 or later (GPLv2+). + */ + +/* + * Clean room attempt (admittedly with lot of code borrowed or inspired from + * the full-blown daemon), minimalistic implementation of based daemon, with + * only important aspects being implemented at the moment. + * + * Hopefully easy to adapt for variety of purposes. + * + * NOTE: currently, only cib_rw API end-point is opened, future refinements + * as new modules are added should conditionalize per what the module + * indicates in the context (which is intentionally very loose data glue + * between the skeleton and modules themselves (like CGI variables so + * to say, but more structurally predestined so as to avoid complexities + * of hash table lookups etc.) + */ + +#include +#if 0 +#include "crm/common/ipcs.h" /* crm_client_t */ +#include "crm/common/xml.h" /* crm_xml_add */ +#endif +#include "crm/msg_xml.h" /* F_SUBTYPE */ +#include "daemons/based/pacemaker-based.h" /* cib_notify_diff */ + +#include /* qb_ipcs_connection_t */ + +#include "based.h" + + +/* direct global access violated in one case only + - mock_based_ipc_accept adds a reference to it to crm_cient_t->userdata */ +mock_based_context_t mock_based_context; + + +/* see based/based_callbacks.c:cib_ipc_accept */ +static int32_t +mock_based_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) +{ + int32_t ret = 0; + crm_client_t *cib_client; + + crm_trace("Connection %p", c); + if ((cib_client = crm_client_new(c, uid, gid)) == NULL) { + ret = -EIO; + } + + cib_client->userdata = &mock_based_context; + + return ret; +} + +/* see based/based_callbacks.c:cib_ipc_created */ +static void +mock_based_ipc_created(qb_ipcs_connection_t *c) +{ + crm_trace("Connection %p", c); +} + +/* see based/based_callbacks.c:cib_ipc_closed */ +static int32_t +mock_based_ipc_closed(qb_ipcs_connection_t *c) +{ + crm_client_t *client = crm_client_get(c); + + if (client != NULL) { + crm_trace("Connection %p", c); + crm_client_destroy(client); + } + + return 0; +} + +/* see based/based_callbacks.c:cib_ipc_destroy */ +static void +mock_based_ipc_destroy(qb_ipcs_connection_t *c) +{ + crm_trace("Connection %p", c); + mock_based_ipc_closed(c); +} + +/* see based/based_callbacks.c:cib_process_command (and more) */ +static void +mock_based_handle_query(crm_client_t *cib_client, uint32_t flags, + const xmlNode *op_request) +{ + xmlNode *reply, *cib; + const char cib_str[] = +#if 0 +""; +#else +""\ +" "\ +" "\ +" "\ +" "\ +" "\ +" "\ +" "\ +""; +#endif + cib = xmlReadMemory(cib_str, sizeof(cib_str), "file:///tmp/foo", NULL, 0)->children; + + reply = create_xml_node(NULL, "cib-reply"); + crm_xml_add(reply, F_TYPE, T_CIB); + crm_xml_add(reply, F_CIB_OPERATION, + crm_element_value(op_request, F_CIB_OPERATION)); + crm_xml_add(reply, F_CIB_CALLID, + crm_element_value(op_request, F_CIB_CALLID)); + crm_xml_add(reply, F_CIB_CLIENTID, + crm_element_value(op_request, F_CIB_CLIENTID)); + crm_xml_add_int(reply, F_CIB_CALLOPTS, flags); + crm_xml_add_int(reply, F_CIB_RC, pcmk_ok); + + if (cib != NULL) { + crm_trace("Attaching reply output"); + add_message_xml(reply, F_CIB_CALLDATA, cib); + } + + crm_ipcs_send(cib_client, cib_client->request_id, reply, + (flags & cib_sync_call) ? crm_ipc_flags_none + : crm_ipc_server_event); + + free_xml(reply); + free_xml(cib); +} + +/* see based/based_callbacks.c:cib_common_callback_worker */ +static void +mock_based_common_callback_worker(uint32_t id, uint32_t flags, + xmlNode *op_request, crm_client_t *cib_client) +{ + const char *op = crm_element_value(op_request, F_CIB_OPERATION); + mock_based_context_t *ctxt; + + if (!strcmp(op, CRM_OP_REGISTER)) { + if (flags & crm_ipc_client_response) { + xmlNode *ack = create_xml_node(NULL, __FUNCTION__); + crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); + crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); + crm_ipcs_send(cib_client, id, ack, flags); + cib_client->request_id = 0; + free_xml(ack); + } + + } else if (!strcmp(op, T_CIB_NOTIFY)) { + int on_off = 0; + const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE); + crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off); + + crm_debug("Setting %s callbacks for %s (%s): %s", + type, cib_client->name, cib_client->id, on_off ? "on" : "off"); + + if (!strcmp(type, T_CIB_DIFF_NOTIFY) && on_off) { + cib_client->options |= cib_notify_diff; + } + + ctxt = (mock_based_context_t *) cib_client->userdata; + for (size_t c = ctxt->modules_cnt; c > 0; c--) { + if (ctxt->modules[c - 1]->hooks.cib_notify != NULL) { + ctxt->modules[c - 1]->hooks.cib_notify(cib_client); + } + } + + if (flags & crm_ipc_client_response) { + crm_ipcs_send_ack(cib_client, id, flags, "ack", __FUNCTION__, __LINE__); + } + + } else if (!strcmp(op, CIB_OP_QUERY)) { + mock_based_handle_query(cib_client, flags, op_request); + + } else { + crm_notice("Discarded request %s", op); + } +} + +/* see based/based_callbacks.c:cib_ipc_dispatch_rw */ +static int32_t +mock_based_dispatch_command(qb_ipcs_connection_t *c, void *data, size_t size) +{ + uint32_t id = 0, flags = 0; + int call_options = 0; + crm_client_t *cib_client = crm_client_get(c); + xmlNode *op_request = crm_ipcs_recv(cib_client, data, size, &id, &flags); + + crm_notice("Got connection %p", c); + assert(op_request != NULL); + + if (cib_client == NULL || op_request == NULL) { + if (op_request == NULL) { + crm_trace("Invalid message from %p", c); + crm_ipcs_send_ack(cib_client, id, flags, "nack", __FUNCTION__, __LINE__); + } + return 0; + } + + crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); + if (call_options & cib_sync_call) { + assert(flags & crm_ipc_client_response); + cib_client->request_id = id; /* reply only to last in-flight request */ + } + + assert(cib_client->name == NULL); + crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); + crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); + crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); + + mock_based_common_callback_worker(id, flags, op_request, cib_client); + free_xml(op_request); + + return 0; +} + +/* * */ + +size_t mock_based_register_module(module_t mod) { + module_t *module; + size_t ret = mock_based_context.modules_cnt++; + + mock_based_context.modules = realloc(mock_based_context.modules, + sizeof(*mock_based_context.modules) + * mock_based_context.modules_cnt); + if (mock_based_context.modules == NULL + || (module = malloc(sizeof(module_t))) == NULL) { + abort(); + } + + memcpy(module, &mod, sizeof(mod)); + mock_based_context.modules[mock_based_context.modules_cnt - 1] = module; + + return ret; +} + +static int +mock_based_options(mock_based_context_t *ctxt, + bool usage, int argc, const char *argv[]) +{ + const char **args2argv; + char *s; + int ret = 0; + + if (argc <= 1) { + const char *help_argv[] = {argv[0], "-h"}; + return mock_based_options(ctxt, false, 2, (const char **) &help_argv); + } + + for (size_t i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] != '-' && argv[i][1] != '\0') { + if (usage) { + printf("\t-%c\t", argv[i][1]); + } + switch(argv[i][1]) { + case 'h': + if (usage) { + printf("show this help message\n"); + ret = 1; + + } else { + if ((args2argv + = malloc((ctxt->modules_cnt + 2) * sizeof(*args2argv))) == NULL + || (s + = malloc((ctxt->modules_cnt * 2 + 2) * sizeof(*s))) == NULL) { + return -1; + } + s[0] = 'h'; + args2argv[ctxt->modules_cnt + 1] = (char[]){'-', 'h', '\0'}; + for (size_t c = ctxt->modules_cnt; c > 0; c--) { + args2argv[c] = (char[]){'-', ctxt->modules[c - 1]->shortopt, '\0'}; + s[(ctxt->modules_cnt - i) + 1] = '|'; + s[(ctxt->modules_cnt - i) + 2] = ctxt->modules[c - 1]->shortopt; + } + s[ctxt->modules_cnt * 2 + 1] = '\0'; + printf("Usage: %s [-{%s}]\n", argv[0], s); + (void) mock_based_options(ctxt, true, 2 + ctxt->modules_cnt, args2argv); + free(args2argv); + free(s); + } + return ret; + default: + for (size_t c = ctxt->modules_cnt; c > 0; c--) { + if (ctxt->modules[c - 1]->shortopt == argv[i][1]) { + ret = ctxt->modules[c - 1]->hooks.argparse(ctxt, usage, argc - i, &argv[i]); + if (ret < 0) { + break; + } else if (ret > 1) { + i += (ret - 1); + } + } + } + if (ret == 0) { + printf("uknown option \"%s\"\n", argv[i]); + } + break; + } + } + } + return ret; +} + +int main(int argc, char *argv[]) +{ + mock_based_context_t *ctxt = &mock_based_context; + + if (mock_based_options(ctxt, false, argc, (const char **) argv) > 0) { + struct qb_ipcs_service_handlers cib_ipc_callbacks = { + .connection_accept = mock_based_ipc_accept, + .connection_created = mock_based_ipc_created, + .msg_process = mock_based_dispatch_command, + .connection_closed = mock_based_ipc_closed, + .connection_destroyed = mock_based_ipc_destroy, + }; + crm_log_preinit(NULL, argc, argv); + crm_log_init(NULL, LOG_DEBUG, false, true, argc, argv, false); + qb_ipcs_service_t *ipcs_command = + mainloop_add_ipc_server(CIB_CHANNEL_RW, QB_IPC_NATIVE, + &cib_ipc_callbacks); + g_main_loop_run(g_main_loop_new(NULL, false)); + qb_ipcs_destroy(ipcs_command); + } + + for (size_t c = ctxt->modules_cnt; c > 0; c--) { + if (ctxt->modules[c - 1]->hooks.destroy != NULL) { + ctxt->modules[c - 1]->hooks.destroy(ctxt->modules[c - 1]); + } + free(mock_based_context.modules[c - 1]); + } + + free(mock_based_context.modules); +} diff --git a/maint/mocked/based.h b/maint/mocked/based.h new file mode 100644 index 0000000000..dcebf0e038 --- /dev/null +++ b/maint/mocked/based.h @@ -0,0 +1,49 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * Licensed under the GNU General Public License version 2 or later (GPLv2+). + */ + +#pragma once + +#include /* size_t */ +#include /* bool */ + +#include /* crm_client_t */ + + +struct module_s; + +typedef struct mock_based_context_s { + size_t modules_cnt; + struct module_s** modules; +} mock_based_context_t; + + +typedef int (*mock_based_argparse_hook)(mock_based_context_t *, + bool, int, + const char *[]); + +typedef void (*mock_based_destroy_hook)(struct module_s *); + +/* specialized callbacks... */ +typedef void (*mock_based_cib_notify_hook)(crm_client_t *); + +typedef struct mock_based_hooks_s { + /* generic ones */ + mock_based_argparse_hook argparse; + mock_based_destroy_hook destroy; + + /* specialized callbacks... */ + mock_based_cib_notify_hook cib_notify; +} mock_based_hooks_t; + +typedef struct module_s { + char shortopt; + mock_based_hooks_t hooks; + void *priv; +} module_t; + +size_t mock_based_register_module(module_t mod);