diff --git a/include/crm_internal.h b/include/crm_internal.h
index b93ba6e3c1..716c4c6c16 100644
--- a/include/crm_internal.h
+++ b/include/crm_internal.h
@@ -1,182 +1,179 @@
 /*
  * Copyright 2006-2020 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_INTERNAL__H
 #  define CRM_INTERNAL__H
 
 #  include <config.h>
 #  include <portability.h>
 
 #  include <glib.h>
 #  include <stdbool.h>
 #  include <libxml/tree.h>
 
 /* Public API headers can guard deprecated code with this symbol, thus
  * preventing internal code (which includes this header) from using it, while
  * still allowing external code (which can't include this header) to use it,
  * for backward compatibility.
  */
 #define PCMK__NO_COMPAT
 
 #  include <crm/lrmd.h>
 #  include <crm/common/logging.h>
 #  include <crm/common/ipcs_internal.h>
 #  include <crm/common/options_internal.h>
 #  include <crm/common/internal.h>
 
-/* Dynamic loading of libraries */
-void *find_library_function(void **handle, const char *lib, const char *fn, int fatal);
-
 /* char2score */
 extern int node_score_red;
 extern int node_score_green;
 extern int node_score_yellow;
 
 /* Assorted convenience functions */
 void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile);
 
 static inline long long
 crm_clear_bit(const char *function, int line, const char *target, long long word, long long bit)
 {
     long long rc = (word & ~bit);
 
     if (rc == word) {
         /* Unchanged */
     } else if (target) {
         crm_trace("Bit 0x%.8llx for %s cleared by %s:%d", bit, target, function, line);
     } else {
         crm_trace("Bit 0x%.8llx cleared by %s:%d", bit, function, line);
     }
 
     return rc;
 }
 
 static inline long long
 crm_set_bit(const char *function, int line, const char *target, long long word, long long bit)
 {
     long long rc = (word | bit);
 
     if (rc == word) {
         /* Unchanged */
     } else if (target) {
         crm_trace("Bit 0x%.8llx for %s set by %s:%d", bit, target, function, line);
     } else {
         crm_trace("Bit 0x%.8llx set by %s:%d", bit, function, line);
     }
 
     return rc;
 }
 
 #  define set_bit(word, bit) word = crm_set_bit(__FUNCTION__, __LINE__, NULL, word, bit)
 #  define clear_bit(word, bit) word = crm_clear_bit(__FUNCTION__, __LINE__, NULL, word, bit)
 
 char *generate_hash_key(const char *crm_msg_reference, const char *sys);
 
 void strip_text_nodes(xmlNode * xml);
 void pcmk_panic(const char *origin);
 pid_t pcmk_locate_sbd(void);
 
 
 /*
  * XML attribute names used only by internal code
  */
 
 #define PCMK__XA_ATTR_DAMPENING         "attr_dampening"
 #define PCMK__XA_ATTR_FORCE             "attrd_is_force_write"
 #define PCMK__XA_ATTR_INTERVAL          "attr_clear_interval"
 #define PCMK__XA_ATTR_IS_PRIVATE        "attr_is_private"
 #define PCMK__XA_ATTR_IS_REMOTE         "attr_is_remote"
 #define PCMK__XA_ATTR_NAME              "attr_name"
 #define PCMK__XA_ATTR_NODE_ID           "attr_host_id"
 #define PCMK__XA_ATTR_NODE_NAME         "attr_host"
 #define PCMK__XA_ATTR_OPERATION         "attr_clear_operation"
 #define PCMK__XA_ATTR_PATTERN           "attr_regex"
 #define PCMK__XA_ATTR_RESOURCE          "attr_resource"
 #define PCMK__XA_ATTR_SECTION           "attr_section"
 #define PCMK__XA_ATTR_SET               "attr_set"
 #define PCMK__XA_ATTR_USER              "attr_user"
 #define PCMK__XA_ATTR_UUID              "attr_key"
 #define PCMK__XA_ATTR_VALUE             "attr_value"
 #define PCMK__XA_ATTR_VERSION           "attr_version"
 #define PCMK__XA_ATTR_WRITER            "attr_writer"
 #define PCMK__XA_MODE                   "mode"
 #define PCMK__XA_TASK                   "task"
 
 
 /*
  * IPC service names that are only used internally
  */
 
 #  define PCMK__SERVER_BASED_RO		"cib_ro"
 #  define PCMK__SERVER_BASED_RW		"cib_rw"
 #  define PCMK__SERVER_BASED_SHM		"cib_shm"
 
 /*
  * IPC commands that can be sent to Pacemaker daemons
  */
 
 #define PCMK__ATTRD_CMD_PEER_REMOVE     "peer-remove"
 #define PCMK__ATTRD_CMD_UPDATE          "update"
 #define PCMK__ATTRD_CMD_UPDATE_BOTH     "update-both"
 #define PCMK__ATTRD_CMD_UPDATE_DELAY    "update-delay"
 #define PCMK__ATTRD_CMD_QUERY           "query"
 #define PCMK__ATTRD_CMD_REFRESH         "refresh"
 #define PCMK__ATTRD_CMD_FLUSH           "flush"
 #define PCMK__ATTRD_CMD_SYNC            "sync"
 #define PCMK__ATTRD_CMD_SYNC_RESPONSE   "sync-response"
 #define PCMK__ATTRD_CMD_CLEAR_FAILURE   "clear-failure"
 
 
 /*
  * Environment variables used by Pacemaker
  */
 
 #define PCMK__ENV_PHYSICAL_HOST         "physical_host"
 
 
 #  if SUPPORT_COROSYNC
 #    include <qb/qbipc_common.h>
 #    include <corosync/corotypes.h>
 typedef struct qb_ipc_request_header cs_ipc_header_request_t;
 typedef struct qb_ipc_response_header cs_ipc_header_response_t;
 #  else
 typedef struct {
     int size __attribute__ ((aligned(8)));
     int id __attribute__ ((aligned(8)));
 } __attribute__ ((aligned(8))) cs_ipc_header_request_t;
 
 typedef struct {
     int size __attribute__ ((aligned(8)));
     int id __attribute__ ((aligned(8)));
     int error __attribute__ ((aligned(8)));
 } __attribute__ ((aligned(8))) cs_ipc_header_response_t;
 
 #  endif
 
 static inline void *
 realloc_safe(void *ptr, size_t size)
 {
     void *new_ptr;
 
     // realloc(p, 0) can replace free(p) but this wrapper can't
     CRM_ASSERT(size > 0);
 
     new_ptr = realloc(ptr, size);
     if (new_ptr == NULL) {
         free(ptr);
         abort();
     }
     return new_ptr;
 }
 
 const char *crm_xml_add_last_written(xmlNode *xml_node);
 void crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth);
 void crm_buffer_add_char(char **buffer, int *offset, int *max, char c);
 
 #endif                          /* CRM_INTERNAL__H */
