Blob Blame History Raw
From ea78f9a90e35be15482129e1bdb9c6d86a9a5015 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Thu, 29 Oct 2020 13:41:41 +0100
Subject: [PATCH 1/2] Refactor: crmadmin: prepare functions to move to
 libpacemaker

---
 tools/crmadmin.c | 321 +++++++++++++++++++++++++++++++++----------------------
 1 file changed, 196 insertions(+), 125 deletions(-)

diff --git a/tools/crmadmin.c b/tools/crmadmin.c
index e61dbf4..ec902df 100644
--- a/tools/crmadmin.c
+++ b/tools/crmadmin.c
@@ -36,7 +36,6 @@ static guint message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS;
 static GMainLoop *mainloop = NULL;
 
 bool need_controld_api = true;
-bool need_pacemakerd_api = false;
 
 bool do_work(pcmk_ipc_api_t *api);
 static char *ipc_name = NULL;
@@ -135,8 +134,6 @@ command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError
 
     if (!strcmp(option_name, "--pacemakerd") || !strcmp(option_name, "-P")) {
         command = cmd_pacemakerd_health;
-        need_pacemakerd_api = true;
-        need_controld_api = false;
     }
 
     if (!strcmp(option_name, "--dc_lookup") || !strcmp(option_name, "-D")) {
@@ -145,7 +142,6 @@ command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError
 
     if (!strcmp(option_name, "--nodes") || !strcmp(option_name, "-N")) {
         command = cmd_list_nodes;
-        need_controld_api = false;
     }
 
     if (!strcmp(option_name, "--election") || !strcmp(option_name, "-E")) {
@@ -353,6 +349,16 @@ static pcmk__supported_format_t formats[] = {
 };
 
 static void
+start_main_loop()
+{
+        exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
+        mainloop = g_main_loop_new(NULL, FALSE);
+        message_timer_id = g_timeout_add(message_timeout_ms,
+                                         admin_message_timeout, NULL);
+        g_main_loop_run(mainloop);
+}
+
+static void
 quit_main_loop(crm_exit_t ec)
 {
     exit_code = ec;
@@ -366,9 +372,14 @@ quit_main_loop(crm_exit_t ec)
 }
 
 static void
-controller_event_cb(pcmk_ipc_api_t *controld_api,
-                    enum pcmk_ipc_event event_type, crm_exit_t status,
-                    void *event_data, void *user_data)
+event_done(pcmk_ipc_api_t *api)
+{
+    pcmk_disconnect_ipc(api);
+    quit_main_loop(exit_code);
+}
+
+static pcmk_controld_api_reply_t *
+controld_event_reply(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data)
 {
     pcmk_controld_api_reply_t *reply = event_data;
 
@@ -377,14 +388,14 @@ controller_event_cb(pcmk_ipc_api_t *controld_api,
             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
                 out->err(out, "error: Lost connection to controller");
             }
-            goto done;
-            break;
+            event_done(controld_api);
+            return NULL;
 
         case pcmk_ipc_event_reply:
             break;
 
         default:
-            return;
+            return NULL;
     }
 
     if (message_timer_id != 0) {
@@ -396,39 +407,54 @@ controller_event_cb(pcmk_ipc_api_t *controld_api,
         out->err(out, "error: Bad reply from controller: %s",
                 crm_exit_str(status));
         exit_code = status;
-        goto done;
+        event_done(controld_api);
+        return NULL;
     }
 
     if (reply->reply_type != pcmk_controld_reply_ping) {
         out->err(out, "error: Unknown reply type %d from controller",
                 reply->reply_type);
-        goto done;
+        event_done(controld_api);
+        return NULL;
     }
 
-    // Parse desired information from reply
-    switch (command) {
-        case cmd_health:
-            out->message(out, "health",
-                   reply->data.ping.sys_from,
-                   reply->host_from,
-                   reply->data.ping.fsa_state,
-                   reply->data.ping.result);
-            exit_code = CRM_EX_OK;
-            break;
+    return reply;
+}
 
-        case cmd_whois_dc:
-            out->message(out, "dc", reply->host_from);
-            exit_code = CRM_EX_OK;
-            break;
+static void
+controller_status_event_cb(pcmk_ipc_api_t *controld_api,
+                    enum pcmk_ipc_event event_type, crm_exit_t status,
+                    void *event_data, void *user_data)
+{
+    pcmk_controld_api_reply_t *reply = controld_event_reply(controld_api,
+        event_type, status, event_data);
 
-        default: // Not really possible here
-            exit_code = CRM_EX_SOFTWARE;
-            break;
+    if (reply != NULL) {
+        out->message(out, "health",
+               reply->data.ping.sys_from,
+               reply->host_from,
+               reply->data.ping.fsa_state,
+               reply->data.ping.result);
+        exit_code = CRM_EX_OK;
     }
 
-done:
-    pcmk_disconnect_ipc(controld_api);
-    quit_main_loop(exit_code);
+    event_done(controld_api);
+}
+
+static void
+designated_controller_event_cb(pcmk_ipc_api_t *controld_api,
+                    enum pcmk_ipc_event event_type, crm_exit_t status,
+                    void *event_data, void *user_data)
+{
+    pcmk_controld_api_reply_t *reply = controld_event_reply(controld_api,
+        event_type, status, event_data);
+
+    if (reply != NULL) {
+        out->message(out, "dc", reply->host_from);
+        exit_code = CRM_EX_OK;
+    }
+
+    event_done(controld_api);
 }
 
 static void
@@ -438,13 +464,16 @@ pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
 {
     pcmk_pacemakerd_api_reply_t *reply = event_data;
 
+    crm_time_t *crm_when = crm_time_new(NULL);
+    char *pinged_buf = NULL;
+
     switch (event_type) {
         case pcmk_ipc_event_disconnect:
             if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
                 out->err(out, "error: Lost connection to pacemakerd");
             }
-            goto done;
-            break;
+            event_done(pacemakerd_api);
+            return;
 
         case pcmk_ipc_event_reply:
             break;
@@ -461,52 +490,119 @@ pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
     if (status != CRM_EX_OK) {
         out->err(out, "error: Bad reply from pacemakerd: %s",
                 crm_exit_str(status));
-        exit_code = status;
-        goto done;
+        event_done(pacemakerd_api);
+        return;
     }
 
     if (reply->reply_type != pcmk_pacemakerd_reply_ping) {
         out->err(out, "error: Unknown reply type %d from pacemakerd",
                 reply->reply_type);
-        goto done;
+        event_done(pacemakerd_api);
+        return;
     }
 
     // Parse desired information from reply
-    switch (command) {
-        case cmd_pacemakerd_health:
-            {
-                crm_time_t *crm_when = crm_time_new(NULL);
-                char *pinged_buf = NULL;
-
-                crm_time_set_timet(crm_when, &reply->data.ping.last_good);
-                pinged_buf = crm_time_as_string(crm_when,
-                    crm_time_log_date | crm_time_log_timeofday |
-                        crm_time_log_with_timezone);
-
-                out->message(out, "pacemakerd-health",
-                    reply->data.ping.sys_from,
-                    (reply->data.ping.status == pcmk_rc_ok)?
-                        pcmk_pacemakerd_api_daemon_state_enum2text(
-                            reply->data.ping.state):"query failed",
-                    (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:"");
-                exit_code = CRM_EX_OK;
-                free(pinged_buf);
-            }
-            break;
+    crm_time_set_timet(crm_when, &reply->data.ping.last_good);
+    pinged_buf = crm_time_as_string(crm_when,
+        crm_time_log_date | crm_time_log_timeofday |
+            crm_time_log_with_timezone);
+
+    out->message(out, "pacemakerd-health",
+        reply->data.ping.sys_from,
+        (reply->data.ping.status == pcmk_rc_ok)?
+            pcmk_pacemakerd_api_daemon_state_enum2text(
+                reply->data.ping.state):"query failed",
+        (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:"");
+    exit_code = CRM_EX_OK;
+    free(pinged_buf);
+
+    event_done(pacemakerd_api);
+}
 
-        default: // Not really possible here
-            exit_code = CRM_EX_SOFTWARE;
-            break;
+static pcmk_ipc_api_t *
+ipc_connect(enum pcmk_ipc_server server, pcmk_ipc_callback_t cb)
+{
+    int rc;
+    pcmk_ipc_api_t *api = NULL;
+
+    rc = pcmk_new_ipc_api(&api, server);
+    if (api == NULL) {
+        out->err(out, "error: Could not connect to %s: %s",
+                (server == pcmk_ipc_controld) ? "controller" : "pacemakerd",
+                pcmk_rc_str(rc));
+        exit_code = pcmk_rc2exitc(rc);
+        return NULL;
+    }
+    pcmk_register_ipc_callback(api, cb, NULL);
+    rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main);
+    if (rc != pcmk_rc_ok) {
+        out->err(out, "error: Could not connect to %s: %s",
+                (server == pcmk_ipc_controld) ? "controller" : "pacemakerd",
+                pcmk_rc_str(rc));
+        exit_code = pcmk_rc2exitc(rc);
+        return NULL;
     }
 
-done:
-    pcmk_disconnect_ipc(pacemakerd_api);
-    quit_main_loop(exit_code);
+    return api;
+}
+
+static void
+pcmk__controller_status()
+{
+    pcmk_ipc_api_t *controld_api = ipc_connect(pcmk_ipc_controld, controller_status_event_cb);
+
+    if (controld_api != NULL) {
+        int rc = pcmk_controld_api_ping(controld_api, dest_node);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            exit_code = pcmk_rc2exitc(rc);
+        }
+
+        start_main_loop();
+
+        pcmk_free_ipc_api(controld_api);
+    }
+}
+
+static void
+pcmk__designated_controller()
+{
+    pcmk_ipc_api_t *controld_api = ipc_connect(pcmk_ipc_controld, designated_controller_event_cb);
+
+    if (controld_api != NULL) {
+        int rc = pcmk_controld_api_ping(controld_api, dest_node);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            exit_code = pcmk_rc2exitc(rc);
+        }
+
+        start_main_loop();
+
+        pcmk_free_ipc_api(controld_api);
+    }
+}
+
+static void
+pcmk__pacemakerd_status()
+{
+    pcmk_ipc_api_t *pacemakerd_api = ipc_connect(pcmk_ipc_pacemakerd, pacemakerd_event_cb);
+
+    if (pacemakerd_api != NULL) {
+        int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            exit_code = pcmk_rc2exitc(rc);
+        }
+
+        start_main_loop();
+
+        pcmk_free_ipc_api(pacemakerd_api);
+    }
 }
 
 // \return Standard Pacemaker return code
 static int
-list_nodes()
+pcmk__list_nodes()
 {
     cib_t *the_cib = cib_new();
     xmlNode *output = NULL;
@@ -565,7 +661,6 @@ main(int argc, char **argv)
     int argerr = 0;
     int rc;
     pcmk_ipc_api_t *controld_api = NULL;
-    pcmk_ipc_api_t *pacemakerd_api = NULL;
 
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 
@@ -643,51 +738,45 @@ main(int argc, char **argv)
         goto done;
     }
 
-    // Connect to the controller if needed
-    if (need_controld_api) {
-        rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
-        if (controld_api == NULL) {
-            out->err(out, "error: Could not connect to controller: %s",
-                    pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
-            goto done;
-        }
-        pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL);
-        rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
-        if (rc != pcmk_rc_ok) {
-            out->err(out, "error: Could not connect to controller: %s",
-                    pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
+    switch (command) {
+        case cmd_health:
+            pcmk__controller_status();
             goto done;
-        }
-    }
-
-    // Connect to pacemakerd if needed
-    if (need_pacemakerd_api) {
-        rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd);
-        if (pacemakerd_api == NULL) {
-            out->err(out, "error: Could not connect to pacemakerd: %s",
-                    pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
+        case cmd_pacemakerd_health:
+            pcmk__pacemakerd_status();
             goto done;
-        }
-        pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, NULL);
-        rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_main);
-        if (rc != pcmk_rc_ok) {
-            out->err(out, "error: Could not connect to pacemakerd: %s",
-                    pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
+        case cmd_list_nodes:
+            rc = pcmk__list_nodes();
+            // might need movink
+            if (rc != pcmk_rc_ok) {
+                out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+                exit_code = pcmk_rc2exitc(rc);
+            }
+            break;
+        case cmd_whois_dc:
+            pcmk__designated_controller();
             goto done;
-        }
+        default:
+            rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
+            if (controld_api == NULL) {
+                out->err(out, "error: Could not connect to controller: %s",
+                        pcmk_rc_str(rc));
+                exit_code = pcmk_rc2exitc(rc);
+                goto done;
+            }
+            rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
+            if (rc != pcmk_rc_ok) {
+                out->err(out, "error: Could not connect to controller: %s",
+                        pcmk_rc_str(rc));
+                exit_code = pcmk_rc2exitc(rc);
+                goto done;
+            }
+            break;
     }
 
-    if (do_work(controld_api?controld_api:pacemakerd_api)) {
+    if (do_work(controld_api?controld_api:NULL)) {
         // A reply is needed from controller, so run main loop to get it
-        exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
-        mainloop = g_main_loop_new(NULL, FALSE);
-        message_timer_id = g_timeout_add(message_timeout_ms,
-                                         admin_message_timeout, NULL);
-        g_main_loop_run(mainloop);
+        start_main_loop();
     }
 
 done:
@@ -698,12 +787,6 @@ done:
         pcmk_free_ipc_api(capi);
     }
 
-    if (pacemakerd_api != NULL) {
-        pcmk_ipc_api_t *capi = pacemakerd_api;
-        pacemakerd_api = NULL; // Ensure we can't free this twice
-        pcmk_free_ipc_api(capi);
-    }
-
     if (mainloop != NULL) {
         g_main_loop_unref(mainloop);
         mainloop = NULL;
@@ -731,26 +814,14 @@ do_work(pcmk_ipc_api_t *api)
             rc = pcmk_controld_api_shutdown(api, dest_node);
             break;
 
-        case cmd_health:    // dest_node != NULL
-        case cmd_whois_dc:  // dest_node == NULL
-            rc = pcmk_controld_api_ping(api, dest_node);
-            need_reply = true;
-            break;
-
         case cmd_elect_dc:
             rc = pcmk_controld_api_start_election(api);
             break;
 
-        case cmd_list_nodes:
-            rc = list_nodes();
-            break;
-
-        case cmd_pacemakerd_health:
-            rc = pcmk_pacemakerd_api_ping(api, ipc_name);
-            need_reply = true;
+        case cmd_none: // not actually possible here
             break;
 
-        case cmd_none: // not actually possible here
+        default:
             break;
     }
     if (rc != pcmk_rc_ok) {
-- 
1.8.3.1


From ac241aec979938175103624f07c8e90c6b550c48 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Fri, 30 Oct 2020 13:09:48 +0100
Subject: [PATCH 2/2] Refactor: crmadmin: move guts to libpacemaker

---
 include/pacemaker-internal.h          |   1 +
 include/pcmki/Makefile.am             |   1 +
 include/pcmki/pcmki_cluster_queries.h |  15 +
 lib/pacemaker/Makefile.am             |   1 +
 lib/pacemaker/pcmk_cluster_queries.c  | 408 +++++++++++++++++++++++
 lib/pacemaker/pcmk_output.c           | 177 ++++++++++
 tools/Makefile.am                     |   3 +-
 tools/crmadmin.c                      | 612 ++--------------------------------
 8 files changed, 642 insertions(+), 576 deletions(-)
 create mode 100644 include/pcmki/pcmki_cluster_queries.h
 create mode 100644 lib/pacemaker/pcmk_cluster_queries.c

diff --git a/include/pacemaker-internal.h b/include/pacemaker-internal.h
index 37399e7..2e75d09 100644
--- a/include/pacemaker-internal.h
+++ b/include/pacemaker-internal.h
@@ -11,6 +11,7 @@
 #  define PACEMAKER_INTERNAL__H
 
 #  include <pcmki/pcmki_error.h>
+#  include <pcmki/pcmki_cluster_queries.h>
 #  include <pcmki/pcmki_fence.h>
 #  include <pcmki/pcmki_output.h>
 #  include <pcmki/pcmki_sched_allocate.h>
diff --git a/include/pcmki/Makefile.am b/include/pcmki/Makefile.am
index 647f2dc..7aa64c7 100644
--- a/include/pcmki/Makefile.am
+++ b/include/pcmki/Makefile.am
@@ -10,6 +10,7 @@
 MAINTAINERCLEANFILES    = Makefile.in
 
 noinst_HEADERS		= pcmki_error.h \
+			  pcmki_cluster_queries.h \
 			  pcmki_fence.h \
 			  pcmki_output.h \
 			  pcmki_sched_allocate.h \
diff --git a/include/pcmki/pcmki_cluster_queries.h b/include/pcmki/pcmki_cluster_queries.h
new file mode 100644
index 0000000..eb3b51c
--- /dev/null
+++ b/include/pcmki/pcmki_cluster_queries.h
@@ -0,0 +1,15 @@
+#include <glib.h>               // gboolean, GMainLoop, etc.
+
+#include <crm/crm.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/ipc_controld.h>
+#include <crm/common/ipc_pacemakerd.h>
+
+int pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms);
+int pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms);
+int pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms);
+int pcmk__list_nodes(pcmk__output_t *out, gboolean BASH_EXPORT);
+
+// remove when parameters removed from tools/crmadmin.c
+int pcmk__shutdown_controller(pcmk__output_t *out, char *dest_node);
+int pcmk__start_election(pcmk__output_t *out);
diff --git a/lib/pacemaker/Makefile.am b/lib/pacemaker/Makefile.am
index 51a811a..4129ade 100644
--- a/lib/pacemaker/Makefile.am
+++ b/lib/pacemaker/Makefile.am
@@ -28,6 +28,7 @@ libpacemaker_la_LIBADD	= $(top_builddir)/lib/pengine/libpe_status.la \
 # -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version
 # Use += rather than backlashed continuation lines for parsing by bumplibs.sh
 libpacemaker_la_SOURCES	=
+libpacemaker_la_SOURCES += pcmk_cluster_queries.c
 libpacemaker_la_SOURCES += pcmk_fence.c
 libpacemaker_la_SOURCES += pcmk_output.c
 libpacemaker_la_SOURCES	+= pcmk_sched_allocate.c
diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c
new file mode 100644
index 0000000..8d729eb
--- /dev/null
+++ b/lib/pacemaker/pcmk_cluster_queries.c
@@ -0,0 +1,408 @@
+#include <glib.h>               // gboolean, GMainLoop, etc.
+#include <libxml/tree.h>        // xmlNode
+
+#include <pacemaker-internal.h>
+
+#include <crm/crm.h>
+#include <crm/cib.h>
+#include <crm/msg_xml.h>
+#include <crm/common/output_internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/iso8601.h>
+#include <crm/common/ipc_controld.h>
+#include <crm/common/ipc_pacemakerd.h>
+#include <crm/common/mainloop.h>
+
+#define DEFAULT_MESSAGE_TIMEOUT_MS 30000
+
+
+typedef struct {
+    pcmk__output_t *out;
+    GMainLoop *mainloop;
+    int rc;
+    guint message_timer_id;
+    guint message_timeout_ms;
+} data_t;
+
+static void
+quit_main_loop(data_t *data)
+{
+    if (data->mainloop != NULL) {
+        GMainLoop *mloop = data->mainloop;
+
+        data->mainloop = NULL; // Don't re-enter this block
+        pcmk_quit_main_loop(mloop, 10);
+        g_main_loop_unref(mloop);
+    }
+}
+
+static gboolean
+admin_message_timeout(gpointer user_data)
+{
+    data_t *data = user_data;
+    pcmk__output_t *out = data->out;
+
+    out->err(out, "error: No reply received from controller before timeout (%dms)",
+            data->message_timeout_ms);
+    data->message_timer_id = 0;
+    data->rc = ETIMEDOUT;
+    quit_main_loop(data);
+    return FALSE; // Tells glib to remove source
+}
+
+static void
+start_main_loop(data_t *data)
+{
+    if (data->message_timeout_ms < 1) {
+        data->message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS;
+    }
+
+    data->rc = ECONNRESET; // For unexpected disconnects
+    data->mainloop = g_main_loop_new(NULL, FALSE);
+    data->message_timer_id = g_timeout_add(data->message_timeout_ms,
+                                     admin_message_timeout,
+                                     data);
+    g_main_loop_run(data->mainloop);
+}
+
+static void
+event_done(data_t *data, pcmk_ipc_api_t *api)
+{
+    pcmk_disconnect_ipc(api);
+    quit_main_loop(data);
+}
+
+static pcmk_controld_api_reply_t *
+controld_event_reply(data_t *data, pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data)
+{
+    pcmk__output_t *out = data->out;
+    pcmk_controld_api_reply_t *reply = event_data;
+
+    switch (event_type) {
+        case pcmk_ipc_event_disconnect:
+            if (data->rc == ECONNRESET) { // Unexpected
+                out->err(out, "error: Lost connection to controller");
+            }
+            event_done(data, controld_api);
+            return NULL;
+
+        case pcmk_ipc_event_reply:
+            break;
+
+        default:
+            return NULL;
+    }
+
+    if (data->message_timer_id != 0) {
+        g_source_remove(data->message_timer_id);
+        data->message_timer_id = 0;
+    }
+
+    if (status != CRM_EX_OK) {
+        out->err(out, "error: Bad reply from controller: %s",
+                crm_exit_str(status));
+        data->rc = EBADMSG;
+        event_done(data, controld_api);
+        return NULL;
+    }
+
+    if (reply->reply_type != pcmk_controld_reply_ping) {
+        out->err(out, "error: Unknown reply type %d from controller",
+                reply->reply_type);
+        data->rc = EBADMSG;
+        event_done(data, controld_api);
+        return NULL;
+    }
+
+    return reply;
+}
+
+static void
+controller_status_event_cb(pcmk_ipc_api_t *controld_api,
+                    enum pcmk_ipc_event event_type, crm_exit_t status,
+                    void *event_data, void *user_data)
+{
+    data_t *data = user_data;
+    pcmk__output_t *out = data->out;
+    pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api,
+        event_type, status, event_data);
+
+    if (reply != NULL) {
+        out->message(out, "health",
+               reply->data.ping.sys_from,
+               reply->host_from,
+               reply->data.ping.fsa_state,
+               reply->data.ping.result);
+        data->rc = pcmk_rc_ok;
+    }
+
+    event_done(data, controld_api);
+}
+
+static void
+designated_controller_event_cb(pcmk_ipc_api_t *controld_api,
+                    enum pcmk_ipc_event event_type, crm_exit_t status,
+                    void *event_data, void *user_data)
+{
+    data_t *data = user_data;
+    pcmk__output_t *out = data->out;
+    pcmk_controld_api_reply_t *reply = controld_event_reply(data, controld_api,
+        event_type, status, event_data);
+
+    if (reply != NULL) {
+        out->message(out, "dc", reply->host_from);
+        data->rc = pcmk_rc_ok;
+    }
+
+    event_done(data, controld_api);
+}
+
+static void
+pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
+                    enum pcmk_ipc_event event_type, crm_exit_t status,
+                    void *event_data, void *user_data)
+{
+    data_t *data = user_data;
+    pcmk__output_t *out = data->out;
+    pcmk_pacemakerd_api_reply_t *reply = event_data;
+
+    crm_time_t *crm_when;
+    char *pinged_buf = NULL;
+
+    switch (event_type) {
+        case pcmk_ipc_event_disconnect:
+            if (data->rc == ECONNRESET) { // Unexpected
+                out->err(out, "error: Lost connection to pacemakerd");
+            }
+            event_done(data, pacemakerd_api);
+            return;
+
+        case pcmk_ipc_event_reply:
+            break;
+
+        default:
+            return;
+    }
+
+    if (data->message_timer_id != 0) {
+        g_source_remove(data->message_timer_id);
+        data->message_timer_id = 0;
+    }
+
+    if (status != CRM_EX_OK) {
+        out->err(out, "error: Bad reply from pacemakerd: %s",
+                crm_exit_str(status));
+        event_done(data, pacemakerd_api);
+        return;
+    }
+
+    if (reply->reply_type != pcmk_pacemakerd_reply_ping) {
+        out->err(out, "error: Unknown reply type %d from pacemakerd",
+                reply->reply_type);
+        event_done(data, pacemakerd_api);
+        return;
+    }
+
+    // Parse desired information from reply
+    crm_when = crm_time_new(NULL);
+    crm_time_set_timet(crm_when, &reply->data.ping.last_good);
+    pinged_buf = crm_time_as_string(crm_when,
+        crm_time_log_date | crm_time_log_timeofday |
+            crm_time_log_with_timezone);
+
+    out->message(out, "pacemakerd-health",
+        reply->data.ping.sys_from,
+        (reply->data.ping.status == pcmk_rc_ok)?
+            pcmk_pacemakerd_api_daemon_state_enum2text(
+                reply->data.ping.state):"query failed",
+        (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:"");
+    data->rc = pcmk_rc_ok;
+    crm_time_free(crm_when);
+    free(pinged_buf);
+
+    event_done(data, pacemakerd_api);
+}
+
+static pcmk_ipc_api_t *
+ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb)
+{
+    int rc;
+    pcmk__output_t *out = data->out;
+    pcmk_ipc_api_t *api = NULL;
+
+
+    rc = pcmk_new_ipc_api(&api, server);
+    if (api == NULL) {
+        out->err(out, "error: Could not connect to %s: %s",
+                pcmk_ipc_name(api, true),
+                pcmk_rc_str(rc));
+        data->rc = rc;
+        return NULL;
+    }
+    if (cb != NULL) {
+        pcmk_register_ipc_callback(api, cb, data);
+    }
+    rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main);
+    if (rc != pcmk_rc_ok) {
+        out->err(out, "error: Could not connect to %s: %s",
+                pcmk_ipc_name(api, true),
+                pcmk_rc_str(rc));
+        data->rc = rc;
+        return NULL;
+    }
+
+    return api;
+}
+
+int
+pcmk__controller_status(pcmk__output_t *out, char *dest_node, guint message_timeout_ms)
+{
+    data_t data = {
+        .out = out,
+        .mainloop = NULL,
+        .rc = pcmk_rc_ok,
+        .message_timer_id = 0,
+        .message_timeout_ms = message_timeout_ms
+    };
+    pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb);
+
+    if (controld_api != NULL) {
+        int rc = pcmk_controld_api_ping(controld_api, dest_node);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            data.rc = rc;
+        }
+
+        start_main_loop(&data);
+
+        pcmk_free_ipc_api(controld_api);
+    }
+
+    return data.rc;
+}
+
+int
+pcmk__designated_controller(pcmk__output_t *out, guint message_timeout_ms)
+{
+    data_t data = {
+        .out = out,
+        .mainloop = NULL,
+        .rc = pcmk_rc_ok,
+        .message_timer_id = 0,
+        .message_timeout_ms = message_timeout_ms
+    };
+    pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb);
+
+    if (controld_api != NULL) {
+        int rc = pcmk_controld_api_ping(controld_api, NULL);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            data.rc = rc;
+        }
+
+        start_main_loop(&data);
+
+        pcmk_free_ipc_api(controld_api);
+    }
+
+    return data.rc;
+}
+
+int
+pcmk__pacemakerd_status(pcmk__output_t *out, char *ipc_name, guint message_timeout_ms)
+{
+    data_t data = {
+        .out = out,
+        .mainloop = NULL,
+        .rc = pcmk_rc_ok,
+        .message_timer_id = 0,
+        .message_timeout_ms = message_timeout_ms
+    };
+    pcmk_ipc_api_t *pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb);
+
+    if (pacemakerd_api != NULL) {
+        int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            data.rc = rc;
+        }
+
+        start_main_loop(&data);
+
+        pcmk_free_ipc_api(pacemakerd_api);
+    }
+
+    return data.rc;
+}
+
+// \return Standard Pacemaker return code
+int
+pcmk__list_nodes(pcmk__output_t *out, gboolean BASH_EXPORT)
+{
+    cib_t *the_cib = cib_new();
+    xmlNode *output = NULL;
+    int rc;
+
+    if (the_cib == NULL) {
+        return ENOMEM;
+    }
+    rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
+    if (rc != pcmk_ok) {
+        return pcmk_legacy2rc(rc);
+    }
+
+    rc = the_cib->cmds->query(the_cib, NULL, &output,
+                              cib_scope_local | cib_sync_call);
+    if (rc == pcmk_ok) {
+        out->message(out, "crmadmin-node-list", output, BASH_EXPORT);
+        free_xml(output);
+    }
+    the_cib->cmds->signoff(the_cib);
+    return pcmk_legacy2rc(rc);
+}
+
+// remove when parameters removed from tools/crmadmin.c
+int
+pcmk__shutdown_controller(pcmk__output_t *out, char *dest_node)
+{
+    data_t data = {
+        .out = out,
+        .mainloop = NULL,
+        .rc = pcmk_rc_ok,
+    };
+    pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, NULL);
+
+    if (controld_api != NULL) {
+        int rc = pcmk_controld_api_shutdown(controld_api, dest_node);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            data.rc = rc;
+        }
+        pcmk_free_ipc_api(controld_api);
+    }
+
+    return data.rc;
+}
+
+int
+pcmk__start_election(pcmk__output_t *out)
+{
+    data_t data = {
+        .out = out,
+        .mainloop = NULL,
+        .rc = pcmk_rc_ok,
+    };
+    pcmk_ipc_api_t *controld_api = ipc_connect(&data, pcmk_ipc_controld, NULL);
+
+    if (controld_api != NULL) {
+        int rc = pcmk_controld_api_start_election(controld_api);
+        if (rc != pcmk_rc_ok) {
+            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+            data.rc = rc;
+        }
+
+        pcmk_free_ipc_api(controld_api);
+    }
+
+    return data.rc;
+}
diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c
index 306e561..fd577c6 100644
--- a/lib/pacemaker/pcmk_output.c
+++ b/lib/pacemaker/pcmk_output.c
@@ -357,6 +357,174 @@ stacks_and_constraints_xml(pcmk__output_t *out, va_list args) {
     return pcmk_rc_ok;
 }
 