diff --git a/lib/common/utils.c b/lib/common/utils.c
index dc358c34df..8210f6ac80 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,661 +1,630 @@
 /*
  * Copyright 2004-2020 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 <crm_internal.h>
-#include <dlfcn.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/utsname.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <limits.h>
 #include <pwd.h>
 #include <time.h>
 #include <libgen.h>
 #include <signal.h>
 
 #include <qb/qbdefs.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <crm/msg_xml.h>
 #include <crm/cib/internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/util.h>
 #include <crm/common/ipc.h>
 #include <crm/common/iso8601.h>
 #include <crm/common/mainloop.h>
 #include <libxml2/libxml/relaxng.h>
 
 #ifndef MAXLINE
 #  define MAXLINE 512
 #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;
 
 int
 char2score(const char *score)
 {
     int score_f = 0;
 
     if (score == NULL) {
 
     } else if (pcmk_str_is_minus_infinity(score)) {
         score_f = -CRM_SCORE_INFINITY;
 
     } else if (pcmk_str_is_infinity(score)) {
         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);
 }
 
 char *
 generate_hash_key(const char *crm_msg_reference, const char *sys)
 {
     char *hash_key = crm_strdup_printf("%s_%s", (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);
     if (buffer == NULL) {
         return -ENOMEM;
     }
 
     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;
 }
 
 /*!
  * \brief Get user and group IDs of pacemaker daemon user
  *
  * \param[out] uid  If non-NULL, where to store daemon user ID
  * \param[out] gid  If non-NULL, where to store daemon group ID
  *
  * \return pcmk_ok on success, -errno otherwise
  */
 int
 pcmk_daemon_user(uid_t *uid, gid_t *gid)
 {
     static uid_t daemon_uid;
     static gid_t daemon_gid;
     static bool found = false;
     int rc = pcmk_err_generic;
 
     if (!found) {
         rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
         if (rc == pcmk_ok) {
             found = true;
         }
     }
     if (found) {
         if (uid) {
             *uid = daemon_uid;
         }
         if (gid) {
             *gid = daemon_gid;
         }
     }
     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;
 }
 
 /*!
  * \brief Parse milliseconds from a Pacemaker interval specification
  *
  * \param[in] input  Pacemaker time interval specification (a bare number of
  *                   seconds, a number with a unit optionally with whitespace
  *                   before and/or after the number, or an ISO 8601 duration)
  *
  * \return Milliseconds equivalent of given specification on success (limited
  *         to the range of an unsigned integer), 0 if input is NULL,
  *         or 0 (and set errno to EINVAL) on error
  */
 guint
 crm_parse_interval_spec(const char *input)
 {
     long long msec = -1;
 
     errno = 0;
     if (input == NULL) {
         return 0;
 
     } else if (input[0] == 'P') {
         crm_time_t *period_s = crm_time_parse_duration(input);
 
         if (period_s) {
             msec = 1000 * crm_time_get_seconds(period_s);
             crm_time_free(period_s);
         }
 
     } else {
         msec = crm_get_msec(input);
     }
 
     if (msec < 0) {
         crm_warn("Using 0 instead of '%s'", input);
         errno = EINVAL;
         return 0;
     }
     return (msec >= G_MAXUINT)? G_MAXUINT : (guint) 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;
     pid_t pid;
     const char *devnull = "/dev/null";
 
     if (daemonize == FALSE) {
         return;
     }
 
     /* Check before we even try... */
     rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
     if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
         crm_err("%s: already running [pid %lld in %s]",
                 name, (long long) pid, pidfile);
         printf("%s: already running [pid %lld in %s]\n",
                name, (long long) 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 = pcmk__lock_pidfile(pidfile, name);
     if (rc != pcmk_rc_ok) {
         crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
                 pidfile, name, pcmk_rc_str(rc), rc);
         printf("Could not lock '%s' for %s: %s (%d)\n",
                pidfile, name, pcmk_rc_str(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_strdup_printf(CRM_META "_%s", 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;
 }
 
-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 <uuid/uuid.h>
 #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 <md5.h>
 
 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);
 }
 
 bool
 pcmk_str_is_infinity(const char *s) {
     return crm_str_eq(s, CRM_INFINITY_S, TRUE) || crm_str_eq(s, CRM_PLUS_INFINITY_S, TRUE);
 }
 
 bool
 pcmk_str_is_minus_infinity(const char *s) {
     return crm_str_eq(s, CRM_MINUS_INFINITY_S, TRUE);
 }
diff --git a/lib/fencing/st_lha.c b/lib/fencing/st_lha.c
index 26a8dba1b4..8ae2276aba 100644
--- a/lib/fencing/st_lha.c
+++ b/lib/fencing/st_lha.c
@@ -1,258 +1,281 @@
 /*
  * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net>
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <errno.h>
 #include <glib.h>
+#include <dlfcn.h>
 
 #include <crm/crm.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 
 #include <stonith/stonith.h>
 
 #define LHA_STONITH_LIBRARY "libstonith.so.1"
 
 static void *lha_agents_lib = NULL;
 
 static const char META_TEMPLATE[] =
     "<?xml version=\"1.0\"?>\n"
     "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
     "<resource-agent name=\"%s\">\n"
     "  <version>1.0</version>\n"
     "  <longdesc lang=\"en\">\n"
     "%s\n"
     "  </longdesc>\n"
     "  <shortdesc lang=\"en\">%s</shortdesc>\n"
     "%s\n"
     "  <actions>\n"
     "    <action name=\"start\"   timeout=\"20\" />\n"
     "    <action name=\"stop\"    timeout=\"15\" />\n"
     "    <action name=\"status\"  timeout=\"20\" />\n"
     "    <action name=\"monitor\" timeout=\"20\" interval=\"3600\"/>\n"
     "    <action name=\"meta-data\"  timeout=\"15\" />\n"
     "  </actions>\n"
     "  <special tag=\"heartbeat\">\n"
     "    <version>2.0</version>\n" "  </special>\n" "</resource-agent>\n";
 
+static void *
+find_library_function(void **handle, const char *lib, const char *fn)
+{
+    void *a_function;
+
+    if (*handle == NULL) {
+        *handle = dlopen(lib, RTLD_LAZY);
+        if ((*handle) == NULL) {
+            crm_err("Could not open %s: %s", lib, dlerror());
+            return NULL;
+        }
+    }
+
+    a_function = dlsym(*handle, fn);
+    if (a_function == NULL) {
+        crm_err("Could not find %s in %s: %s", fn, lib, dlerror());
+    }
+
+    return a_function;
+}
+
 /*!
  * \brief Determine namespace of a fence agent
  *
  * \param[in] agent        Fence agent type
  * \param[in] namespace_s  Name of agent namespace as string, if known
  *
  * \return Namespace of specified agent, as enum value
  */
 bool
 stonith__agent_is_lha(const char *agent)
 {
     Stonith *stonith_obj = NULL;
 
     static gboolean need_init = TRUE;
     static Stonith *(*st_new_fn) (const char *) = NULL;
     static void (*st_del_fn) (Stonith *) = NULL;
 
     if (need_init) {
         need_init = FALSE;
         st_new_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
-                                          "stonith_new", FALSE);
+                                          "stonith_new");
         st_del_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
-                                          "stonith_delete", FALSE);
+                                          "stonith_delete");
     }
 
     if (lha_agents_lib && st_new_fn && st_del_fn) {
         stonith_obj = (*st_new_fn) (agent);
         if (stonith_obj) {
             (*st_del_fn) (stonith_obj);
             return TRUE;
         }
     }
     return FALSE;
 }
 
 int
 stonith__list_lha_agents(stonith_key_value_t **devices)
 {
     static gboolean need_init = TRUE;
 
     int count = 0;
     char **entry = NULL;
     char **type_list = NULL;
     static char **(*type_list_fn) (void) = NULL;
     static void (*type_free_fn) (char **) = NULL;
 
     if (need_init) {
         need_init = FALSE;
-        type_list_fn =
-            find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_types", FALSE);
-        type_free_fn =
-            find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_free_hostlist",
-                                  FALSE);
+        type_list_fn = find_library_function(&lha_agents_lib,
+                                             LHA_STONITH_LIBRARY,
+                                             "stonith_types");
+        type_free_fn = find_library_function(&lha_agents_lib,
+                                             LHA_STONITH_LIBRARY,
+                                             "stonith_free_hostlist");
     }
 
     if (type_list_fn) {
         type_list = (*type_list_fn) ();
     }
 
     for (entry = type_list; entry != NULL && *entry; ++entry) {
         crm_trace("Added: %s", *entry);
         *devices = stonith_key_value_add(*devices, NULL, *entry);
         count++;
     }
     if (type_list && type_free_fn) {
         (*type_free_fn) (type_list);
     }
     return count;
 }
 
 static inline char *
 strdup_null(const char *val)
 {
     if (val) {
         return strdup(val);
     }
     return NULL;
 }
 
 static void
 stonith_plugin(int priority, const char *fmt, ...) __attribute__((__format__ (__printf__, 2, 3)));
 
 static void
 stonith_plugin(int priority, const char *format, ...)
 {
     int err = errno;
 
     va_list ap;
     int len = 0;
     char *string = NULL;
 
     va_start(ap, format);
 
     len = vasprintf (&string, format, ap);
     va_end(ap);
     CRM_ASSERT(len > 0);
 
     do_crm_log_alias(priority, __FILE__, __func__, __LINE__, "%s", string);
 
     free(string);
     errno = err;
 }
 
 int
 stonith__lha_metadata(const char *agent, int timeout, char **output)
 {
     int rc = 0;
     char *buffer = NULL;
     static const char *no_parameter_info = "<!-- no value -->";
 
     Stonith *stonith_obj = NULL;
 
     static gboolean need_init = TRUE;
     static Stonith *(*st_new_fn) (const char *) = NULL;
     static const char *(*st_info_fn) (Stonith *, int) = NULL;
     static void (*st_del_fn) (Stonith *) = NULL;
     static void (*st_log_fn) (Stonith *, PILLogFun) = NULL;
 
     if (need_init) {
         need_init = FALSE;
         st_new_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
-                                          "stonith_new", FALSE);
+                                          "stonith_new");
         st_del_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