+PCMK__OUTPUT_ARGS("health", "char *", "char *", "char *", "char *")
+static int
+health_text(pcmk__output_t *out, va_list args)
+{
+    char *sys_from = va_arg(args, char *);
+    char *host_from = va_arg(args, char *);
+    char *fsa_state = va_arg(args, char *);
+    char *result = va_arg(args, char *);
+
+    if (!out->is_quiet(out)) {
+        out->info(out, "Status of %s@%s: %s (%s)", crm_str(sys_from),
+                       crm_str(host_from), crm_str(fsa_state), crm_str(result));
+    } else if (fsa_state != NULL) {
+        out->info(out, "%s", fsa_state);
+    }
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("health", "char *", "char *", "char *", "char *")
+static int
+health_xml(pcmk__output_t *out, va_list args)
+{
+    char *sys_from = va_arg(args, char *);
+    char *host_from = va_arg(args, char *);
+    char *fsa_state = va_arg(args, char *);
+    char *result = va_arg(args, char *);
+
+    xmlNodePtr node = pcmk__output_create_xml_node(out, crm_str(sys_from));
+    xmlSetProp(node, (pcmkXmlStr) "node_name", (pcmkXmlStr) crm_str(host_from));
+    xmlSetProp(node, (pcmkXmlStr) "state", (pcmkXmlStr) crm_str(fsa_state));
+    xmlSetProp(node, (pcmkXmlStr) "result", (pcmkXmlStr) crm_str(result));
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("pacemakerd-health", "char *", "char *", "char *")
+static int
+pacemakerd_health_text(pcmk__output_t *out, va_list args)
+{
+    char *sys_from = va_arg(args, char *);
+    char *state = va_arg(args, char *);
+    char *last_updated = va_arg(args, char *);
+
+    if (!out->is_quiet(out)) {
+        out->info(out, "Status of %s: '%s' %s %s", crm_str(sys_from),
+                  crm_str(state), (!pcmk__str_empty(last_updated))?
+                  "last updated":"", crm_str(last_updated));
+    } else {
+        out->info(out, "%s", crm_str(state));
+    }
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("pacemakerd-health", "char *", "char *", "char *")
+static int
+pacemakerd_health_xml(pcmk__output_t *out, va_list args)
+{
+    char *sys_from = va_arg(args, char *);
+    char *state = va_arg(args, char *);
+    char *last_updated = va_arg(args, char *);
+
+
+    xmlNodePtr node = pcmk__output_create_xml_node(out, crm_str(sys_from));
+    xmlSetProp(node, (pcmkXmlStr) "state", (pcmkXmlStr) crm_str(state));
+    xmlSetProp(node, (pcmkXmlStr) "last_updated", (pcmkXmlStr) crm_str(last_updated));
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("dc", "char *")
+static int
+dc_text(pcmk__output_t *out, va_list args)
+{
+    char *dc = va_arg(args, char *);
+
+    if (!out->is_quiet(out)) {
+        out->info(out, "Designated Controller is: %s", crm_str(dc));
+    } else if (dc != NULL) {
+        out->info(out, "%s", dc);
+    }
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("dc", "char *")
+static int
+dc_xml(pcmk__output_t *out, va_list args)
+{
+    char *dc = va_arg(args, char *);
+
+    xmlNodePtr node = pcmk__output_create_xml_node(out, "dc");
+    xmlSetProp(node, (pcmkXmlStr) "node_name", (pcmkXmlStr) crm_str(dc));
+
+    return pcmk_rc_ok;
+}
+
+
+PCMK__OUTPUT_ARGS("crmadmin-node-list", "pcmk__output_t *", "xmlNode *")
+static int
+crmadmin_node_list(pcmk__output_t *out, va_list args)
+{
+    xmlNode *xml_node = va_arg(args, xmlNode *);
+    int found = 0;
+    xmlNode *node = NULL;
+    xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node);
+    gboolean BASH_EXPORT = va_arg(args, gboolean);
+
+    out->begin_list(out, NULL, NULL, "nodes");
+
+    for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL;
+         node = crm_next_same_xml(node)) {
+        const char *node_type = BASH_EXPORT ? NULL :
+                     crm_element_value(node, XML_ATTR_TYPE);
+        out->message(out, "crmadmin-node", node_type,
+                     crm_str(crm_element_value(node, XML_ATTR_UNAME)),
+                     crm_str(crm_element_value(node, XML_ATTR_ID)),
+                     BASH_EXPORT);
+
+        found++;
+    }
+    // @TODO List Pacemaker Remote nodes that don't have a <node> entry
+
+    out->end_list(out);
+
+    if (found == 0) {
+        out->info(out, "No nodes configured");
+    }
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("crmadmin-node", "char *", "char *", "char *", "gboolean")
+static int
+crmadmin_node_text(pcmk__output_t *out, va_list args)
+{
+    char *type = va_arg(args, char *);
+    char *name = va_arg(args, char *);
+    char *id = va_arg(args, char *);
+    gboolean BASH_EXPORT = va_arg(args, gboolean);
+
+    if (BASH_EXPORT) {
+        out->info(out, "export %s=%s", crm_str(name), crm_str(id));
+    } else {
+        out->info(out, "%s node: %s (%s)", type ? type : "member",
+                  crm_str(name), crm_str(id));
+    }
+
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("crmadmin-node", "char *", "char *", "char *", "gboolean")
+static int
+crmadmin_node_xml(pcmk__output_t *out, va_list args)
+{
+    char *type = va_arg(args, char *);
+    char *name = va_arg(args, char *);
+    char *id = va_arg(args, char *);
+
+    xmlNodePtr node = pcmk__output_create_xml_node(out, "node");
+    xmlSetProp(node, (pcmkXmlStr) "type", (pcmkXmlStr) (type ? type : "member"));
+    xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) crm_str(name));
+    xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) crm_str(id));
+
+    return pcmk_rc_ok;
+}
+
 static pcmk__message_entry_t fmt_functions[] = {
     { "colocations-list", "default", colocations_list },
     { "colocations-list", "xml", colocations_list_xml },
@@ -364,6 +532,15 @@ static pcmk__message_entry_t fmt_functions[] = {
     { "locations-list", "xml", locations_list_xml },
     { "stacks-constraints", "default", stacks_and_constraints },
     { "stacks-constraints", "xml", stacks_and_constraints_xml },
+    { "health", "default", health_text },
+    { "health", "xml", health_xml },
+    { "pacemakerd-health", "default", pacemakerd_health_text },
+    { "pacemakerd-health", "xml", pacemakerd_health_xml },
+    { "dc", "default", dc_text },
+    { "dc", "xml", dc_xml },
+    { "crmadmin-node-list", "default", crmadmin_node_list },
+    { "crmadmin-node", "default", crmadmin_node_text },
+    { "crmadmin-node", "xml", crmadmin_node_xml },
 
     { NULL, NULL, NULL }
 };
diff --git a/tools/Makefile.am b/tools/Makefile.am
index a278fa3..de64c93 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -69,7 +69,8 @@ MAN8DEPS		= crm_attribute
 crmadmin_SOURCES	= crmadmin.c
 crmadmin_LDADD		= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
-			  $(top_builddir)/lib/common/libcrmcommon.la
+			  $(top_builddir)/lib/common/libcrmcommon.la	\
+			  $(top_builddir)/lib/pacemaker/libpacemaker.la
 
 crm_error_SOURCES	= crm_error.c
 crm_error_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
diff --git a/tools/crmadmin.c b/tools/crmadmin.c
index ec902df..2d9d663 100644
--- a/tools/crmadmin.c
+++ b/tools/crmadmin.c
@@ -16,32 +16,13 @@
 #include <glib.h>               // gboolean, GMainLoop, etc.
 #include <libxml/tree.h>        // xmlNode
 
-#include <crm/crm.h>
-#include <crm/cib.h>
-#include <crm/msg_xml.h>
+#include <pacemaker-internal.h>
+
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/output_internal.h>
-#include <crm/common/xml.h>
-#include <crm/common/iso8601.h>
-#include <crm/common/ipc_controld.h>
-#include <crm/common/ipc_pacemakerd.h>
-#include <crm/common/mainloop.h>
 
 #define SUMMARY "query and manage the Pacemaker controller"
 
-#define DEFAULT_MESSAGE_TIMEOUT_MS 30000
-
-static guint message_timer_id = 0;
-static guint message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS;
-static GMainLoop *mainloop = NULL;
-
-bool need_controld_api = true;
-
-bool do_work(pcmk_ipc_api_t *api);
-static char *ipc_name = NULL;
-
-gboolean admin_message_timeout(gpointer data);
-
 static enum {
     cmd_none,
     cmd_shutdown,
@@ -52,17 +33,17 @@ static enum {
     cmd_pacemakerd_health,
 } command = cmd_none;
 
-static gboolean BE_VERBOSE = FALSE;
-static gboolean BASH_EXPORT = FALSE;
-static char *dest_node = NULL;
-static crm_exit_t exit_code = CRM_EX_OK;
-pcmk__output_t *out = NULL;
-
-
 struct {
     gboolean health;
     gint timeout;
-} options;
+    char *dest_node;
+    char *ipc_name;
+    gboolean BASH_EXPORT;
+} options = {
+    .dest_node = NULL,
+    .ipc_name = NULL,
+    .BASH_EXPORT = FALSE
+};
 
 gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
@@ -112,11 +93,11 @@ static GOptionEntry additional_options[] = {
       "\n                          operation failed",
       NULL
     },
-    { "bash-export", 'B', 0, G_OPTION_ARG_NONE, &BASH_EXPORT,
+    { "bash-export", 'B', 0, G_OPTION_ARG_NONE, &options.BASH_EXPORT,
       "Display nodes as shell commands of the form 'export uname=uuid'"
       "\n                          (valid with -N/--nodes)",
     },
-    { "ipc-name", 'i', 0, G_OPTION_ARG_STRING, &ipc_name,
+    { "ipc-name", 'i', 0, G_OPTION_ARG_STRING, &options.ipc_name,
       "Name to use for ipc instead of 'crmadmin' (with -P/--pacemakerd).",
       NULL
     },
@@ -154,478 +135,21 @@ command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError
     }
 
     if (optarg) {
-        if (dest_node != NULL) {
-            free(dest_node);
+        if (options.dest_node != NULL) {
+            free(options.dest_node);
         }
-        dest_node = strdup(optarg);
+        options.dest_node = strdup(optarg);
     }
 
     return TRUE;
 }
 
-PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
-static int
-health_text(pcmk__output_t *out, va_list args)
-{
-    const char *sys_from = va_arg(args, const char *);
-    const char *host_from = va_arg(args, const char *);
-    const char *fsa_state = va_arg(args, const char *);
-    const char *result = va_arg(args, const char *);
-
-    if (!out->is_quiet(out)) {
-        out->info(out, "Status of %s@%s: %s (%s)", crm_str(sys_from),
-                       crm_str(host_from), crm_str(fsa_state), crm_str(result));
-    } else if (fsa_state != NULL) {
-        out->info(out, "%s", fsa_state);
-    }
-
-    return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
-static int
-health_xml(pcmk__output_t *out, va_list args)
-{
-    const char *sys_from = va_arg(args, const char *);
-    const char *host_from = va_arg(args, const char *);
-    const char *fsa_state = va_arg(args, const char *);
-    const char *result = va_arg(args, const char *);
-
-    xmlNodePtr node = pcmk__output_create_xml_node(out, crm_str(sys_from));
-    xmlSetProp(node, (pcmkXmlStr) "node_name", (pcmkXmlStr) crm_str(host_from));
-    xmlSetProp(node, (pcmkXmlStr) "state", (pcmkXmlStr) crm_str(fsa_state));
-    xmlSetProp(node, (pcmkXmlStr) "result", (pcmkXmlStr) crm_str(result));
-
-    return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "const char *", "const char *")
-static int
-pacemakerd_health_text(pcmk__output_t *out, va_list args)
-{
-    const char *sys_from = va_arg(args, const char *);
-    const char *state = va_arg(args, const char *);
-    const char *last_updated = va_arg(args, const char *);
-
-    if (!out->is_quiet(out)) {
-        out->info(out, "Status of %s: '%s' %s %s", crm_str(sys_from),
-                  crm_str(state), (!pcmk__str_empty(last_updated))?
-                  "last updated":"", crm_str(last_updated));
-    } else {
-        out->info(out, "%s", crm_str(state));
-    }
-
-    return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "const char *", "const char *")
-static int
-pacemakerd_health_xml(pcmk__output_t *out, va_list args)
-{
-    const char *sys_from = va_arg(args, const char *);
-    const char *state = va_arg(args, const char *);
-    const char *last_updated = va_arg(args, const char *);
-
-
-    xmlNodePtr node = pcmk__output_create_xml_node(out, crm_str(sys_from));
-    xmlSetProp(node, (pcmkXmlStr) "state", (pcmkXmlStr) crm_str(state));
-    xmlSetProp(node, (pcmkXmlStr) "last_updated", (pcmkXmlStr) crm_str(last_updated));
-
-    return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("dc", "const char *")
-static int
-dc_text(pcmk__output_t *out, va_list args)
-{
-    const char *dc = va_arg(args, const char *);
-
-    if (!out->is_quiet(out)) {
-        out->info(out, "Designated Controller is: %s", crm_str(dc));
-    } else if (dc != NULL) {
-        out->info(out, "%s", dc);
-    }
-
-    return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("dc", "const char *")
-static int
-dc_xml(pcmk__output_t *out, va_list args)
-{
-    const char *dc = va_arg(args, const char *);
-
-    xmlNodePtr node = pcmk__output_create_xml_node(out, "dc");
-    xmlSetProp(node, (pcmkXmlStr) "node_name", (pcmkXmlStr) crm_str(dc));
-
-    return pcmk_rc_ok;
-}
-
-
-PCMK__OUTPUT_ARGS("crmadmin-node-list", "struct xmlNode *")
-static int
-crmadmin_node_list(pcmk__output_t *out, va_list args)
-{
-    xmlNode *xml_node = va_arg(args, xmlNode *);
-    int found = 0;
-    xmlNode *node = NULL;
-    xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node);
-
-    out->begin_list(out, NULL, NULL, "nodes");
-
-    for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL;
-         node = crm_next_same_xml(node)) {
-        const char *node_type = BASH_EXPORT ? NULL :
-                     crm_element_value(node, XML_ATTR_TYPE);
-        out->message(out, "crmadmin-node", node_type,
-                     crm_str(crm_element_value(node, XML_ATTR_UNAME)),
-                     crm_str(crm_element_value(node, XML_ATTR_ID)));
-
-        found++;
-    }
-    // @TODO List Pacemaker Remote nodes that don't have a <node> entry
-
-    out->end_list(out);
-
-    if (found == 0) {
-        out->info(out, "No nodes configured");
-    }
-
-    return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *")
-static int
-crmadmin_node_text(pcmk__output_t *out, va_list args)
-{
-        const char *type = va_arg(args, const char *);
-        const char *name = va_arg(args, const char *);
-        const char *id = va_arg(args, const char *);
-
-        if (BASH_EXPORT) {
-            out->info(out, "export %s=%s", crm_str(name), crm_str(id));
-        } else {
-            out->info(out, "%s node: %s (%s)", type ? type : "member",
-                      crm_str(name), crm_str(id));
-        }
-
-        return pcmk_rc_ok;
-}
-
-PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *")
-static int
-crmadmin_node_xml(pcmk__output_t *out, va_list args)
-{
-    const char *type = va_arg(args, const char *);
-    const char *name = va_arg(args, const char *);
-    const char *id = va_arg(args, const char *);
-
-    xmlNodePtr node = pcmk__output_create_xml_node(out, "node");
-    xmlSetProp(node, (pcmkXmlStr) "type", (pcmkXmlStr) (type ? type : "member"));
-    xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) crm_str(name));
-    xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) crm_str(id));
-
-    return pcmk_rc_ok;
-}
-
-static pcmk__message_entry_t fmt_functions[] = {
-    {"health", "default", health_text },
-    {"health", "xml", health_xml },
-    {"pacemakerd-health", "default", pacemakerd_health_text },
-    {"pacemakerd-health", "xml", pacemakerd_health_xml },
-    {"dc", "default", dc_text },
-    {"dc", "xml", dc_xml },
-    {"crmadmin-node-list", "default", crmadmin_node_list },
-    {"crmadmin-node", "default", crmadmin_node_text },
-    {"crmadmin-node", "xml", crmadmin_node_xml },
-
-    { NULL, NULL, NULL }
-};
-
 static pcmk__supported_format_t formats[] = {
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
-static void
-start_main_loop()
-{
-        exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
-        mainloop = g_main_loop_new(NULL, FALSE);
-        message_timer_id = g_timeout_add(message_timeout_ms,
-                                         admin_message_timeout, NULL);
-        g_main_loop_run(mainloop);
-}
-
-static void
-quit_main_loop(crm_exit_t ec)
-{
-    exit_code = ec;
-    if (mainloop != NULL) {
-        GMainLoop *mloop = mainloop;
-
-        mainloop = NULL; // Don't re-enter this block
-        pcmk_quit_main_loop(mloop, 10);
-        g_main_loop_unref(mloop);
-    }
-}
-
-static void
-event_done(pcmk_ipc_api_t *api)
-{
-    pcmk_disconnect_ipc(api);
-    quit_main_loop(exit_code);
-}
-
-static pcmk_controld_api_reply_t *
-controld_event_reply(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data)
-{
-    pcmk_controld_api_reply_t *reply = event_data;
-
-    switch (event_type) {
-        case pcmk_ipc_event_disconnect:
-            if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
-                out->err(out, "error: Lost connection to controller");
-            }
-            event_done(controld_api);
-            return NULL;
-
-        case pcmk_ipc_event_reply:
-            break;
-
-        default:
-            return NULL;
-    }
-
-    if (message_timer_id != 0) {
-        g_source_remove(message_timer_id);
-        message_timer_id = 0;
-    }
-
-    if (status != CRM_EX_OK) {
-        out->err(out, "error: Bad reply from controller: %s",
-                crm_exit_str(status));
-        exit_code = status;
-        event_done(controld_api);
-        return NULL;
-    }
-
-    if (reply->reply_type != pcmk_controld_reply_ping) {
-        out->err(out, "error: Unknown reply type %d from controller",
-                reply->reply_type);
-        event_done(controld_api);
-        return NULL;
-    }
-
-    return reply;
-}
-
-static void
-controller_status_event_cb(pcmk_ipc_api_t *controld_api,
-                    enum pcmk_ipc_event event_type, crm_exit_t status,
-                    void *event_data, void *user_data)
-{
-    pcmk_controld_api_reply_t *reply = controld_event_reply(controld_api,
-        event_type, status, event_data);
-
-    if (reply != NULL) {
-        out->message(out, "health",
-               reply->data.ping.sys_from,
-               reply->host_from,
-               reply->data.ping.fsa_state,
-               reply->data.ping.result);
-        exit_code = CRM_EX_OK;
-    }
-
-    event_done(controld_api);
-}
-
-static void
-designated_controller_event_cb(pcmk_ipc_api_t *controld_api,
-                    enum pcmk_ipc_event event_type, crm_exit_t status,
-                    void *event_data, void *user_data)
-{
-    pcmk_controld_api_reply_t *reply = controld_event_reply(controld_api,
-        event_type, status, event_data);
-
-    if (reply != NULL) {
-        out->message(out, "dc", reply->host_from);
-        exit_code = CRM_EX_OK;
-    }
-
-    event_done(controld_api);
-}
-
-static void
-pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
-                    enum pcmk_ipc_event event_type, crm_exit_t status,
-                    void *event_data, void *user_data)
-{
-    pcmk_pacemakerd_api_reply_t *reply = event_data;
-
-    crm_time_t *crm_when = crm_time_new(NULL);
-    char *pinged_buf = NULL;
-
-    switch (event_type) {
-        case pcmk_ipc_event_disconnect:
-            if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
-                out->err(out, "error: Lost connection to pacemakerd");
-            }
-            event_done(pacemakerd_api);
-            return;
-
-        case pcmk_ipc_event_reply:
-            break;
-
-        default:
-            return;
-    }
-
-    if (message_timer_id != 0) {
-        g_source_remove(message_timer_id);
-        message_timer_id = 0;
-    }
-
-    if (status != CRM_EX_OK) {
-        out->err(out, "error: Bad reply from pacemakerd: %s",
-                crm_exit_str(status));
-        event_done(pacemakerd_api);
-        return;
-    }
-
-    if (reply->reply_type != pcmk_pacemakerd_reply_ping) {
-        out->err(out, "error: Unknown reply type %d from pacemakerd",
-                reply->reply_type);
-        event_done(pacemakerd_api);
-        return;
-    }
-
-    // Parse desired information from reply
-    crm_time_set_timet(crm_when, &reply->data.ping.last_good);
-    pinged_buf = crm_time_as_string(crm_when,
-        crm_time_log_date | crm_time_log_timeofday |
-            crm_time_log_with_timezone);
-
-    out->message(out, "pacemakerd-health",
-        reply->data.ping.sys_from,
-        (reply->data.ping.status == pcmk_rc_ok)?
-            pcmk_pacemakerd_api_daemon_state_enum2text(
-                reply->data.ping.state):"query failed",
-        (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:"");
-    exit_code = CRM_EX_OK;
-    free(pinged_buf);
-
-    event_done(pacemakerd_api);
-}
-
-static pcmk_ipc_api_t *
-ipc_connect(enum pcmk_ipc_server server, pcmk_ipc_callback_t cb)
-{
-    int rc;
-    pcmk_ipc_api_t *api = NULL;
-
-    rc = pcmk_new_ipc_api(&api, server);
-    if (api == NULL) {
-        out->err(out, "error: Could not connect to %s: %s",
-                (server == pcmk_ipc_controld) ? "controller" : "pacemakerd",
-                pcmk_rc_str(rc));
-        exit_code = pcmk_rc2exitc(rc);
-        return NULL;
-    }
-    pcmk_register_ipc_callback(api, cb, NULL);
-    rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_main);
-    if (rc != pcmk_rc_ok) {
-        out->err(out, "error: Could not connect to %s: %s",
-                (server == pcmk_ipc_controld) ? "controller" : "pacemakerd",
-                pcmk_rc_str(rc));
-        exit_code = pcmk_rc2exitc(rc);
-        return NULL;
-    }
-
-    return api;
-}
-
-static void
-pcmk__controller_status()
-{
-    pcmk_ipc_api_t *controld_api = ipc_connect(pcmk_ipc_controld, controller_status_event_cb);
-
-    if (controld_api != NULL) {
-        int rc = pcmk_controld_api_ping(controld_api, dest_node);
-        if (rc != pcmk_rc_ok) {
-            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
-        }
-
-        start_main_loop();
-
-        pcmk_free_ipc_api(controld_api);
-    }
-}
-
-static void
-pcmk__designated_controller()
-{
-    pcmk_ipc_api_t *controld_api = ipc_connect(pcmk_ipc_controld, designated_controller_event_cb);
-
-    if (controld_api != NULL) {
-        int rc = pcmk_controld_api_ping(controld_api, dest_node);
-        if (rc != pcmk_rc_ok) {
-            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
-        }
-
-        start_main_loop();
-
-        pcmk_free_ipc_api(controld_api);
-    }
-}
-
-static void
-pcmk__pacemakerd_status()
-{
-    pcmk_ipc_api_t *pacemakerd_api = ipc_connect(pcmk_ipc_pacemakerd, pacemakerd_event_cb);
-
-    if (pacemakerd_api != NULL) {
-        int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name);
-        if (rc != pcmk_rc_ok) {
-            out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
-            exit_code = pcmk_rc2exitc(rc);
-        }
-
-        start_main_loop();
-
-        pcmk_free_ipc_api(pacemakerd_api);
-    }
-}
-
-// \return Standard Pacemaker return code
-static int
-pcmk__list_nodes()
-{
-    cib_t *the_cib = cib_new();
-    xmlNode *output = NULL;
-    int rc;
-
-    if (the_cib == NULL) {
-        return ENOMEM;
-    }
-    rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
-    if (rc != pcmk_ok) {
-        return pcmk_legacy2rc(rc);
-    }
-
-    rc = the_cib->cmds->query(the_cib, NULL, &output,
-                              cib_scope_local | cib_sync_call);
-    if (rc == pcmk_ok) {
-        out->message(out, "crmadmin-node-list", output);
-        free_xml(output);
-    }
-    the_cib->cmds->signoff(the_cib);
-    return pcmk_legacy2rc(rc);
-}
-
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
@@ -658,8 +182,10 @@ build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
 int
 main(int argc, char **argv)
 {
-    int argerr = 0;
+    pcmk__output_t *out = NULL;
+    crm_exit_t exit_code = CRM_EX_OK;
     int rc;
+    int argerr = 0;
     pcmk_ipc_api_t *controld_api = NULL;
 
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
@@ -683,7 +209,6 @@ main(int argc, char **argv)
     }
 
     for (int i = 0; i < args->verbosity; i++) {
-        BE_VERBOSE = TRUE;
         crm_bump_log_level(argc, argv);
     }
 