-                                          "stonith_delete", FALSE);
+                                          "stonith_delete");
         st_log_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
-                                          "stonith_set_log", FALSE);
+                                          "stonith_set_log");
         st_info_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
-                                           "stonith_get_info", FALSE);
+                                           "stonith_get_info");
     }
 
     if (lha_agents_lib && st_new_fn && st_del_fn && st_info_fn && st_log_fn) {
         char *xml_meta_longdesc = NULL;
         char *xml_meta_shortdesc = NULL;
 
         char *meta_param = NULL;
         char *meta_longdesc = NULL;
         char *meta_shortdesc = NULL;
 
         stonith_obj = (*st_new_fn) (agent);
         if (stonith_obj) {
             (*st_log_fn) (stonith_obj, (PILLogFun) & stonith_plugin);
             meta_longdesc = strdup_null((*st_info_fn) (stonith_obj, ST_DEVICEDESCR));
             if (meta_longdesc == NULL) {
                 crm_warn("no long description in %s's metadata.", agent);
                 meta_longdesc = strdup(no_parameter_info);
             }
 
             meta_shortdesc = strdup_null((*st_info_fn) (stonith_obj, ST_DEVICEID));
             if (meta_shortdesc == NULL) {
                 crm_warn("no short description in %s's metadata.", agent);
                 meta_shortdesc = strdup(no_parameter_info);
             }
 
             meta_param = strdup_null((*st_info_fn) (stonith_obj, ST_CONF_XML));
             if (meta_param == NULL) {
                 crm_warn("no list of parameters in %s's metadata.", agent);
                 meta_param = strdup(no_parameter_info);
             }
             (*st_del_fn) (stonith_obj);
         } else {
             errno = EINVAL;
             crm_perror(LOG_ERR, "Agent %s not found", agent);
             return -EINVAL;
         }
 
         xml_meta_longdesc =
             (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc);
         xml_meta_shortdesc =
             (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc);
 
         buffer = crm_strdup_printf(META_TEMPLATE, agent, xml_meta_longdesc,
                                    xml_meta_shortdesc, meta_param);
 
         xmlFree(xml_meta_longdesc);
         xmlFree(xml_meta_shortdesc);
 
         free(meta_shortdesc);
         free(meta_longdesc);
         free(meta_param);
     }
     if (output) {
         *output = buffer;
     } else {
         free(buffer);
     }
     return rc;
 }
 
 /* Implement a dummy function that uses -lpils so that linkers don't drop the
  * reference.
  */
 
 #include <pils/plugin.h>
 
 const char *i_hate_pils(int rc);
 
 const char *
 i_hate_pils(int rc)
 {
     return PIL_strerror(rc);
 }
 
 int
 stonith__lha_validate(stonith_t *st, int call_options, const char *target,
                       const char *agent, GHashTable *params, int timeout,
                       char **output, char **error_output)
 {
     errno = EOPNOTSUPP;
     crm_perror(LOG_ERR, "Cannot validate Linux-HA fence agents");
     return -EOPNOTSUPP;
 }