@@ -697,7 +222,7 @@ main(int argc, char **argv)
 
     out->quiet = args->quiet;
 
-    pcmk__register_messages(out, fmt_functions);
+    pcmk__register_lib_messages(out);
 
     if (!pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname())) {
         goto done;
@@ -708,13 +233,6 @@ main(int argc, char **argv)
         goto done;
     }
 
-    if (options.timeout) {
-        message_timeout_ms = (guint) options.timeout;
-        if (message_timeout_ms < 1) {
-            message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS;
-        }
-    }
-
     if (options.health) {
         out->err(out, "Cluster-wide health option not supported");
         ++argerr;
@@ -740,43 +258,31 @@ main(int argc, char **argv)
 
     switch (command) {
         case cmd_health:
-            pcmk__controller_status();
-            goto done;
+            rc = pcmk__controller_status(out, options.dest_node, options.timeout);
+            break;
         case cmd_pacemakerd_health:
-            pcmk__pacemakerd_status();
-            goto done;
+            rc = pcmk__pacemakerd_status(out, options.ipc_name, options.timeout);
+            break;
         case cmd_list_nodes:
-            rc = pcmk__list_nodes();
-            // might need movink
-            if (rc != pcmk_rc_ok) {
-                out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
-                exit_code = pcmk_rc2exitc(rc);
-            }
+            rc = pcmk__list_nodes(out, options.BASH_EXPORT);
             break;
         case cmd_whois_dc:
-            pcmk__designated_controller();
-            goto done;
-        default:
-            rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
-            if (controld_api == NULL) {
-                out->err(out, "error: Could not connect to controller: %s",
-                        pcmk_rc_str(rc));
-                exit_code = pcmk_rc2exitc(rc);
-                goto done;
-            }
-            rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
-            if (rc != pcmk_rc_ok) {
-                out->err(out, "error: Could not connect to controller: %s",
-                        pcmk_rc_str(rc));
-                exit_code = pcmk_rc2exitc(rc);
-                goto done;
-            }
+            rc = pcmk__designated_controller(out, options.timeout);
+            break;
+        case cmd_shutdown:
+            rc = pcmk__shutdown_controller(out, options.dest_node);
+            break;
+        case cmd_elect_dc:
+            rc = pcmk__start_election(out);
+            break;
+        case cmd_none:
+            rc = pcmk_rc_error;
             break;
     }
 
-    if (do_work(controld_api?controld_api:NULL)) {
-        // A reply is needed from controller, so run main loop to get it
-        start_main_loop();
+    if (rc != pcmk_rc_ok) {
+        out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
+        exit_code = pcmk_rc2exitc(rc);
     }
 
 done:
@@ -787,10 +293,6 @@ done:
         pcmk_free_ipc_api(capi);
     }
 
-    if (mainloop != NULL) {
-        g_main_loop_unref(mainloop);
-        mainloop = NULL;
-    }
     g_strfreev(processed_args);
     g_clear_error(&error);
     pcmk__free_arg_context(context);
@@ -801,43 +303,3 @@ done:
     return crm_exit(exit_code);
 
 }
-
-// \return True if reply from controller is needed
-bool
-do_work(pcmk_ipc_api_t *api)
-{
-    bool need_reply = false;
-    int rc = pcmk_rc_ok;
-
-    switch (command) {
-        case cmd_shutdown:
-            rc = pcmk_controld_api_shutdown(api, dest_node);
-            break;
-
-        case cmd_elect_dc:
-            rc = pcmk_controld_api_start_election(api);
-            break;
-
-        case cmd_none: // not actually possible here
-            break;
-
-        default:
-            break;
-    }
-    if (rc != pcmk_rc_ok) {
-        out->err(out, "error: Command failed: %s", pcmk_rc_str(rc));
-        exit_code = pcmk_rc2exitc(rc);
-    }
-    return need_reply;
-}
-
-gboolean
-admin_message_timeout(gpointer data)
-{
-    out->err(out,
-            "error: No reply received from controller before timeout (%dms)",
-            message_timeout_ms);
-    message_timer_id = 0;
-    quit_main_loop(CRM_EX_TIMEOUT);
-    return FALSE; // Tells glib to remove source
-}
-- 
1.8.3.1