diff --git a/cib/io.c b/cib/io.c
index 17e5a9e37e..2dcaa37dc5 100644
--- a/cib/io.c
+++ b/cib/io.c
@@ -1,828 +1,844 @@
 /*
  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <dirent.h>
 
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 
 #include <crm/crm.h>
 
 #include <cibio.h>
 #include <crm/cib.h>
 #include <crm/common/util.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/util.h>
 #include <crm/cluster.h>
 
 #define CIB_SERIES "cib"
 #define CIB_SERIES_MAX 100
 #define CIB_SERIES_BZIP FALSE /* Must be false due to the way archived
                                * copies are created - ie. with calls to
                                * link()
                                */
 
 extern const char *cib_root;
 
 #define CIB_WRITE_PARANOIA	0
 
 const char *local_resource_path[] = {
     XML_CIB_TAG_STATUS,
 };
 
 const char *resource_path[] = {
     XML_CIB_TAG_RESOURCES,
 };
 
 const char *node_path[] = {
     XML_CIB_TAG_NODES,
 };
 
 const char *constraint_path[] = {
     XML_CIB_TAG_CONSTRAINTS,
 };
 
 crm_trigger_t *cib_writer = NULL;
 gboolean initialized = FALSE;
 xmlNode *node_search = NULL;
 xmlNode *resource_search = NULL;
 xmlNode *constraint_search = NULL;
 xmlNode *status_search = NULL;
 
 extern int cib_status;
 
 int set_connected_peers(xmlNode * xml_obj);
 void GHFunc_count_peers(gpointer key, gpointer value, gpointer user_data);
 int write_cib_contents(gpointer p);
 extern void cib_cleanup(void);
 
 static gboolean
 validate_cib_digest(xmlNode * local_cib, const char *sigfile)
 {
     char *digest = NULL;
     char *expected = NULL;
     gboolean passed = FALSE;
     FILE *expected_strm = NULL;
     int start = 0, length = 0, read_len = 0;
 
     CRM_ASSERT(sigfile != NULL);
 
     expected_strm = fopen(sigfile, "r");
     if (expected_strm == NULL && errno == ENOENT) {
         crm_warn("No on-disk digest present");
         return TRUE;
 
     } else if (expected_strm == NULL) {
         crm_perror(LOG_ERR, "Could not open signature file %s for reading", sigfile);
         goto bail;
     }
 
     if (local_cib != NULL) {
         digest = calculate_on_disk_digest(local_cib);
     }
 
     start = ftell(expected_strm);
     fseek(expected_strm, 0L, SEEK_END);
     length = ftell(expected_strm);
     fseek(expected_strm, 0L, start);
 
     CRM_ASSERT(length >= 0);
     CRM_ASSERT(start == ftell(expected_strm));
 
     if (length > 0) {
         crm_trace("Reading %d bytes from file", length);
         expected = calloc(1, (length + 1));
         read_len = fread(expected, 1, length, expected_strm);   /* Coverity: False positive */
         CRM_ASSERT(read_len == length);
     }
     fclose(expected_strm);
 
   bail:
     if (expected == NULL) {
         crm_err("On-disk digest is empty");
 
     } else if (safe_str_eq(expected, digest)) {
         crm_trace("Digest comparision passed: %s", digest);
         passed = TRUE;
 
     } else {
         crm_err("Digest comparision failed: expected %s (%s), calculated %s",
                 expected, sigfile, digest);
     }
 
     free(digest);
     free(expected);
     return passed;
 }
 
 static int
 write_cib_digest(xmlNode * local_cib, const char *digest_file, int fd, char *digest)
 {
     int rc = 0;
     char *local_digest = NULL;
     FILE *digest_strm = fdopen(fd, "w");
 
     if (digest_strm == NULL) {
         crm_perror(LOG_ERR, "Cannot open signature file %s for writing", digest_file);
         return -1;
     }
 
     if (digest == NULL) {
         local_digest = calculate_on_disk_digest(local_cib);
         CRM_ASSERT(digest != NULL);
         digest = local_digest;
     }
 
     rc = fprintf(digest_strm, "%s", digest);
     if (rc < 0) {
         crm_perror(LOG_ERR, "Cannot write to signature file %s", digest_file);
     }
 
     CRM_ASSERT(digest_strm != NULL);
     if (fflush(digest_strm) != 0) {
         crm_perror(LOG_ERR, "Couldnt flush the contents of %s", digest_file);
         rc = -1;
     }
 
     if (fsync(fileno(digest_strm)) < 0) {
         crm_perror(LOG_ERR, "Couldnt sync the contents of %s", digest_file);
         rc = -1;
     }
 
     fclose(digest_strm);
     free(local_digest);
     return rc;
 }
 
 static gboolean
 validate_on_disk_cib(const char *filename, xmlNode ** on_disk_cib)
 {
     int s_res = -1;
     struct stat buf;
     gboolean passed = TRUE;
     xmlNode *root = NULL;
 
     CRM_ASSERT(filename != NULL);
 
     s_res = stat(filename, &buf);
     if (s_res == 0) {
         char *sigfile = NULL;
         size_t fnsize;
 
-        if (buf.st_size == 0) {
-            crm_warn("Cluster configuration file %s is corrupt: size is zero", filename);
-            return TRUE;
-        }
-
         crm_trace("Reading cluster configuration from: %s", filename);
         root = filename2xml(filename);
 
         fnsize = strlen(filename) + 5;
         sigfile = calloc(1, fnsize);
         snprintf(sigfile, fnsize, "%s.sig", filename);
         if (validate_cib_digest(root, sigfile) == FALSE) {
             passed = FALSE;
         }
         free(sigfile);
     }
 
     if (on_disk_cib != NULL) {
         *on_disk_cib = root;
     } else {
         free_xml(root);
     }
 
     return passed;
 }
 
+static gboolean
+on_disk_cib_corrupt(const char *filename)
+{
+    int s_res = -1;
+    struct stat buf;
+    gboolean corrupt = FALSE;
+
+    CRM_ASSERT(filename != NULL);
+
+    s_res = stat(filename, &buf);
+    if (s_res == 0) {
+        if (buf.st_size == 0) {
+            crm_warn("Cluster configuration file %s is corrupt: size is zero", filename);
+            corrupt = TRUE;
+        }
+    }
+
+    return corrupt;
+}
+
 static int
 cib_rename(const char *old, const char *new)
 {
     int rc = 0;
     int automatic_fd = 0;
     char *automatic = NULL;
 
     if (new == NULL) {
         umask(S_IWGRP | S_IWOTH | S_IROTH);
 
         automatic = g_strdup_printf("%s/cib.auto.XXXXXX", cib_root);
         automatic_fd = mkstemp(automatic);
         new = automatic;
 
         crm_err("Archiving corrupt or unusable file %s as %s", old, automatic);
     }
 
     rc = rename(old, new);
     if (rc < 0) {
         crm_perror(LOG_ERR, "Couldn't rename %s as %s - Disabling disk writes and continuing", old,
                    new);
         cib_writes_enabled = FALSE;
     }
     if (automatic_fd > 0) {
         close(automatic_fd);
     }
     free(automatic);
     return rc;
 }
 
 /*
  * It is the callers responsibility to free the output of this function
  */
 
 static xmlNode *
 retrieveCib(const char *filename, const char *sigfile, gboolean archive_invalid)
 {
     struct stat buf;
     xmlNode *root = NULL;
 
     crm_info("Reading cluster configuration from: %s (digest: %s)", filename, sigfile);
 
     if (stat(filename, &buf) != 0) {
         crm_warn("Cluster configuration not found: %s", filename);
         return NULL;
     }
 
     root = filename2xml(filename);
     if (root == NULL) {
         crm_err("%s exists but does NOT contain valid XML. ", filename);
         crm_warn("Continuing but %s will NOT used.", filename);
 
     } else if (validate_cib_digest(root, sigfile) == FALSE) {
         crm_err("Checksum of %s failed!  Configuration contents ignored!", filename);
         crm_err("Usually this is caused by manual changes, "
                 "please refer to http://clusterlabs.org/wiki/FAQ#cib_changes_detected");
         crm_warn("Continuing but %s will NOT used.", filename);
         free_xml(root);
         root = NULL;
 
         if (archive_invalid) {
             /* Archive the original files so the contents are not lost */
             cib_rename(filename, NULL);
             cib_rename(sigfile, NULL);
         }
     }
     return root;
 }
 
 static int cib_archive_filter(const struct dirent * a)
 {
     int rc = 0;
     /* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */
     struct stat s;
     char *a_path = g_strdup_printf("%s/%s", cib_root, a->d_name);
 
     if(stat(a_path, &s) != 0) {
         rc = errno;
         crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_strerror(rc), rc);
         rc = 0;
 
     } else if ((s.st_mode & S_IFREG) != S_IFREG) {
         crm_trace("%s - wrong type (%d)", a->d_name, a->d_type);
 
     } else if(strstr(a->d_name, "cib-") != a->d_name) {
         crm_trace("%s - wrong prefix", a->d_name);
 
     } else if(strstr(a->d_name, ".sig") != NULL) {
         crm_trace("%s - wrong suffix", a->d_name);
 
     } else {
         crm_debug("%s - candidate", a->d_name);
         rc = 1;
     }
 
     free(a_path);
     return rc;
 }
 
 static int cib_archive_sort(const struct dirent ** a, const struct dirent **b)
 {
     /* Order by creation date - most recently created file first */
     int rc = 0;
     struct stat buf;
 
     time_t a_age = 0;
     time_t b_age = 0;
 
     char *a_path = g_strdup_printf("%s/%s", cib_root, a[0]->d_name);
     char *b_path = g_strdup_printf("%s/%s", cib_root, b[0]->d_name);
 
     if(stat(a_path, &buf) == 0) {
         a_age = buf.st_ctime;
     }
     if(stat(b_path, &buf) == 0) {
         b_age = buf.st_ctime;
     }
 
     free(a_path);
     free(b_path);
 
     if(a_age > b_age) {
         rc = 1;
     } else if(a_age < b_age) {
         rc = -1;
     }
 
     crm_trace("%s (%u) vs. %s (%u) : %d", a[0]->d_name, a_age, b[0]->d_name, b_age, rc);
     return rc;
 }
 
 xmlNode *
 readCibXmlFile(const char *dir, const char *file, gboolean discard_status)
 {
     struct dirent **namelist = NULL;
 
     int lpc = 0;
     char *sigfile = NULL;
     char *filename = NULL;
     const char *name = NULL;
     const char *value = NULL;
     const char *validation = NULL;
     const char *use_valgrind = getenv("PCMK_valgrind_enabled");
 
     xmlNode *root = NULL;
     xmlNode *status = NULL;
 
     if (!crm_is_writable(dir, file, CRM_DAEMON_USER, NULL, FALSE)) {
         cib_status = -EACCES;
         return NULL;
     }
 
     filename = crm_concat(dir, file, '/');
     sigfile = crm_concat(filename, "sig", '.');
 
     cib_status = pcmk_ok;
     root = retrieveCib(filename, sigfile, TRUE);
     free(filename);
     free(sigfile);
 
     if (root == NULL) {
         crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root);
         lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort);
         if (lpc < 0) {
             crm_perror(LOG_NOTICE, "scandir(%s) failed", cib_root);
         }
     }
 
     while (root == NULL && lpc > 1) {
         crm_debug("Testing %d candidates", lpc);
 
         lpc--;
 
         filename = g_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name);
         sigfile = crm_concat(filename, "sig", '.');
 
         root = retrieveCib(filename, sigfile, FALSE);
         if(root) {
             crm_notice("Continuing with last valid configuration archive: %s", filename);
         }
 
         free(namelist[lpc]);
         free(filename);
         free(sigfile);
     }
     free(namelist);
 
     if (root == NULL) {
         root = createEmptyCib();
         crm_xml_add(root, XML_ATTR_GENERATION, "0");
         crm_xml_add(root, XML_ATTR_NUMUPDATES, "0");
         crm_xml_add(root, XML_ATTR_GENERATION_ADMIN, "0");
         crm_xml_add(root, XML_ATTR_VALIDATION, LATEST_SCHEMA_VERSION);
         crm_warn("Continuing with an empty configuration.");
     }
 
     if (cib_writes_enabled && use_valgrind) {
         if (crm_is_true(use_valgrind) || strstr(use_valgrind, "cib")) {
             cib_writes_enabled = FALSE;
             crm_err("*********************************************************");
             crm_err("*** Disabling disk writes to avoid confusing Valgrind ***");
             crm_err("*********************************************************");
         }
     }
 
     status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE);
     if (discard_status && status != NULL) {
         /* strip out the status section if there is one */
         free_xml(status);
         status = NULL;
     }
     if (status == NULL) {
         create_xml_node(root, XML_CIB_TAG_STATUS);
     }
 
     /* Do this before DTD validation happens */
 
     /* fill in some defaults */
     name = XML_ATTR_GENERATION_ADMIN;
     value = crm_element_value(root, name);
     if (value == NULL) {
         crm_warn("No value for %s was specified in the configuration.", name);
         crm_warn("The reccomended course of action is to shutdown,"
                  " run crm_verify and fix any errors it reports.");
         crm_warn("We will default to zero and continue but may get"
                  " confused about which configuration to use if"
                  " multiple nodes are powered up at the same time.");
         crm_xml_add_int(root, name, 0);
     }
 
     name = XML_ATTR_GENERATION;
     value = crm_element_value(root, name);
     if (value == NULL) {
         crm_xml_add_int(root, name, 0);
     }
 
     name = XML_ATTR_NUMUPDATES;
     value = crm_element_value(root, name);
     if (value == NULL) {
         crm_xml_add_int(root, name, 0);
     }
 
     /* unset these and require the DC/CCM to update as needed */
     xml_remove_prop(root, XML_ATTR_DC_UUID);
 
     if (discard_status) {
         crm_log_xml_trace(root, "[on-disk]");
     }
 
     validation = crm_element_value(root, XML_ATTR_VALIDATION);
     if (validate_xml(root, NULL, TRUE) == FALSE) {
         crm_err("CIB does not validate with %s", crm_str(validation));
         cib_status = -pcmk_err_dtd_validation;
 
     } else if (validation == NULL) {
         int version = 0;
 
         update_validation(&root, &version, FALSE, FALSE);
         if (version > 0) {
             crm_notice("Enabling %s validation on"
                        " the existing (sane) configuration", get_schema_name(version));
         } else {
             crm_err("CIB does not validate with any known DTD or schema");
             cib_status = -pcmk_err_dtd_validation;
         }
     }
 
     return root;
 }
 
 /*
  * The caller should never free the return value
  */
 xmlNode *
 get_the_CIB(void)
 {
     return the_cib;
 }
 
 gboolean
 uninitializeCib(void)
 {
     xmlNode *tmp_cib = the_cib;
 
     if (tmp_cib == NULL) {
         crm_debug("The CIB has already been deallocated.");
         return FALSE;
     }
 
     initialized = FALSE;
     the_cib = NULL;
     node_search = NULL;
     resource_search = NULL;
     constraint_search = NULL;
     status_search = NULL;
 
     crm_debug("Deallocating the CIB.");
 
     free_xml(tmp_cib);
 
     crm_debug("The CIB has been deallocated.");
 
     return TRUE;
 }
 
 /*
  * This method will not free the old CIB pointer or the new one.
  * We rely on the caller to have saved a pointer to the old CIB
  *   and to free the old/bad one depending on what is appropriate.
  */
 gboolean
 initializeCib(xmlNode * new_cib)
 {
     if (new_cib == NULL) {
         return FALSE;
     }
 
     the_cib = new_cib;
     initialized = TRUE;
     return TRUE;
 }
 
 static void
 sync_directory(const char *name)
 {
     int fd = 0;
     DIR *directory = NULL;
 
     directory = opendir(name);
     if (directory == NULL) {
         crm_perror(LOG_ERR, "Could not open %s for syncing", name);
         return;
     }
 
     fd = dirfd(directory);
     if (fd < 0) {
         crm_perror(LOG_ERR, "Could not obtain file descriptor for %s", name);
 
     } else if (fsync(fd) < 0) {
         crm_perror(LOG_ERR, "Could not sync %s", name);
     }
 
     if (closedir(directory) < 0) {
         crm_perror(LOG_ERR, "Could not close %s after fsync", name);
     }
 }
 
 /*
  * This method will free the old CIB pointer on success and the new one
  * on failure.
  */
 int
 activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
 {
     xmlNode *saved_cib = the_cib;
 
     CRM_ASSERT(new_cib != saved_cib);
     if (initializeCib(new_cib) == FALSE) {
         free_xml(new_cib);
         crm_err("Ignoring invalid or NULL CIB");
 
         if (saved_cib != NULL) {
             crm_warn("Reverting to last known CIB");
             if (initializeCib(saved_cib) == FALSE) {
                 /* oh we are so dead  */
                 crm_crit("Couldn't re-initialize the old CIB!");
                 exit(1);
             }
 
         } else {
             crm_crit("Could not write out new CIB and no saved" " version to revert to");
         }
         return -ENODATA;
     }
 
     free_xml(saved_cib);
     if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) {
         crm_debug("Triggering CIB write for %s op", op);
         mainloop_set_trigger(cib_writer);
     }
 
     return pcmk_ok;
 }
 
 static void
 cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
 {
     if (signo) {
         crm_notice("Disk write process terminated with signal %d (pid=%d, core=%d)", signo, pid,
                    core);
 
     } else  {
         do_crm_log(exitcode == 0 ? LOG_TRACE : LOG_ERR, "Disk write process exited (pid=%d, rc=%d)",
                    pid, exitcode);
     }
 
     if (exitcode != 0 && cib_writes_enabled) {
         crm_err("Disabling disk writes after write failure");
         cib_writes_enabled = FALSE;
     }
 
     mainloop_trigger_complete(cib_writer);
 }
 
 int
 write_cib_contents(gpointer p)
 {
     int exit_rc = pcmk_ok;
     char *digest = NULL;
     xmlNode *cib_status_root = NULL;
 
     xmlNode *cib_local = NULL;
     xmlNode *cib_tmp = NULL;
 
     int tmp_cib_fd = 0;
     int tmp_digest_fd = 0;
     char *tmp_cib = NULL;
     char *tmp_digest = NULL;
 
     char *digest_file = NULL;
     char *primary_file = NULL;
 
     char *backup_file = NULL;
     char *backup_digest = NULL;
 
     const char *epoch = NULL;
     const char *admin_epoch = NULL;
 
     if (p) {
         /* Synchronous write out */
         cib_local = copy_xml(p);
 
     } else {
         int pid = 0;
         int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0);
 
         /* Turn it off before the fork() to avoid:
          * - 2 processes writing to the same shared mem
          * - the child needing to disable it
          *   (which would close it from underneath the parent)
          * This way, the shared mem files are already closed
          */
         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
 
         pid = fork();
         if (pid < 0) {
             crm_perror(LOG_ERR, "Disabling disk writes after fork failure");
             cib_writes_enabled = FALSE;
             return FALSE;
         }
 
         if (pid) {
             /* Parent */
             mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete);
             if (bb_state == QB_LOG_STATE_ENABLED) {
                 /* Re-enable now that it it safe */
                 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
             }
 
             return -1;          /* -1 means 'still work to do' */
         }
 
         /* A-synchronous write out after a fork() */
 
         /* In theory we can scribble on "the_cib" here and not affect the parent
          * But lets be safe anyway
          */
         cib_local = copy_xml(the_cib);
     }
 
     epoch = crm_element_value(cib_local, XML_ATTR_GENERATION);
     admin_epoch = crm_element_value(cib_local, XML_ATTR_GENERATION_ADMIN);
 
     primary_file = crm_concat(cib_root, "cib.xml", '/');
     digest_file = crm_concat(primary_file, "sig", '.');
 
     /* Always write out with num_updates=0 */
     crm_xml_add(cib_local, XML_ATTR_NUMUPDATES, "0");
 
     /* check the admin didnt modify it underneath us */
-    if (validate_on_disk_cib(primary_file, NULL) == FALSE) {
+    if (on_disk_cib_corrupt(primary_file) == FALSE
+        && validate_on_disk_cib(primary_file, NULL) == FALSE) {
         crm_err("%s was manually modified while the cluster was active!", primary_file);
         exit_rc = pcmk_err_cib_modified;
         goto cleanup;
 
     } else {
         int rc = 0;
         int seq = get_last_sequence(cib_root, CIB_SERIES);
 
         backup_file = generate_series_filename(cib_root, CIB_SERIES, seq, CIB_SERIES_BZIP);
         backup_digest = crm_concat(backup_file, "sig", '.');
 
         unlink(backup_file);
         unlink(backup_digest);
 
         rc = link(primary_file, backup_file);
         if (rc < 0) {
             rc = errno;
             switch(rc) {
                 case ENOENT:
                     /* No file to back up */
                     goto writeout;
                     break;
                 default:
                     exit_rc = pcmk_err_cib_backup;
                     crm_err("Cannot link %s to %s: %s (%d)", primary_file, backup_file, pcmk_strerror(rc), rc);
             }
             goto cleanup;
         }
 
         rc = link(digest_file, backup_digest);
         if (rc < 0 && errno != ENOENT) {
             exit_rc = pcmk_err_cib_backup;
             crm_perror(LOG_ERR, "Cannot link %s to %s", digest_file, backup_digest);
             goto cleanup;
         }
         write_last_sequence(cib_root, CIB_SERIES, seq + 1, CIB_SERIES_MAX);
         sync_directory(cib_root);
 
         crm_info("Archived previous version as %s", backup_file);
     }
 
   writeout:
     /* Given that we discard the status section on startup
      *   there is no point writing it out in the first place
      *   since users just get confused by it
      *
      * So delete the status section before we write it out
      */
     crm_debug("Writing CIB to disk");
     if (p == NULL) {
         cib_status_root = find_xml_node(cib_local, XML_CIB_TAG_STATUS, TRUE);
         CRM_LOG_ASSERT(cib_status_root != NULL);
 
         if (cib_status_root != NULL) {
             free_xml(cib_status_root);
         }
     }
 
     tmp_cib = g_strdup_printf("%s/cib.XXXXXX", cib_root);
     tmp_digest = g_strdup_printf("%s/cib.XXXXXX", cib_root);
 
     umask(S_IWGRP | S_IWOTH | S_IROTH);
 
     tmp_cib_fd = mkstemp(tmp_cib);
     if (tmp_cib_fd < 0 || write_xml_fd(cib_local, tmp_cib, tmp_cib_fd, FALSE) <= 0) {
         crm_err("Changes couldn't be written to %s", tmp_cib);
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
 
     /* Must calculate the digest after writing as write_xml_file() updates the last-written field */
     digest = calculate_on_disk_digest(cib_local);
     crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
              admin_epoch ? admin_epoch : "0", epoch ? epoch : "0", digest);
 
     tmp_digest_fd = mkstemp(tmp_digest);
     if (tmp_digest_fd < 0 || write_cib_digest(cib_local, tmp_digest, tmp_digest_fd, digest) <= 0) {
         crm_err("Digest couldn't be written to %s", tmp_digest);
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
     crm_debug("Wrote digest %s to disk", digest);
     cib_tmp = retrieveCib(tmp_cib, tmp_digest, FALSE);
     CRM_ASSERT(cib_tmp != NULL);
     sync_directory(cib_root);
 
     crm_debug("Activating %s", tmp_cib);
     cib_rename(tmp_cib, primary_file);
     cib_rename(tmp_digest, digest_file);
     sync_directory(cib_root);
 
   cleanup:
     free(backup_digest);
     free(primary_file);
     free(backup_file);
     free(digest_file);
     free(digest);
     free(tmp_digest);
     free(tmp_cib);
 
     free_xml(cib_tmp);
     free_xml(cib_local);
 
     if (p == NULL) {
         /* exit() could potentially affect the parent by closing things it shouldn't
          * Use _exit instead
          */
         _exit(exit_rc);
     }
     return exit_rc;
 }
 
 void
 GHFunc_count_peers(gpointer key, gpointer value, gpointer user_data)
 {
     int *active = user_data;
 
     if (safe_str_eq(value, ONLINESTATUS)) {
         (*active)++;
 
     } else if (safe_str_eq(value, JOINSTATUS)) {
         (*active)++;
     }
 }
diff --git a/crmd/lrm.c b/crmd/lrm.c
index 3875d22a3a..4d08ab7c6f 100644
--- a/crmd/lrm.c
+++ b/crmd/lrm.c
@@ -1,2123 +1,2133 @@
 /*
  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 
 #include <crmd.h>
 #include <crmd_fsa.h>
 #include <crmd_messages.h>
 #include <crmd_callbacks.h>
 #include <crmd_lrm.h>
 
 #define START_DELAY_THRESHOLD 5 * 60 * 1000
 #define MAX_LRM_REG_FAILS 30
 
 struct delete_event_s {
     int rc;
     const char *rsc;
     lrm_state_t *lrm_state;
 };
 
 gboolean process_lrm_event(lrm_state_t * lrm_state, lrmd_event_data_t * op);
 static gboolean is_rsc_active(lrm_state_t * lrm_state, const char *rsc_id);
 static gboolean build_active_RAs(lrm_state_t * lrm_state, xmlNode * rsc_list);
 static gboolean stop_recurring_actions(gpointer key, gpointer value, gpointer user_data);
 static int delete_rsc_status(lrm_state_t * lrm_state, const char *rsc_id, int call_options,
                              const char *user_name);
 
 static lrmd_event_data_t *construct_op(lrm_state_t * lrm_state, xmlNode * rsc_op,
                                        const char *rsc_id, const char *operation);
 static void do_lrm_rsc_op(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *operation,
                           xmlNode * msg, xmlNode * request);
 
 void send_direct_ack(const char *to_host, const char *to_sys,
                      lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, const char *rsc_id);
 
 static gboolean lrm_state_verify_stopped(lrm_state_t * lrm_state, enum crmd_fsa_state cur_state,
                                          int log_level);
 
 static void
 lrm_connection_destroy(void)
 {
     if (is_set(fsa_input_register, R_LRM_CONNECTED)) {
         crm_crit("LRM Connection failed");
         register_fsa_input(C_FSA_INTERNAL, I_ERROR, NULL);
         clear_bit(fsa_input_register, R_LRM_CONNECTED);
 
     } else {
         crm_info("LRM Connection disconnected");
     }
 
 }
 
 static char *
 make_stop_id(const char *rsc, int call_id)
 {
     char *op_id = NULL;
 
     op_id = calloc(1, strlen(rsc) + 34);
     if (op_id != NULL) {
         snprintf(op_id, strlen(rsc) + 34, "%s:%d", rsc, call_id);
     }
     return op_id;
 }
 
 static void
 copy_instance_keys(gpointer key, gpointer value, gpointer user_data)
 {
     if (strstr(key, CRM_META "_") == NULL) {
         g_hash_table_replace(user_data, strdup((const char *)key), strdup((const char *)value));
     }
 }
 
 static void
 copy_meta_keys(gpointer key, gpointer value, gpointer user_data)
 {
     if (strstr(key, CRM_META "_") != NULL) {
         g_hash_table_replace(user_data, strdup((const char *)key), strdup((const char *)value));
     }
 }
 
 static void
 update_history_cache(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
 {
     int target_rc = 0;
     rsc_history_t *entry = NULL;
 
     if (op->rsc_deleted) {
         crm_debug("Purged history for '%s' after %s", op->rsc_id, op->op_type);
         delete_rsc_status(lrm_state, op->rsc_id, cib_quorum_override, NULL);
         return;
     }
 
     if (safe_str_eq(op->op_type, RSC_NOTIFY)) {
         return;
     }
 
     crm_debug("Updating history for '%s' with %s op", op->rsc_id, op->op_type);
 
     entry = g_hash_table_lookup(lrm_state->resource_history, op->rsc_id);
     if (entry == NULL && rsc) {
         entry = calloc(1, sizeof(rsc_history_t));
         entry->id = strdup(op->rsc_id);
         g_hash_table_insert(lrm_state->resource_history, entry->id, entry);
 
         entry->rsc.id = entry->id;
         entry->rsc.type = strdup(rsc->type);
         entry->rsc.class = strdup(rsc->class);
         if (rsc->provider) {
             entry->rsc.provider = strdup(rsc->provider);
         } else {
             entry->rsc.provider = NULL;
         }
 
     } else if (entry == NULL) {
         crm_info("Resource %s no longer exists, not updating cache", op->rsc_id);
         return;
     }
 
     entry->last_callid = op->call_id;
     target_rc = rsc_op_expected_rc(op);
     if (op->op_status == PCMK_LRM_OP_CANCELLED) {
         if (op->interval > 0) {
             GList *gIter, *gIterNext;
 
             crm_trace("Removing cancelled recurring op: %s_%s_%d", op->rsc_id, op->op_type,
                       op->interval);
 
             for (gIter = entry->recurring_op_list; gIter != NULL; gIter = gIterNext) {
                 lrmd_event_data_t *existing = gIter->data;
 
                 gIterNext = gIter->next;
 
                 if (safe_str_eq(op->rsc_id, existing->rsc_id)
                     && safe_str_eq(op->op_type, existing->op_type)
                     && op->interval == existing->interval) {
                     lrmd_free_event(existing);
                     entry->recurring_op_list = g_list_delete_link(entry->recurring_op_list, gIter);
                 }
             }
             return;
 
         } else {
             crm_trace("Skipping %s_%s_%d rc=%d, status=%d", op->rsc_id, op->op_type, op->interval,
                       op->rc, op->op_status);
         }
 
     } else if (did_rsc_op_fail(op, target_rc)) {
         /* We must store failed monitors here
          * - otherwise the block below will cause them to be forgetten them when a stop happens
          */
         if (entry->failed) {
             lrmd_free_event(entry->failed);
         }
         entry->failed = lrmd_copy_event(op);
 
     } else if (op->interval == 0) {
         if (entry->last) {
             lrmd_free_event(entry->last);
         }
         entry->last = lrmd_copy_event(op);
 
         if (op->params &&
             (safe_str_eq(CRMD_ACTION_START, op->op_type) ||
              safe_str_eq(CRMD_ACTION_STATUS, op->op_type))) {
 
             if (entry->stop_params) {
                 g_hash_table_destroy(entry->stop_params);
             }
             entry->stop_params = g_hash_table_new_full(crm_str_hash,
                                                        g_str_equal, g_hash_destroy_str,
                                                        g_hash_destroy_str);
 
             g_hash_table_foreach(op->params, copy_instance_keys, entry->stop_params);
         }
     }
 
     if (op->interval > 0) {
         GListPtr iter = NULL;
 
         for(iter = entry->recurring_op_list; iter; iter = iter->next) {
             lrmd_event_data_t *o = iter->data;
 
             /* op->rsc_id is implied */
             if(op->interval == o->interval && strcmp(op->op_type, o->op_type) == 0) {
                 crm_trace("Removing existing recurring op entry: %s_%s_%d", op->rsc_id, op->op_type, op->interval);
                 entry->recurring_op_list = g_list_remove(entry->recurring_op_list, o);
                 break;
             }
         }
 
         crm_trace("Adding recurring op: %s_%s_%d", op->rsc_id, op->op_type, op->interval);
         entry->recurring_op_list = g_list_prepend(entry->recurring_op_list, lrmd_copy_event(op));
 
     } else if (entry->recurring_op_list && safe_str_eq(op->op_type, RSC_STATUS) == FALSE) {
         GList *gIter = entry->recurring_op_list;
 
         crm_trace("Dropping %d recurring ops because of: %s_%s_%d",
                   g_list_length(gIter), op->rsc_id, op->op_type, op->interval);
         for (; gIter != NULL; gIter = gIter->next) {
             lrmd_free_event(gIter->data);
         }
         g_list_free(entry->recurring_op_list);
         entry->recurring_op_list = NULL;
     }
 }
 
 void
 lrm_op_callback(lrmd_event_data_t * op)
 {
     const char *nodename = NULL;
     lrm_state_t *lrm_state = NULL;
 
     CRM_CHECK(op != NULL, return);
 
     /* determine the node name for this connection. */
     nodename = op->remote_nodename ? op->remote_nodename : fsa_our_uname;
 
     if (op->type == lrmd_event_disconnect && (safe_str_eq(nodename, fsa_our_uname))) {
         /* if this is the local lrmd ipc connection, set the right bits in the
          * crmd when the connection goes down */
         lrm_connection_destroy();
         return;
     } else if (op->type != lrmd_event_exec_complete) {
         /* we only need to process execution results */
         return;
     }
 
     lrm_state = lrm_state_find(nodename);
     CRM_ASSERT(lrm_state != NULL);
 
     process_lrm_event(lrm_state, op);
 }
 
 /*	 A_LRM_CONNECT	*/
 void
 do_lrm_control(long long action,
                enum crmd_fsa_cause cause,
                enum crmd_fsa_state cur_state,
                enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     /* This only pertains to local lrmd connections.  Remote connections are handled as
      * resources within the pengine.  Connecting and disconnecting from remote lrmd instances
      * handled differently than the local. */
 
     lrm_state_t *lrm_state = NULL;
 
     if(fsa_our_uname == NULL) {
         return; /* Nothing to do */
     }
     lrm_state = lrm_state_find_or_create(fsa_our_uname);
     if (lrm_state == NULL) {
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
         return;
     }
 
     if (action & A_LRM_DISCONNECT) {
         if (lrm_state_verify_stopped(lrm_state, cur_state, LOG_INFO) == FALSE) {
             if (action == A_LRM_DISCONNECT) {
                 crmd_fsa_stall(FALSE);
                 return;
             }
         }
 
         clear_bit(fsa_input_register, R_LRM_CONNECTED);
         crm_info("Disconnecting from the LRM");
         lrm_state_disconnect(lrm_state);
         lrm_state_reset_tables(lrm_state);
         crm_notice("Disconnected from the LRM");
     }
 
     if (action & A_LRM_CONNECT) {
         int ret = pcmk_ok;
 
         crm_debug("Connecting to the LRM");
         ret = lrm_state_ipc_connect(lrm_state);
 
         if (ret != pcmk_ok) {
             if (lrm_state->num_lrm_register_fails < MAX_LRM_REG_FAILS) {
                 crm_warn("Failed to sign on to the LRM %d"
                          " (%d max) times", lrm_state->num_lrm_register_fails, MAX_LRM_REG_FAILS);
 
                 crm_timer_start(wait_timer);
                 crmd_fsa_stall(FALSE);
                 return;
             }
         }
 
         if (ret != pcmk_ok) {
             crm_err("Failed to sign on to the LRM %d" " (max) times",
                     lrm_state->num_lrm_register_fails);
             register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
             return;
         }
 
         set_bit(fsa_input_register, R_LRM_CONNECTED);
         crm_info("LRM connection established");
     }
 
     if (action & ~(A_LRM_CONNECT | A_LRM_DISCONNECT)) {
         crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__);
     }
 }
 
 static gboolean
 lrm_state_verify_stopped(lrm_state_t * lrm_state, enum crmd_fsa_state cur_state, int log_level)
 {
     int counter = 0;
     gboolean rc = TRUE;
     const char *when = "lrm disconnect";
 
     GHashTableIter gIter;
     const char *key = NULL;
     rsc_history_t *entry = NULL;
     struct recurring_op_s *pending = NULL;
 
     crm_debug("Checking for active resources before exit");
 
     if (cur_state == S_TERMINATE) {
         log_level = LOG_ERR;
         when = "shutdown";
 
     } else if (is_set(fsa_input_register, R_SHUTDOWN)) {
         when = "shutdown... waiting";
     }
 
     if (lrm_state->pending_ops && lrm_state_is_connected(lrm_state) == TRUE) {
         guint removed = g_hash_table_foreach_remove(
             lrm_state->pending_ops, stop_recurring_actions, lrm_state);
 
         crm_notice("Stopped %u recurring operations at %s (%u ops remaining)",
                    removed, when, g_hash_table_size(lrm_state->pending_ops));
     }
 
     if (lrm_state->pending_ops) {
         g_hash_table_iter_init(&gIter, lrm_state->pending_ops);
         while (g_hash_table_iter_next(&gIter, NULL, (void **)&pending)) {
             /* Ignore recurring actions in the shutdown calculations */
             if (pending->interval == 0) {
                 counter++;
             }
         }
     }
 
     if (counter > 0) {
         do_crm_log(log_level, "%d pending LRM operations at %s", counter, when);
 
         if (cur_state == S_TERMINATE || !is_set(fsa_input_register, R_SENT_RSC_STOP)) {
             g_hash_table_iter_init(&gIter, lrm_state->pending_ops);
             while (g_hash_table_iter_next(&gIter, (gpointer*)&key, (gpointer*)&pending)) {
                 do_crm_log(log_level, "Pending action: %s (%s)", key, pending->op_key);
             }
 
         } else {
             rc = FALSE;
         }
         return rc;
     }
 
     if (lrm_state->resource_history == NULL) {
         return rc;
     }
 
     if (cur_state == S_TERMINATE || is_set(fsa_input_register, R_SHUTDOWN)) {
         /* At this point we're not waiting, we're just shutting down */
         when = "shutdown";
     }
 
     counter = 0;
     g_hash_table_iter_init(&gIter, lrm_state->resource_history);
     while (g_hash_table_iter_next(&gIter, NULL, (gpointer*)&entry)) {
         if (is_rsc_active(lrm_state, entry->id) == FALSE) {
             continue;
         }
 
         counter++;
         crm_trace("Found %s active", entry->id);
         if (lrm_state->pending_ops) {
             GHashTableIter hIter;
 
             g_hash_table_iter_init(&hIter, lrm_state->pending_ops);
             while (g_hash_table_iter_next(&hIter, (gpointer*)&key, (gpointer*)&pending)) {
                 if (safe_str_eq(entry->id, pending->rsc_id)) {
                     crm_notice("%sction %s (%s) incomplete at %s",
                                pending->interval == 0 ? "A" : "Recurring a",
                                key, pending->op_key, when);
                 }
             }
         }
     }
 
     if (counter) {
         crm_err("%d resources were active at %s.", counter, when);
     }
 
     return rc;
 }
 
 static char *
 get_rsc_metadata(const char *type, const char *class, const char *provider)
 {
     int rc = 0;
     char *metadata = NULL;
 
     /* Always use a local connection for this operation */
     lrm_state_t *lrm_state = lrm_state_find(fsa_our_uname);
 
     CRM_CHECK(type != NULL, return NULL);
     CRM_CHECK(class != NULL, return NULL);
     CRM_CHECK(lrm_state != NULL, return NULL);
 
     if (provider == NULL) {
         provider = "heartbeat";
     }
 
     crm_trace("Retreiving metadata for %s::%s:%s", type, class, provider);
     rc = lrm_state_get_metadata(lrm_state, class, provider, type, &metadata, 0);
 
     if (metadata) {
         /* copy the metadata because the LRM likes using
          *   g_alloc instead of cl_malloc
          */
         char *m_copy = strdup(metadata);
 
         g_free(metadata);
         metadata = m_copy;
 
     } else {
         crm_warn("No metadata found for %s::%s:%s: %s (%d)", type, class, provider, pcmk_strerror(rc), rc);
     }
 
     return metadata;
 }
 
 typedef struct reload_data_s {
     char *key;
     char *metadata;
     time_t last_query;
     gboolean can_reload;
     GListPtr restart_list;
 } reload_data_t;
 
 static void
 g_hash_destroy_reload(gpointer data)
 {
     reload_data_t *reload = data;
 
     free(reload->key);
     free(reload->metadata);
     g_list_free_full(reload->restart_list, free);
     free(reload);
 }
 
 GHashTable *reload_hash = NULL;
 static GListPtr
 get_rsc_restart_list(lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
 {
     int len = 0;
     char *key = NULL;
     char *copy = NULL;
     const char *value = NULL;
     const char *provider = NULL;
 
     xmlNode *param = NULL;
     xmlNode *params = NULL;
     xmlNode *actions = NULL;
     xmlNode *metadata = NULL;
 
     time_t now = time(NULL);
     reload_data_t *reload = NULL;
 
     if (reload_hash == NULL) {
         reload_hash = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, g_hash_destroy_reload);
     }
 
     provider = rsc->provider;
     if (provider == NULL) {
         provider = "heartbeat";
     }
 
     len = strlen(rsc->type) + strlen(rsc->class) + strlen(provider) + 4;
     key = malloc(len);
     if(key) {
         snprintf(key, len, "%s::%s:%s", rsc->type, rsc->class, provider);
         reload = g_hash_table_lookup(reload_hash, key);
     }
 
     if (reload && ((now - 9) > reload->last_query)
         && safe_str_eq(op->op_type, RSC_START)) {
         reload = NULL;          /* re-query */
     }
 
     if (reload == NULL) {
         xmlNode *action = NULL;
 
         reload = calloc(1, sizeof(reload_data_t));
         g_hash_table_replace(reload_hash, key, reload);
 
         reload->last_query = now;
         reload->key = key;
         key = NULL;
         reload->metadata = get_rsc_metadata(rsc->type, rsc->class, provider);
 
         if(reload->metadata == NULL) {
             goto cleanup;
         }
 
         metadata = string2xml(reload->metadata);
         if (metadata == NULL) {
             crm_err("Metadata for %s::%s:%s is not valid XML",
                     rsc->provider, rsc->class, rsc->type);
             goto cleanup;
         }
 
         actions = find_xml_node(metadata, "actions", TRUE);
 
         for (action = __xml_first_child(actions); action != NULL; action = __xml_next(action)) {
             if (crm_str_eq((const char *)action->name, "action", TRUE)) {
                 value = crm_element_value(action, "name");
                 if (safe_str_eq("reload", value)) {
                     reload->can_reload = TRUE;
                     break;
                 }
             }
         }
 
         if (reload->can_reload == FALSE) {
             goto cleanup;
         }
 
         params = find_xml_node(metadata, "parameters", TRUE);
         for (param = __xml_first_child(params); param != NULL; param = __xml_next(param)) {
             if (crm_str_eq((const char *)param->name, "parameter", TRUE)) {
                 value = crm_element_value(param, "unique");
                 if (crm_is_true(value)) {
                     value = crm_element_value(param, "name");
                     if (value == NULL) {
                         crm_err("%s: NULL param", key);
                         continue;
                     }
                     crm_debug("Attr %s is not reloadable", value);
                     copy = strdup(value);
                     CRM_CHECK(copy != NULL, continue);
                     reload->restart_list = g_list_append(reload->restart_list, copy);
                 }
             }
         }
     }
 
   cleanup:
     free(key);
     free_xml(metadata);
     return reload->restart_list;
 }
 
 static void
 append_restart_list(lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, xmlNode * update,
                     const char *version)
 {
     int len = 0;
     char *list = NULL;
     char *digest = NULL;
     const char *value = NULL;
     xmlNode *restart = NULL;
     GListPtr restart_list = NULL;
     GListPtr lpc = NULL;
 
     if (op->interval > 0) {
         /* monitors are not reloadable */
         return;
 
     } else if (op->params == NULL) {
         crm_debug("%s has no parameters", ID(update));
         return;
 
     } else if (rsc == NULL) {
         return;
 
     } else if (crm_str_eq(CRMD_ACTION_STOP, op->op_type, TRUE)) {
         /* Stopped resources don't need to be reloaded */
         return;
 
     } else if (compare_version("1.0.8", version) > 0) {
         /* Caller version does not support reloads */
         return;
     }
 
     restart_list = get_rsc_restart_list(rsc, op);
     if (restart_list == NULL) {
         /* Resource does not support reloads */
         return;
     }
 
     restart = create_xml_node(NULL, XML_TAG_PARAMS);
     for (lpc = restart_list; lpc != NULL; lpc = lpc->next) {
         const char *param = (const char *)lpc->data;
 
         int start = len;
 
         CRM_CHECK(param != NULL, continue);
         value = g_hash_table_lookup(op->params, param);
         if (value != NULL) {
             crm_xml_add(restart, param, value);
         }
         len += strlen(param) + 2;
         list = realloc(list, len + 1);
         sprintf(list + start, " %s ", param);
     }
 
     digest = calculate_operation_digest(restart, version);
     crm_xml_add(update, XML_LRM_ATTR_OP_RESTART, list);
     crm_xml_add(update, XML_LRM_ATTR_RESTART_DIGEST, digest);
 
     crm_trace("%s: %s, %s", rsc->id, digest, list);
     crm_log_xml_trace(restart, "restart digest source");
 
     free_xml(restart);
     free(digest);
     free(list);
 }
 
 static gboolean
 build_operation_update(xmlNode * parent, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op,
                        const char *src)
 {
     int target_rc = 0;
     xmlNode *xml_op = NULL;
     const char *caller_version = CRM_FEATURE_SET;
 
     if (op == NULL) {
         return FALSE;
 
     } else if (AM_I_DC) {
 
     } else if (fsa_our_dc_version != NULL) {
         caller_version = fsa_our_dc_version;
     } else if (op->params == NULL) {
         caller_version = fsa_our_dc_version;
     } else {
         /* there is a small risk in formerly mixed clusters that
          *   it will be sub-optimal.
          * however with our upgrade policy, the update we send
          *   should still be completely supported anyway
          */
         caller_version = g_hash_table_lookup(op->params, XML_ATTR_CRM_VERSION);
         crm_debug("Falling back to operation originator version: %s", caller_version);
     }
 
     target_rc = rsc_op_expected_rc(op);
     xml_op = create_operation_update(parent, op, caller_version, target_rc, src, LOG_DEBUG);
     crm_xml_add(xml_op, XML_LRM_ATTR_TARGET, fsa_our_uname); /* For context during triage */
 
     if (xml_op) {
         append_restart_list(rsc, op, xml_op, caller_version);
     }
     return TRUE;
 }
 
 static gboolean
 is_rsc_active(lrm_state_t * lrm_state, const char *rsc_id)
 {
     rsc_history_t *entry = NULL;
 
     entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
     if (entry == NULL || entry->last == NULL) {
         return FALSE;
     }
 
     crm_trace("Processing %s: %s.%d=%d",
               rsc_id, entry->last->op_type, entry->last->interval, entry->last->rc);
     if (entry->last->rc == PCMK_OCF_OK && safe_str_eq(entry->last->op_type, CRMD_ACTION_STOP)) {
         return FALSE;
 
     } else if (entry->last->rc == PCMK_OCF_OK
                && safe_str_eq(entry->last->op_type, CRMD_ACTION_MIGRATE)) {
         /* a stricter check is too complex...
          * leave that to the PE
          */
         return FALSE;
 
     } else if (entry->last->rc == PCMK_OCF_NOT_RUNNING) {
         return FALSE;
 
     } else if (entry->last->interval == 0 && entry->last->rc == PCMK_OCF_NOT_CONFIGURED) {
         /* Badly configured resources can't be reliably stopped */
         return FALSE;
     }
 
     return TRUE;
 }
 
 static gboolean
 build_active_RAs(lrm_state_t * lrm_state, xmlNode * rsc_list)
 {
     GHashTableIter iter;
     rsc_history_t *entry = NULL;
 
     g_hash_table_iter_init(&iter, lrm_state->resource_history);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&entry)) {
 
         GList *gIter = NULL;
         xmlNode *xml_rsc = create_xml_node(rsc_list, XML_LRM_TAG_RESOURCE);
 
         crm_xml_add(xml_rsc, XML_ATTR_ID, entry->id);
         crm_xml_add(xml_rsc, XML_ATTR_TYPE, entry->rsc.type);
         crm_xml_add(xml_rsc, XML_AGENT_ATTR_CLASS, entry->rsc.class);
         crm_xml_add(xml_rsc, XML_AGENT_ATTR_PROVIDER, entry->rsc.provider);
 
         if (entry->last && entry->last->params) {
             const char *container = g_hash_table_lookup(entry->last->params, CRM_META"_"XML_RSC_ATTR_CONTAINER);
             if (container) {
                 crm_trace("Resource %s is a part of container resource %s", entry->id, container);
                 crm_xml_add(xml_rsc, XML_RSC_ATTR_CONTAINER, container);
             }
         }
         build_operation_update(xml_rsc, &(entry->rsc), entry->last, __FUNCTION__);
         build_operation_update(xml_rsc, &(entry->rsc), entry->failed, __FUNCTION__);
         for (gIter = entry->recurring_op_list; gIter != NULL; gIter = gIter->next) {
             build_operation_update(xml_rsc, &(entry->rsc), gIter->data, __FUNCTION__);
         }
     }
 
     return FALSE;
 }
 
 xmlNode *
 do_lrm_query_internal(lrm_state_t * lrm_state, gboolean is_replace)
 {
     xmlNode *xml_result = NULL;
     xmlNode *xml_state = NULL;
     xmlNode *xml_data = NULL;
     xmlNode *rsc_list = NULL;
     const char *uuid = NULL;
 
     if (safe_str_eq(lrm_state->node_name, fsa_our_uname)) {
         crm_node_t *peer = crm_get_peer(0, lrm_state->node_name);
         xml_state = do_update_node_cib(peer, node_update_cluster|node_update_peer, NULL, __FUNCTION__);
         /* The next two lines shouldn't be necessary for newer DCs */
         crm_xml_add(xml_state, XML_NODE_JOIN_STATE, CRMD_JOINSTATE_MEMBER);
         crm_xml_add(xml_state, XML_NODE_EXPECTED, CRMD_JOINSTATE_MEMBER);
         uuid = fsa_our_uuid;
 
     } else {
         xml_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
         crm_xml_add(xml_state, XML_NODE_IS_REMOTE, "true");
         crm_xml_add(xml_state, XML_ATTR_ID, lrm_state->node_name);
         crm_xml_add(xml_state, XML_ATTR_UNAME, lrm_state->node_name);
         uuid = lrm_state->node_name;
     }
 
     xml_data = create_xml_node(xml_state, XML_CIB_TAG_LRM);
     crm_xml_add(xml_data, XML_ATTR_ID, uuid);
     rsc_list = create_xml_node(xml_data, XML_LRM_TAG_RESOURCES);
 
     /* Build a list of active (not always running) resources */
     build_active_RAs(lrm_state, rsc_list);
 
     xml_result = create_cib_fragment(xml_state, XML_CIB_TAG_STATUS);
     crm_log_xml_trace(xml_state, "Current state of the LRM");
     free_xml(xml_state);
 
     return xml_result;
 }
 
 xmlNode *
 do_lrm_query(gboolean is_replace, const char *node_name)
 {
     lrm_state_t *lrm_state = lrm_state_find(node_name);
 
     if (!lrm_state) {
         crm_err("Could not query lrm state for lrmd node %s", node_name);
         return NULL;
     }
     return do_lrm_query_internal(lrm_state, is_replace);
 }
 
 static void
 notify_deleted(lrm_state_t * lrm_state, ha_msg_input_t * input, const char *rsc_id, int rc)
 {
     lrmd_event_data_t *op = NULL;
     const char *from_sys = crm_element_value(input->msg, F_CRM_SYS_FROM);
     const char *from_host = crm_element_value(input->msg, F_CRM_HOST_FROM);
 
     crm_info("Notifying %s on %s that %s was%s deleted",
              from_sys, from_host, rsc_id, rc == pcmk_ok ? "" : " not");
 
     op = construct_op(lrm_state, input->xml, rsc_id, CRMD_ACTION_DELETE);
     CRM_ASSERT(op != NULL);
 
     if (rc == pcmk_ok) {
         op->op_status = PCMK_LRM_OP_DONE;
         op->rc = PCMK_OCF_OK;
     } else {
         op->op_status = PCMK_LRM_OP_ERROR;
         op->rc = PCMK_OCF_UNKNOWN_ERROR;
     }
 
     send_direct_ack(from_host, from_sys, NULL, op, rsc_id);
     lrmd_free_event(op);
 
     if (safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) {
         /* this isn't expected - trigger a new transition */
         time_t now = time(NULL);
         char *now_s = crm_itoa(now);
 
         crm_debug("Triggering a refresh after %s deleted %s from the LRM", from_sys, rsc_id);
 
         update_attr_delegate(fsa_cib_conn, cib_none, XML_CIB_TAG_CRMCONFIG, NULL, NULL, NULL, NULL,
                              "last-lrm-refresh", now_s, FALSE, NULL);
 
         free(now_s);
     }
 }
 
 static gboolean
 lrm_remove_deleted_rsc(gpointer key, gpointer value, gpointer user_data)
 {
     struct delete_event_s *event = user_data;
     struct pending_deletion_op_s *op = value;
 
     if (safe_str_eq(event->rsc, op->rsc)) {
         notify_deleted(event->lrm_state, op->input, event->rsc, event->rc);
         return TRUE;
     }
     return FALSE;
 }
 
 static gboolean
 lrm_remove_deleted_op(gpointer key, gpointer value, gpointer user_data)
 {
     const char *rsc = user_data;
     struct recurring_op_s *pending = value;
 
     if (safe_str_eq(rsc, pending->rsc_id)) {
         crm_info("Removing op %s:%d for deleted resource %s",
                  pending->op_key, pending->call_id, rsc);
         return TRUE;
     }
     return FALSE;
 }
 
 /*
  * Remove the rsc from the CIB
  *
  * Avoids refreshing the entire LRM section of this host
  */
 #define rsc_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']"
 
 static int
 delete_rsc_status(lrm_state_t * lrm_state, const char *rsc_id, int call_options,
                   const char *user_name)
 {
     char *rsc_xpath = NULL;
     int max = 0;
     int rc = pcmk_ok;
 
     CRM_CHECK(rsc_id != NULL, return -ENXIO);
 
     max = strlen(rsc_template) + strlen(rsc_id) + strlen(lrm_state->node_name) + 1;
     rsc_xpath = calloc(1, max);
     snprintf(rsc_xpath, max, rsc_template, lrm_state->node_name, rsc_id);
 
     rc = cib_internal_op(fsa_cib_conn, CIB_OP_DELETE, NULL, rsc_xpath,
                          NULL, NULL, call_options | cib_xpath, user_name);
 
     free(rsc_xpath);
     return rc;
 }
 
 static void
 delete_rsc_entry(lrm_state_t * lrm_state, ha_msg_input_t * input, const char *rsc_id,
                  GHashTableIter * rsc_gIter, int rc, const char *user_name)
 {
     struct delete_event_s event;
 
     CRM_CHECK(rsc_id != NULL, return);
 
     if (rc == pcmk_ok) {
         char *rsc_id_copy = strdup(rsc_id);
 
         if (rsc_gIter)
             g_hash_table_iter_remove(rsc_gIter);
         else
             g_hash_table_remove(lrm_state->resource_history, rsc_id_copy);
         crm_debug("sync: Sending delete op for %s", rsc_id_copy);
         delete_rsc_status(lrm_state, rsc_id_copy, cib_quorum_override, user_name);
 
         g_hash_table_foreach_remove(lrm_state->pending_ops, lrm_remove_deleted_op, rsc_id_copy);
         free(rsc_id_copy);
     }
 
     if (input) {
         notify_deleted(lrm_state, input, rsc_id, rc);
     }
 
     event.rc = rc;
     event.rsc = rsc_id;
     event.lrm_state = lrm_state;
     g_hash_table_foreach_remove(lrm_state->deletion_ops, lrm_remove_deleted_rsc, &event);
 }
 
 /*
  * Remove the op from the CIB
  *
  * Avoids refreshing the entire LRM section of this host
  */
 
 #define op_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']/"XML_LRM_TAG_RSC_OP"[@id='%s']"
 #define op_call_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']/"XML_LRM_TAG_RSC_OP"[@id='%s' and @"XML_LRM_ATTR_CALLID"='%d']"
 
 static void
 delete_op_entry(lrm_state_t * lrm_state, lrmd_event_data_t * op, const char *rsc_id,
                 const char *key, int call_id)
 {
     xmlNode *xml_top = NULL;
 
     if (op != NULL) {
         xml_top = create_xml_node(NULL, XML_LRM_TAG_RSC_OP);
         crm_xml_add_int(xml_top, XML_LRM_ATTR_CALLID, op->call_id);
         crm_xml_add(xml_top, XML_ATTR_TRANSITION_KEY, op->user_data);
 
         if (op->interval > 0) {
             char *op_id = generate_op_key(op->rsc_id, op->op_type, op->interval);
 
             /* Avoid deleting last_failure too (if it was a result of this recurring op failing) */
             crm_xml_add(xml_top, XML_ATTR_ID, op_id);
             free(op_id);
         }
 
         crm_debug("async: Sending delete op for %s_%s_%d (call=%d)",
                   op->rsc_id, op->op_type, op->interval, op->call_id);
 
         fsa_cib_conn->cmds->delete(fsa_cib_conn, XML_CIB_TAG_STATUS, xml_top, cib_quorum_override);
 
     } else if (rsc_id != NULL && key != NULL) {
         int max = 0;
         char *op_xpath = NULL;
 
         if (call_id > 0) {
             max =
                 strlen(op_call_template) + strlen(rsc_id) + strlen(lrm_state->node_name) +
                 strlen(key) + 10;
             op_xpath = calloc(1, max);
             snprintf(op_xpath, max, op_call_template, lrm_state->node_name, rsc_id, key, call_id);
 
         } else {
             max =
                 strlen(op_template) + strlen(rsc_id) + strlen(lrm_state->node_name) + strlen(key) +
                 1;
             op_xpath = calloc(1, max);
             snprintf(op_xpath, max, op_template, lrm_state->node_name, rsc_id, key);
         }
 
         crm_debug("sync: Sending delete op for %s (call=%d)", rsc_id, call_id);
         fsa_cib_conn->cmds->delete(fsa_cib_conn, op_xpath, NULL, cib_quorum_override | cib_xpath);
 
         free(op_xpath);
 
     } else {
         crm_err("Not enough information to delete op entry: rsc=%p key=%p", rsc_id, key);
         return;
     }
 
     crm_log_xml_trace(xml_top, "op:cancel");
     free_xml(xml_top);
 }
 
 void
 lrm_clear_last_failure(const char *rsc_id, const char *node_name)
 {
     char *attr = NULL;
     GHashTableIter iter;
     GList *lrm_state_list = lrm_state_get_list();
     GList *state_entry;
     rsc_history_t *entry = NULL;
 
     attr = generate_op_key(rsc_id, "last_failure", 0);
 
     /* This clears last failure for every lrm state that has this rsc.*/
     for (state_entry = lrm_state_list; state_entry != NULL; state_entry = state_entry->next) {
         lrm_state_t *lrm_state = state_entry->data;
 
         if (node_name != NULL) {
             if (strcmp(node_name, lrm_state->node_name) != 0) {
                 /* filter by node_name if node_name is present */
                 continue;
             }
         }
 
         delete_op_entry(lrm_state, NULL, rsc_id, attr, 0);
 
         if (!lrm_state->resource_history) {
             continue;
         }
 
         g_hash_table_iter_init(&iter, lrm_state->resource_history);
         while (g_hash_table_iter_next(&iter, NULL, (void **)&entry)) {
             if (safe_str_eq(rsc_id, entry->id)) {
                 lrmd_free_event(entry->failed);
                 entry->failed = NULL;
             }
         }
     }
     free(attr);
     g_list_free(lrm_state_list);
 }
 
 /* Returns: gboolean - cancellation is in progress */
 static gboolean
 cancel_op(lrm_state_t * lrm_state, const char *rsc_id, const char *key, int op, gboolean remove)
 {
     int rc = pcmk_ok;
     struct recurring_op_s *pending = NULL;
 
     CRM_CHECK(op != 0, return FALSE);
     CRM_CHECK(rsc_id != NULL, return FALSE);
     if (key == NULL) {
         key = make_stop_id(rsc_id, op);
     }
     pending = g_hash_table_lookup(lrm_state->pending_ops, key);
 
     if (pending) {
         if (remove && pending->remove == FALSE) {
             pending->remove = TRUE;
             crm_debug("Scheduling %s for removal", key);
         }
 
         if (pending->cancelled) {
             crm_debug("Operation %s already cancelled", key);
             return FALSE;
         }
 
         pending->cancelled = TRUE;
 
     } else {
         crm_info("No pending op found for %s", key);
         return FALSE;
     }
 
     crm_debug("Cancelling op %d for %s (%s)", op, rsc_id, key);
     rc = lrm_state_cancel(lrm_state, pending->rsc_id, pending->op_type, pending->interval);
     if (rc == pcmk_ok) {
         crm_debug("Op %d for %s (%s): cancelled", op, rsc_id, key);
         return TRUE;
     }
 
     crm_debug("Op %d for %s (%s): Nothing to cancel", op, rsc_id, key);
     /* The caller needs to make sure the entry is
      * removed from the pending_ops list
      *
      * Usually by returning TRUE inside the worker function
      * supplied to g_hash_table_foreach_remove()
      *
      * Not removing the entry from pending_ops will block
      * the node from shutting down
      */
     return FALSE;
 }
 
 struct cancel_data {
     gboolean done;
     gboolean remove;
     const char *key;
     lrmd_rsc_info_t *rsc;
     lrm_state_t *lrm_state;
 };
 
 static gboolean
 cancel_action_by_key(gpointer key, gpointer value, gpointer user_data)
 {
     gboolean remove = FALSE;
     struct cancel_data *data = user_data;
     struct recurring_op_s *op = (struct recurring_op_s *)value;
 
     if (safe_str_eq(op->op_key, data->key)) {
         data->done = TRUE;
         remove = !cancel_op(data->lrm_state, data->rsc->id, key, op->call_id, data->remove);
     }
     return remove;
 }
 
 static gboolean
 cancel_op_key(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *key, gboolean remove)
 {
     guint removed = 0;
     struct cancel_data data;
 
     CRM_CHECK(rsc != NULL, return FALSE);
     CRM_CHECK(key != NULL, return FALSE);
 
     data.key = key;
     data.rsc = rsc;
     data.done = FALSE;
     data.remove = remove;
     data.lrm_state = lrm_state;
 
     removed = g_hash_table_foreach_remove(lrm_state->pending_ops, cancel_action_by_key, &data);
     crm_trace("Removed %u op cache entries, new size: %u",
               removed, g_hash_table_size(lrm_state->pending_ops));
     return data.done;
 }
 
 static lrmd_rsc_info_t *
 get_lrm_resource(lrm_state_t * lrm_state, xmlNode * resource, xmlNode * op_msg, gboolean do_create)
 {
     lrmd_rsc_info_t *rsc = NULL;
     const char *id = ID(resource);
     const char *type = crm_element_value(resource, XML_ATTR_TYPE);
     const char *class = crm_element_value(resource, XML_AGENT_ATTR_CLASS);
     const char *provider = crm_element_value(resource, XML_AGENT_ATTR_PROVIDER);
     const char *long_id = crm_element_value(resource, XML_ATTR_ID_LONG);
 
     crm_trace("Retrieving %s from the LRM.", id);
     CRM_CHECK(id != NULL, return NULL);
 
     rsc = lrm_state_get_rsc_info(lrm_state, id, 0);
 
     if (!rsc && long_id) {
         rsc = lrm_state_get_rsc_info(lrm_state, long_id, 0);
     }
 
     if (!rsc && do_create) {
         CRM_CHECK(class != NULL, return NULL);
         CRM_CHECK(type != NULL, return NULL);
 
         crm_trace("Adding rsc %s before operation", id);
 
         lrm_state_register_rsc(lrm_state, id, class, provider, type, lrmd_opt_drop_recurring);
 
         rsc = lrm_state_get_rsc_info(lrm_state, id, 0);
 
         if (!rsc) {
             fsa_data_t *msg_data = NULL;
 
             crm_err("Could not add resource %s to LRM", id);
             register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
         }
     }
 
     return rsc;
 }
 
 static void
 delete_resource(lrm_state_t * lrm_state,
                 const char *id,
                 lrmd_rsc_info_t * rsc,
                 GHashTableIter * gIter,
                 const char *sys, const char *host, const char *user, ha_msg_input_t * request)
 {
     int rc = pcmk_ok;
 
     crm_info("Removing resource %s for %s (%s) on %s", id, sys, user ? user : "internal", host);
 
     if (rsc) {
         rc = lrm_state_unregister_rsc(lrm_state, id, 0);
     }
 
     if (rc == pcmk_ok) {
         crm_trace("Resource '%s' deleted", id);
     } else if (rc == -EINPROGRESS) {
         crm_info("Deletion of resource '%s' pending", id);
         if (request) {
             struct pending_deletion_op_s *op = NULL;
             char *ref = crm_element_value_copy(request->msg, XML_ATTR_REFERENCE);
 
             op = calloc(1, sizeof(struct pending_deletion_op_s));
             op->rsc = strdup(rsc->id);
             op->input = copy_ha_msg_input(request);
             g_hash_table_insert(lrm_state->deletion_ops, ref, op);
         }
         return;
     } else {
         crm_warn("Deletion of resource '%s' for %s (%s) on %s failed: %d",
                  id, sys, user ? user : "internal", host, rc);
     }
 
     delete_rsc_entry(lrm_state, request, id, gIter, rc, user);
 }
 
 /*	 A_LRM_INVOKE	*/
 void
 do_lrm_invoke(long long action,
               enum crmd_fsa_cause cause,
               enum crmd_fsa_state cur_state,
               enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     gboolean create_rsc = TRUE;
     lrm_state_t *lrm_state = NULL;
     const char *crm_op = NULL;
     const char *from_sys = NULL;
     const char *from_host = NULL;
     const char *operation = NULL;
     ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg);
     const char *user_name = NULL;
     const char *target_node = NULL;
     gboolean is_remote_node = FALSE;
 
     if (input->xml != NULL) {
         /* Remote node operations are routed here to their remote connections */
         target_node = crm_element_value(input->xml, XML_LRM_ATTR_TARGET);
     }
     if (target_node == NULL) {
         target_node = fsa_our_uname;
     } else if (safe_str_neq(target_node, fsa_our_uname)) {
         is_remote_node = TRUE;
     }
 
     lrm_state = lrm_state_find(target_node);
 
     if (lrm_state == NULL && is_remote_node) {
         crm_err("no lrmd connection for remote node %s found on cluster node %s. Can not process request.",
             target_node, fsa_our_uname);
         return;
     }
 
     CRM_ASSERT(lrm_state != NULL);
 
 #if ENABLE_ACL
     user_name = crm_element_value(input->msg, F_CRM_USER);
     crm_trace("LRM command from user '%s'", user_name);
 #endif
 
     crm_op = crm_element_value(input->msg, F_CRM_TASK);
     from_sys = crm_element_value(input->msg, F_CRM_SYS_FROM);
     if (safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) {
         from_host = crm_element_value(input->msg, F_CRM_HOST_FROM);
     }
 
     crm_trace("LRM command from: %s", from_sys);
 
     if (safe_str_eq(crm_op, CRM_OP_LRM_DELETE)) {
         operation = CRMD_ACTION_DELETE;
 
     } else if (safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) {
         operation = CRM_OP_LRM_REFRESH;
 
     } else if (safe_str_eq(crm_op, CRM_OP_LRM_FAIL)) {
         rsc_history_t *entry = NULL;
         lrmd_event_data_t *op = NULL;
         lrmd_rsc_info_t *rsc = NULL;
         xmlNode *xml_rsc = find_xml_node(input->xml, XML_CIB_TAG_RESOURCE, TRUE);
 
         CRM_CHECK(xml_rsc != NULL, return);
 
         /* The lrmd can not fail a resource, it does not understand the
          * concept of success or failure in relation to a resource, it simply
          * executes operations and reports the results. We determine what a failure is.
          * Becaues of this, if we want to fail a resource we have to fake what we
          * understand a failure to look like.
          *
          * To do this we create a fake lrmd operation event for the resource
          * we want to fail.  We then pass that event to the lrmd client callback
          * so it will be processed as if it actually came from the lrmd. */
         op = construct_op(lrm_state, input->xml, ID(xml_rsc), "asyncmon");
         CRM_ASSERT(op != NULL);
 
         free((char *)op->user_data);
         op->user_data = NULL;
         entry = g_hash_table_lookup(lrm_state->resource_history, op->rsc_id);
         /* Make sure the call id is greater than the last successful operation,
          * otherwise the failure will not result in a possible recovery of the resource
          * as it could appear the failure occurred before the successful start */
         if (entry) {
             op->call_id = entry->last_callid + 1;
             if (op->call_id < 0) {
                 op->call_id = 1;
             }
         }
         op->interval = 0;
         op->op_status = PCMK_LRM_OP_DONE;
         op->rc = PCMK_OCF_UNKNOWN_ERROR;
         op->t_run = time(NULL);
         op->t_rcchange = op->t_run;
 
 #if ENABLE_ACL
         if (user_name && is_privileged(user_name) == FALSE) {
             crm_err("%s does not have permission to fail %s", user_name, ID(xml_rsc));
             send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
             lrmd_free_event(op);
             return;
         }
 #endif
 
         rsc = get_lrm_resource(lrm_state, xml_rsc, input->xml, create_rsc);
         if (rsc) {
             crm_info("Failing resource %s...", rsc->id);
             process_lrm_event(lrm_state, op);
             op->op_status = PCMK_LRM_OP_DONE;
             op->rc = PCMK_OCF_OK;
             lrmd_free_rsc_info(rsc);
         } else {
             crm_info("Cannot find/create resource in order to fail it...");
             crm_log_xml_warn(input->msg, "bad input");
         }
 
         send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
         lrmd_free_event(op);
         return;
 
     } else if (input->xml != NULL) {
         operation = crm_element_value(input->xml, XML_LRM_ATTR_TASK);
     }
 
     if (safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) {
         int rc = pcmk_ok;
         xmlNode *fragment = do_lrm_query_internal(lrm_state, TRUE);
 
         fsa_cib_update(XML_CIB_TAG_STATUS, fragment, cib_quorum_override, rc, user_name);
         crm_info("Forced a local LRM refresh: call=%d", rc);
 
         if(strcmp(CRM_SYSTEM_CRMD, from_sys) != 0) {
             xmlNode *reply = create_request(
                 CRM_OP_INVOKE_LRM, fragment,
                 from_host, from_sys, CRM_SYSTEM_LRMD, fsa_our_uuid);
 
             crm_debug("ACK'ing refresh from %s (%s)", from_sys, from_host);
 
             if (relay_message(reply, TRUE) == FALSE) {
                 crm_log_xml_err(reply, "Unable to route reply");
             }
             free_xml(reply);
         }
 
         free_xml(fragment);
 
     } else if (safe_str_eq(crm_op, CRM_OP_LRM_QUERY)) {
         xmlNode *data = do_lrm_query_internal(lrm_state, FALSE);
         xmlNode *reply = create_reply(input->msg, data);
 
         if (relay_message(reply, TRUE) == FALSE) {
             crm_err("Unable to route reply");
             crm_log_xml_err(reply, "reply");
         }
         free_xml(reply);
         free_xml(data);
 
     } else if (safe_str_eq(operation, CRM_OP_PROBED)) {
         update_attrd(lrm_state->node_name, CRM_OP_PROBED, XML_BOOLEAN_TRUE, user_name, is_remote_node);
 
     } else if (safe_str_eq(operation, CRM_OP_REPROBE) || safe_str_eq(crm_op, CRM_OP_REPROBE)) {
         GHashTableIter gIter;
         rsc_history_t *entry = NULL;
 
         crm_notice("Forcing the status of all resources to be redetected");
 
         g_hash_table_iter_init(&gIter, lrm_state->resource_history);
         while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
             delete_resource(lrm_state, entry->id, &entry->rsc, &gIter, from_sys, from_host,
                             user_name, NULL);
         }
 
         /* Now delete the copy in the CIB */
         erase_status_tag(lrm_state->node_name, XML_CIB_TAG_LRM, cib_scope_local);
 
         /* And finally, _delete_ the value in attrd
          * Setting it to FALSE results in the PE sending us back here again
          */
         update_attrd(lrm_state->node_name, CRM_OP_PROBED, NULL, user_name, is_remote_node);
 
         if(strcmp(CRM_SYSTEM_TENGINE, from_sys) != 0
            && strcmp(CRM_SYSTEM_TENGINE, from_sys) != 0) {
             xmlNode *reply = create_request(
                 CRM_OP_INVOKE_LRM, NULL,
                 from_host, from_sys, CRM_SYSTEM_LRMD, fsa_our_uuid);
 
             crm_debug("ACK'ing re-probe from %s (%s)", from_sys, from_host);
 
             if (relay_message(reply, TRUE) == FALSE) {
                 crm_log_xml_err(reply, "Unable to route reply");
             }
             free_xml(reply);
         }
 
     } else if (operation != NULL) {
         lrmd_rsc_info_t *rsc = NULL;
         xmlNode *params = NULL;
         xmlNode *xml_rsc = find_xml_node(input->xml, XML_CIB_TAG_RESOURCE, TRUE);
 
         CRM_CHECK(xml_rsc != NULL, return);
 
         /* only the first 16 chars are used by the LRM */
         params = find_xml_node(input->xml, XML_TAG_ATTRS, TRUE);
 
         if (safe_str_eq(operation, CRMD_ACTION_DELETE)) {
             create_rsc = FALSE;
         }
 
         rsc = get_lrm_resource(lrm_state, xml_rsc, input->xml, create_rsc);
 
         if (rsc == NULL && create_rsc) {
             crm_err("Invalid resource definition");
             crm_log_xml_warn(input->msg, "bad input");
 
         } else if (rsc == NULL) {
             lrmd_event_data_t *op = NULL;
 
             crm_notice("Not creating resource for a %s event: %s", operation, ID(input->xml));
             delete_rsc_entry(lrm_state, input, ID(xml_rsc), NULL, pcmk_ok, user_name);
 
             op = construct_op(lrm_state, input->xml, ID(xml_rsc), operation);
             op->op_status = PCMK_LRM_OP_DONE;
             op->rc = PCMK_OCF_OK;
             CRM_ASSERT(op != NULL);
             send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
             lrmd_free_event(op);
 
         } else if (safe_str_eq(operation, CRMD_ACTION_CANCEL)) {
             char *op_key = NULL;
             char *meta_key = NULL;
             int call = 0;
             const char *call_id = NULL;
             const char *op_task = NULL;
             const char *op_interval = NULL;
             gboolean in_progress = FALSE;
 
             CRM_CHECK(params != NULL, crm_log_xml_warn(input->xml, "Bad command");
                       return);
 
             meta_key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
             op_interval = crm_element_value(params, meta_key);
             free(meta_key);
 
             meta_key = crm_meta_name(XML_LRM_ATTR_TASK);
             op_task = crm_element_value(params, meta_key);
             free(meta_key);
 
             meta_key = crm_meta_name(XML_LRM_ATTR_CALLID);
             call_id = crm_element_value(params, meta_key);
             free(meta_key);
 
             CRM_CHECK(op_task != NULL, crm_log_xml_warn(input->xml, "Bad command");
                       return);
             CRM_CHECK(op_interval != NULL, crm_log_xml_warn(input->xml, "Bad command");
                       return);
 
             op_key = generate_op_key(rsc->id, op_task, crm_parse_int(op_interval, "0"));
 
             crm_debug("PE requested op %s (call=%s) be cancelled",
                       op_key, call_id ? call_id : "NA");
             call = crm_parse_int(call_id, "0");
             if (call == 0) {
                 /* the normal case when the PE cancels a recurring op */
                 in_progress = cancel_op_key(lrm_state, rsc, op_key, TRUE);
 
             } else {
                 /* the normal case when the PE cancels an orphan op */
                 in_progress = cancel_op(lrm_state, rsc->id, NULL, call, TRUE);
             }
 
             if (in_progress == FALSE) {
                 lrmd_event_data_t *op = construct_op(lrm_state, input->xml, rsc->id, op_task);
 
                 crm_info("Nothing known about operation %d for %s", call, op_key);
                 delete_op_entry(lrm_state, NULL, rsc->id, op_key, call);
 
                 CRM_ASSERT(op != NULL);
 
                 op->rc = PCMK_OCF_OK;
                 op->op_status = PCMK_LRM_OP_DONE;
                 send_direct_ack(from_host, from_sys, rsc, op, rsc->id);
                 lrmd_free_event(op);
 
                 /* needed?? surely not otherwise the cancel_op_(_key) wouldn't
                  * have failed in the first place
                  */
                 g_hash_table_remove(lrm_state->pending_ops, op_key);
             }
 
             free(op_key);
 
         } else if (rsc != NULL && safe_str_eq(operation, CRMD_ACTION_DELETE)) {
 
 #if ENABLE_ACL
             int cib_rc = delete_rsc_status(lrm_state, rsc->id, cib_dryrun | cib_sync_call, user_name);
             if (cib_rc != pcmk_ok) {
                 lrmd_event_data_t *op = NULL;
 
                 crm_err
                     ("Attempted deletion of resource status '%s' from CIB for %s (user=%s) on %s failed: (rc=%d) %s",
                      rsc->id, from_sys, user_name ? user_name : "unknown", from_host, cib_rc,
                      pcmk_strerror(cib_rc));
 
                 op = construct_op(lrm_state, input->xml, rsc->id, operation);
                 op->op_status = PCMK_LRM_OP_ERROR;
 
                 if (cib_rc == -EACCES) {
                     op->rc = PCMK_OCF_INSUFFICIENT_PRIV;
                 } else {
                     op->rc = PCMK_OCF_UNKNOWN_ERROR;
                 }
                 send_direct_ack(from_host, from_sys, NULL, op, rsc->id);
                 lrmd_free_event(op);
                 return;
             }
 #endif
             delete_resource(lrm_state, rsc->id, rsc, NULL, from_sys, from_host, user_name, input);
 
         } else if (rsc != NULL) {
             do_lrm_rsc_op(lrm_state, rsc, operation, input->xml, input->msg);
         }
 
         lrmd_free_rsc_info(rsc);
 
     } else {
         crm_err("Operation was neither a lrm_query, nor a rsc op.  %s", crm_str(crm_op));
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
 }
 
 static lrmd_event_data_t *
 construct_op(lrm_state_t * lrm_state, xmlNode * rsc_op, const char *rsc_id, const char *operation)
 {
     lrmd_event_data_t *op = NULL;
     const char *op_delay = NULL;
     const char *op_timeout = NULL;
     const char *op_interval = NULL;
     GHashTable *params = NULL;
 
     const char *transition = NULL;
 
     CRM_LOG_ASSERT(rsc_id != NULL);
 
     op = calloc(1, sizeof(lrmd_event_data_t));
     op->type = lrmd_event_exec_complete;
     op->op_type = strdup(operation);
     op->op_status = PCMK_LRM_OP_PENDING;
     op->rc = -1;
     op->rsc_id = strdup(rsc_id);
     op->interval = 0;
     op->timeout = 0;
     op->start_delay = 0;
 
     if (rsc_op == NULL) {
         CRM_LOG_ASSERT(safe_str_eq(CRMD_ACTION_STOP, operation));
         op->user_data = NULL;
         /* the stop_all_resources() case
          * by definition there is no DC (or they'd be shutting
          *   us down).
          * So we should put our version here.
          */
         op->params = g_hash_table_new_full(crm_str_hash, g_str_equal,
                                            g_hash_destroy_str, g_hash_destroy_str);
 
         g_hash_table_insert(op->params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET));
 
         crm_trace("Constructed %s op for %s", operation, rsc_id);
         return op;
     }
 
     params = xml2list(rsc_op);
     g_hash_table_remove(params, CRM_META "_op_target_rc");
 
     op_delay = crm_meta_value(params, XML_OP_ATTR_START_DELAY);
     op_timeout = crm_meta_value(params, XML_ATTR_TIMEOUT);
     op_interval = crm_meta_value(params, XML_LRM_ATTR_INTERVAL);
 
     op->interval = crm_parse_int(op_interval, "0");
     op->timeout = crm_parse_int(op_timeout, "0");
     op->start_delay = crm_parse_int(op_delay, "0");
 
     if (safe_str_neq(operation, RSC_STOP)) {
         op->params = params;
 
     } else {
         rsc_history_t *entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
 
         /* If we do not have stop parameters cached, use
          * whatever we are given */
         if (!entry || !entry->stop_params) {
             op->params = params;
         } else {
             /* Copy the cached parameter list so that we stop the resource
              * with the old attributes, not the new ones */
             op->params = g_hash_table_new_full(crm_str_hash, g_str_equal,
                                                g_hash_destroy_str, g_hash_destroy_str);
 
             g_hash_table_foreach(params, copy_meta_keys, op->params);
             g_hash_table_foreach(entry->stop_params, copy_instance_keys, op->params);
             g_hash_table_destroy(params);
             params = NULL;
         }
     }
 
     /* sanity */
     if (op->interval < 0) {
         op->interval = 0;
     }
     if (op->timeout <= 0) {
         op->timeout = op->interval;
     }
     if (op->start_delay < 0) {
         op->start_delay = 0;
     }
 
     transition = crm_element_value(rsc_op, XML_ATTR_TRANSITION_KEY);
     CRM_CHECK(transition != NULL, return op);
 
     op->user_data = strdup(transition);
 
     if (op->interval != 0) {
         if (safe_str_eq(operation, CRMD_ACTION_START)
             || safe_str_eq(operation, CRMD_ACTION_STOP)) {
             crm_err("Start and Stop actions cannot have an interval: %d", op->interval);
             op->interval = 0;
         }
     }
 
     crm_trace("Constructed %s op for %s: interval=%d", operation, rsc_id, op->interval);
 
     return op;
 }
 
 void
 send_direct_ack(const char *to_host, const char *to_sys,
                 lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, const char *rsc_id)
 {
     xmlNode *reply = NULL;
     xmlNode *update, *iter;
     xmlNode *fragment;
     crm_node_t *peer = NULL;
 
     CRM_CHECK(op != NULL, return);
     if (op->rsc_id == NULL) {
         CRM_LOG_ASSERT(rsc_id != NULL);
         op->rsc_id = strdup(rsc_id);
     }
     if (to_sys == NULL) {
         to_sys = CRM_SYSTEM_TENGINE;
     }
 
     peer = crm_get_peer(0, fsa_our_uname);
     update = do_update_node_cib(peer, node_update_none, NULL, __FUNCTION__);
 
     iter = create_xml_node(update, XML_CIB_TAG_LRM);
     crm_xml_add(iter, XML_ATTR_ID, fsa_our_uuid);
     iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES);
     iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE);
 
     crm_xml_add(iter, XML_ATTR_ID, op->rsc_id);
 
     build_operation_update(iter, rsc, op, __FUNCTION__);
     fragment = create_cib_fragment(update, XML_CIB_TAG_STATUS);
 
     reply = create_request(CRM_OP_INVOKE_LRM, fragment, to_host, to_sys, CRM_SYSTEM_LRMD, NULL);
 
     crm_log_xml_trace(update, "ACK Update");
 
     crm_debug("ACK'ing resource op %s_%s_%d from %s: %s",
               op->rsc_id, op->op_type, op->interval, op->user_data,
               crm_element_value(reply, XML_ATTR_REFERENCE));
 
     if (relay_message(reply, TRUE) == FALSE) {
         crm_log_xml_err(reply, "Unable to route reply");
     }
 
     free_xml(fragment);
     free_xml(update);
     free_xml(reply);
 }
 
 gboolean
 verify_stopped(enum crmd_fsa_state cur_state, int log_level)
 {
     gboolean res = TRUE;
     GList *lrm_state_list = lrm_state_get_list();
     GList *state_entry;
 
     for (state_entry = lrm_state_list; state_entry != NULL; state_entry = state_entry->next) {
         lrm_state_t *lrm_state = state_entry->data;
 
         if (!lrm_state_verify_stopped(lrm_state, cur_state, log_level)) {
             /* keep iterating through all even when false is returned */
             res = FALSE;
         }
     }
 
     set_bit(fsa_input_register, R_SENT_RSC_STOP);
     g_list_free(lrm_state_list); lrm_state_list = NULL;
     return res;
 }
 
 struct stop_recurring_action_s {
     lrmd_rsc_info_t *rsc;
     lrm_state_t *lrm_state;
 };
 
 static gboolean
 stop_recurring_action_by_rsc(gpointer key, gpointer value, gpointer user_data)
 {
     gboolean remove = FALSE;
     struct stop_recurring_action_s *event = user_data;
     struct recurring_op_s *op = (struct recurring_op_s *)value;
 
     if (op->interval != 0 && safe_str_eq(op->rsc_id, event->rsc->id)) {
         crm_debug("Cancelling op %d for %s (%s)", op->call_id, op->rsc_id, key);
         remove = !cancel_op(event->lrm_state, event->rsc->id, key, op->call_id, FALSE);
     }
 
     return remove;
 }
 
 static gboolean
 stop_recurring_actions(gpointer key, gpointer value, gpointer user_data)
 {
     gboolean remove = FALSE;
     lrm_state_t *lrm_state = user_data;
     struct recurring_op_s *op = (struct recurring_op_s *)value;
 
     if (op->interval != 0) {
         crm_info("Cancelling op %d for %s (%s)", op->call_id, op->rsc_id, key);
         remove = !cancel_op(lrm_state, op->rsc_id, key, op->call_id, FALSE);
     }
 
     return remove;
 }
 
 static void
 do_lrm_rsc_op(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *operation, xmlNode * msg,
               xmlNode * request)
 {
     int call_id = 0;
     char *op_id = NULL;
     lrmd_event_data_t *op = NULL;
     lrmd_key_value_t *params = NULL;
     fsa_data_t *msg_data = NULL;
     const char *transition = NULL;
 
     CRM_CHECK(rsc != NULL, return);
     CRM_CHECK(operation != NULL, return);
 
     if (msg != NULL) {
         transition = crm_element_value(msg, XML_ATTR_TRANSITION_KEY);
         if (transition == NULL) {
             crm_log_xml_err(msg, "Missing transition number");
         }
     }
 
     op = construct_op(lrm_state, msg, rsc->id, operation);
     CRM_CHECK(op != NULL, return);
 
     /* stop any previous monitor operations before changing the resource state */
     if (op->interval == 0
         && strcmp(operation, CRMD_ACTION_STATUS) != 0
         && strcmp(operation, CRMD_ACTION_NOTIFY) != 0) {
         guint removed = 0;
         struct stop_recurring_action_s data;
 
         data.rsc = rsc;
         data.lrm_state = lrm_state;
         removed = g_hash_table_foreach_remove(
             lrm_state->pending_ops, stop_recurring_action_by_rsc, &data);
 
         crm_debug("Stopped %u recurring operations in preparation for %s_%s_%d",
                   removed, rsc->id, operation, op->interval);
     }
 
     /* now do the op */
     crm_info("Performing key=%s op=%s_%s_%d", transition, rsc->id, operation, op->interval);
 
     if (fsa_state != S_NOT_DC && fsa_state != S_POLICY_ENGINE && fsa_state != S_TRANSITION_ENGINE) {
         if (safe_str_neq(operation, "fail")
             && safe_str_neq(operation, CRMD_ACTION_STOP)) {
             crm_info("Discarding attempt to perform action %s on %s in state %s",
                      operation, rsc->id, fsa_state2string(fsa_state));
             op->rc = 99;
             op->op_status = PCMK_LRM_OP_ERROR;
             send_direct_ack(NULL, NULL, rsc, op, rsc->id);
             lrmd_free_event(op);
             free(op_id);
             return;
         }
     }
 
     op_id = generate_op_key(rsc->id, op->op_type, op->interval);
 
     if (op->interval > 0) {
         /* cancel it so we can then restart it without conflict */
         cancel_op_key(lrm_state, rsc, op_id, FALSE);
     }
 
     if (op->params) {
         char *key = NULL;
         char *value = NULL;
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, op->params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             params = lrmd_key_value_add(params, key, value);
         }
     }
 
     call_id = lrm_state_exec(lrm_state,
                              rsc->id,
                              op->op_type,
                              op->user_data, op->interval, op->timeout, op->start_delay, params);
 
     if (call_id <= 0) {
         crm_err("Operation %s on %s failed: %d", operation, rsc->id, call_id);
         register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
 
     } else {
         /* record all operations so we can wait
          * for them to complete during shutdown
          */
         char *call_id_s = make_stop_id(rsc->id, call_id);
         struct recurring_op_s *pending = NULL;
 
         pending = calloc(1, sizeof(struct recurring_op_s));
         crm_trace("Recording pending op: %d - %s %s", call_id, op_id, call_id_s);
 
         pending->call_id = call_id;
         pending->interval = op->interval;
         pending->op_type = strdup(operation);
         pending->op_key = strdup(op_id);
         pending->rsc_id = strdup(rsc->id);
         g_hash_table_replace(lrm_state->pending_ops, call_id_s, pending);
 
         if (op->interval > 0 && op->start_delay > START_DELAY_THRESHOLD) {
             char *uuid = NULL;
             int dummy = 0, target_rc = 0;
 
             crm_info("Faking confirmation of %s: execution postponed for over 5 minutes", op_id);
 
             decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &target_rc);
             free(uuid);
 
             op->rc = target_rc;
             op->op_status = PCMK_LRM_OP_DONE;
             send_direct_ack(NULL, NULL, rsc, op, rsc->id);
         }
     }
 
     free(op_id);
     lrmd_free_event(op);
     return;
 }
 
 int last_resource_update = 0;
 
 static void
 cib_rsc_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     switch (rc) {
         case pcmk_ok:
         case -pcmk_err_diff_failed:
         case -pcmk_err_diff_resync:
             crm_trace("Resource update %d complete: rc=%d", call_id, rc);
             break;
         default:
             crm_warn("Resource update %d failed: (rc=%d) %s", call_id, rc, pcmk_strerror(rc));
     }
 
     if (call_id == last_resource_update) {
         last_resource_update = 0;
         trigger_fsa(fsa_source);
     }
 }
 
 static int
 do_update_resource(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
 {
 /*
   <status>
   <nodes_status id=uname>
   <lrm>
   <lrm_resources>
   <lrm_resource id=...>
   </...>
 */
     int rc = pcmk_ok;
     xmlNode *update, *iter = NULL;
     int call_opt = cib_quorum_override;
     const char *uuid = NULL;
 
     CRM_CHECK(op != NULL, return 0);
 
     if (fsa_state == S_ELECTION || fsa_state == S_PENDING) {
         crm_info("Sending update to local CIB in state: %s", fsa_state2string(fsa_state));
         call_opt |= cib_scope_local;
     }
 
     iter = create_xml_node(iter, XML_CIB_TAG_STATUS);
     update = iter;
     iter = create_xml_node(iter, XML_CIB_TAG_STATE);
 
     if (safe_str_eq(lrm_state->node_name, fsa_our_uname)) {
         uuid = fsa_our_uuid;
 
     } else {
         /* remote nodes uuid and uname are equal */
         uuid = lrm_state->node_name;
         crm_xml_add(iter, XML_NODE_IS_REMOTE, "true");
     }
 
     CRM_LOG_ASSERT(uuid != NULL);
     if(uuid == NULL) {
         rc = -EINVAL;
         goto done;
     }
 
     crm_xml_add(iter, XML_ATTR_UUID,  uuid);
     crm_xml_add(iter, XML_ATTR_UNAME, lrm_state->node_name);
     crm_xml_add(iter, XML_ATTR_ORIGIN, __FUNCTION__);
 
     iter = create_xml_node(iter, XML_CIB_TAG_LRM);
     crm_xml_add(iter, XML_ATTR_ID, uuid);
 
     iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES);
     iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE);
     crm_xml_add(iter, XML_ATTR_ID, op->rsc_id);
 
     build_operation_update(iter, rsc, op, __FUNCTION__);
 
     if (rsc) {
         const char *container = NULL;
 
         crm_xml_add(iter, XML_ATTR_TYPE, rsc->type);
         crm_xml_add(iter, XML_AGENT_ATTR_CLASS, rsc->class);
         crm_xml_add(iter, XML_AGENT_ATTR_PROVIDER, rsc->provider);
 
         if (op->params) {
             container = g_hash_table_lookup(op->params, CRM_META"_"XML_RSC_ATTR_CONTAINER);
         }
         if (container) {
             crm_trace("Resource %s is a part of container resource %s", op->rsc_id, container);
             crm_xml_add(iter, XML_RSC_ATTR_CONTAINER, container);
         }
 
         CRM_CHECK(rsc->type != NULL, crm_err("Resource %s has no value for type", op->rsc_id));
         CRM_CHECK(rsc->class != NULL, crm_err("Resource %s has no value for class", op->rsc_id));
 
     } else {
         crm_warn("Resource %s no longer exists in the lrmd", op->rsc_id);
         send_direct_ack(NULL, NULL, rsc, op, op->rsc_id);
         goto cleanup;
     }
 
     crm_log_xml_trace(update, __FUNCTION__);
 
     /* make it an asyncronous call and be done with it
      *
      * Best case:
      *   the resource state will be discovered during
      *   the next signup or election.
      *
      * Bad case:
      *   we are shutting down and there is no DC at the time,
      *   but then why were we shutting down then anyway?
      *   (probably because of an internal error)
      *
      * Worst case:
      *   we get shot for having resources "running" when the really weren't
      *
      * the alternative however means blocking here for too long, which
      * isnt acceptable
      */
     fsa_cib_update(XML_CIB_TAG_STATUS, update, call_opt, rc, NULL);
 
     if (rc > 0) {
         last_resource_update = rc;
     }
   done:
     /* the return code is a call number, not an error code */
     crm_trace("Sent resource state update message: %d for %s=%d on %s", rc,
               op->op_type, op->interval, op->rsc_id);
     fsa_register_cib_callback(rc, FALSE, NULL, cib_rsc_callback);
 
   cleanup:
     free_xml(update);
     return rc;
 }
 
 void
 do_lrm_event(long long action,
              enum crmd_fsa_cause cause,
              enum crmd_fsa_state cur_state, enum crmd_fsa_input cur_input, fsa_data_t * msg_data)
 {
     CRM_CHECK(FALSE, return);
 }
 
 gboolean
 process_lrm_event(lrm_state_t * lrm_state, lrmd_event_data_t * op)
 {
     char *op_id = NULL;
     char *op_key = NULL;
 
     int update_id = 0;
     gboolean removed = FALSE;
     lrmd_rsc_info_t *rsc = NULL;
 
     struct recurring_op_s *pending = NULL;
 
     CRM_CHECK(op != NULL, return FALSE);
 
     CRM_CHECK(op->rsc_id != NULL, return FALSE);
     op_id = make_stop_id(op->rsc_id, op->call_id);
     pending = g_hash_table_lookup(lrm_state->pending_ops, op_id);
     op_key = generate_op_key(op->rsc_id, op->op_type, op->interval);
     rsc = lrm_state_get_rsc_info(lrm_state, op->rsc_id, 0);
 
     if (op->op_status == PCMK_LRM_OP_ERROR
         && (op->rc == PCMK_OCF_RUNNING_MASTER || op->rc == PCMK_OCF_NOT_RUNNING)) {
         /* Leave it up to the TE/PE to decide if this is an error */
         op->op_status = PCMK_LRM_OP_DONE;
     }
 
     if (op->op_status != PCMK_LRM_OP_CANCELLED) {
         if (safe_str_eq(op->op_type, RSC_NOTIFY)) {
             /* Keep notify ops out of the CIB */
             send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
         } else {
             update_id = do_update_resource(lrm_state, rsc, op);
         }
     } else if (op->interval == 0) {
         /* This will occur when "crm resource cleanup" is called while actions are in-flight */
         crm_err("Op %s (call=%d): Cancelled", op_key, op->call_id);
         send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
 
     } else if (pending == NULL) {
-        /* Operations that are cancelled may safely be removed
-         * from the pending op list before the lrmd completion event
-         * is received. Only report non-cancelled ops here. */
-        if (op->op_status != PCMK_LRM_OP_CANCELLED) {
-            crm_err("Op %s (call=%d): No 'pending' entry", op_key, op->call_id);
-        }
+        /* We don't need to do anything for cancelled ops
+         * that are not in our pending op list. There are no
+         * transition actions waiting on these operations. */
+
     } else if (op->user_data == NULL) {
+        /* At this point we have a pending entry, but no transition
+         * key present in the user_data field. report this */
         crm_err("Op %s (call=%d): No user data", op_key, op->call_id);
 
     } else if (pending->remove) {
+        /* The tengine canceled this op, we have been waiting for the cancel to finish. */
         delete_op_entry(lrm_state, op, op->rsc_id, op_key, op->call_id);
 
+    } else if (pending && op->rsc_deleted) {
+        /* The tengine initiated this op, but it was cancelled outside of the
+         * tengine's control during a resource cleanup/re-probe request. The tengine
+         * must be alerted that this operation completed, otherwise the tengine
+         * will continue waiting for this update to occur until it is timed out.
+         * We don't want this update going to the cib though, so use a direct ack. */
+        crm_trace("Op %s (call=%d): cancelled due to rsc deletion", op_key, op->call_id);
+        send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
+
     } else {
         /* Before a stop is called, no need to direct ack */
         crm_trace("Op %s (call=%d): no delete event required", op_key, op->call_id);
     }
 
     if ((op->interval == 0) && g_hash_table_remove(lrm_state->pending_ops, op_id)) {
         removed = TRUE;
         crm_trace("Op %s (call=%d, stop-id=%s, remaining=%u): Confirmed",
                   op_key, op->call_id, op_id, g_hash_table_size(lrm_state->pending_ops));
 
     } else if(op->interval != 0 && op->op_status == PCMK_LRM_OP_CANCELLED) {
         removed = TRUE;
         g_hash_table_remove(lrm_state->pending_ops, op_id);
     }
 
     switch (op->op_status) {
         case PCMK_LRM_OP_CANCELLED:
             crm_info("Operation %s: %s (node=%s, call=%d, confirmed=%s)",
                      op_key, services_lrm_status_str(op->op_status), lrm_state->node_name,
                      op->call_id, removed ? "true" : "false");
             break;
 
         case PCMK_LRM_OP_DONE:
             crm_notice("Operation %s: %s (node=%s, call=%d, rc=%d, cib-update=%d, confirmed=%s)",
                        op_key, services_ocf_exitcode_str(op->rc), lrm_state->node_name,
                        op->call_id, op->rc, update_id, removed ? "true" : "false");
             break;
 
         case PCMK_LRM_OP_TIMEOUT:
             crm_err("Operation %s: %s (node=%s, call=%d, timeout=%dms)",
                     op_key, services_lrm_status_str(op->op_status), lrm_state->node_name, op->call_id, op->timeout);
             break;
 
         default:
             crm_err("Operation %s (node=%s, call=%d, status=%d, cib-update=%d, confirmed=%s) %s",
                     op_key, lrm_state->node_name, op->call_id, op->op_status, update_id, removed ? "true" : "false",
                     services_lrm_status_str(op->op_status));
     }
 
     if (op->output) {
         char *prefix =
             g_strdup_printf("%s-%s_%s_%d:%d", lrm_state->node_name, op->rsc_id, op->op_type, op->interval, op->call_id);
 
         if (op->rc) {
             crm_log_output(LOG_NOTICE, prefix, op->output);
         } else {
             crm_log_output(LOG_DEBUG, prefix, op->output);
         }
         g_free(prefix);
     }
 
     if (op->rsc_deleted) {
         crm_info("Deletion of resource '%s' complete after %s", op->rsc_id, op_key);
         delete_rsc_entry(lrm_state, NULL, op->rsc_id, NULL, pcmk_ok, NULL);
     }
 
     /* If a shutdown was escalated while operations were pending,
      * then the FSA will be stalled right now... allow it to continue
      */
     mainloop_set_trigger(fsa_source);
     update_history_cache(lrm_state, rsc, op);
 
     lrmd_free_rsc_info(rsc);
     free(op_key);
     free(op_id);
 
     return TRUE;
 }
diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h
index 7a60a1d30a..6d1c51e7ad 100644
--- a/include/crm/common/mainloop.h
+++ b/include/crm/common/mainloop.h
@@ -1,113 +1,114 @@
 /*
  * Copyright (C) 2009 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 #ifndef CRM_COMMON_MAINLOOP__H
 #  define CRM_COMMON_MAINLOOP__H
 
 /**
  * \file
  * \brief Wrappers for and extensions to glib mainloop
  * \ingroup core
  */
 
 #  include <glib.h>
 
 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);
 
 gboolean crm_signal(int sig, void (*dispatch) (int sig));
 
 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 <crm/common/ipc.h>
 #  include <qb/qbipcs.h>
 
 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);
 
 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_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);
 
 #  define G_PRIORITY_MEDIUM (G_PRIORITY_HIGH/2)
 
 #endif
diff --git a/include/crm/services.h b/include/crm/services.h
index c06aae97a2..ca2c1079a1 100644
--- a/include/crm/services.h
+++ b/include/crm/services.h
@@ -1,350 +1,355 @@
 /*
  * Copyright (C) 2010 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 /**
  * \file
  * \brief Services API
  * \ingroup core
  */
 
 #ifndef __PCMK_SERVICES__
 #  define __PCMK_SERVICES__
 
 #  ifdef __cplusplus
 extern "C" {
 #  endif
 
 #  include <glib.h>
 #  include <stdio.h>
 #  include <string.h>
 
 #  ifndef OCF_ROOT_DIR
 #    define OCF_ROOT_DIR "/usr/lib/ocf"
 #  endif
 
 #  ifndef LSB_ROOT_DIR
 #    define LSB_ROOT_DIR "/etc/init.d"
 #  endif
 
 /* TODO: Autodetect these two ?*/
 #  ifndef SYSTEMCTL
 #    define SYSTEMCTL "/bin/systemctl"
 #  endif
 
 #  ifndef SERVICE_SCRIPT
 #    define SERVICE_SCRIPT "/sbin/service"
 #  endif
 
 /* *INDENT-OFF* */
 enum lsb_exitcode {
     PCMK_LSB_OK                  = 0,
     PCMK_LSB_UNKNOWN_ERROR       = 1,
     PCMK_LSB_INVALID_PARAM       = 2,
     PCMK_LSB_UNIMPLEMENT_FEATURE = 3,
     PCMK_LSB_INSUFFICIENT_PRIV   = 4,
     PCMK_LSB_NOT_INSTALLED       = 5,
     PCMK_LSB_NOT_CONFIGURED      = 6,
     PCMK_LSB_NOT_RUNNING         = 7,
 };
 
 /* The return codes for the status operation are not the same for other
  * operatios - go figure
  */
 enum lsb_status_exitcode {
     PCMK_LSB_STATUS_OK             = 0,
     PCMK_LSB_STATUS_VAR_PID        = 1,
     PCMK_LSB_STATUS_VAR_LOCK       = 2,
     PCMK_LSB_STATUS_NOT_RUNNING    = 3,
     PCMK_LSB_STATUS_NOT_INSTALLED  = 4,
 };
 
 /* Uniform exit codes
  * Everything is mapped to its OCF equivalent so that Pacemaker only deals with one set of codes
  */
 enum ocf_exitcode {
     PCMK_OCF_OK                   = 0,
     PCMK_OCF_UNKNOWN_ERROR        = 1,
     PCMK_OCF_INVALID_PARAM        = 2,
     PCMK_OCF_UNIMPLEMENT_FEATURE  = 3,
     PCMK_OCF_INSUFFICIENT_PRIV    = 4,
     PCMK_OCF_NOT_INSTALLED        = 5,
     PCMK_OCF_NOT_CONFIGURED       = 6,
     PCMK_OCF_NOT_RUNNING          = 7,  /* End of overlap with LSB */
     PCMK_OCF_RUNNING_MASTER       = 8,
     PCMK_OCF_FAILED_MASTER        = 9,
 
 
     /* 150-199	reserved for application use */
     PCMK_OCF_EXEC_ERROR    = 192, /* Generic problem invoking the agent */
     PCMK_OCF_UNKNOWN       = 193, /* State of the service is unknown - used for recording in-flight operations */
     PCMK_OCF_SIGNAL        = 194,
     PCMK_OCF_NOT_SUPPORTED = 195,
     PCMK_OCF_PENDING       = 196,
     PCMK_OCF_CANCELLED     = 197,
     PCMK_OCF_TIMEOUT       = 198,
     PCMK_OCF_OTHER_ERROR   = 199, /* Keep the same codes as PCMK_LSB */
 };
 
 enum op_status {
     PCMK_LRM_OP_PENDING = -1,
     PCMK_LRM_OP_DONE,
     PCMK_LRM_OP_CANCELLED,
     PCMK_LRM_OP_TIMEOUT,
     PCMK_LRM_OP_NOTSUPPORTED,
     PCMK_LRM_OP_ERROR,
     PCMK_LRM_OP_ERROR_HARD,
     PCMK_LRM_OP_ERROR_FATAL,
     PCMK_LRM_OP_NOT_INSTALLED,
 };
 
 enum nagios_exitcode {
     NAGIOS_STATE_OK        = 0,
     NAGIOS_STATE_WARNING   = 1,
     NAGIOS_STATE_CRITICAL  = 2,
     NAGIOS_STATE_UNKNOWN   = 3,
     NAGIOS_STATE_DEPENDENT = 4,
 
     NAGIOS_INSUFFICIENT_PRIV = 100,
     NAGIOS_NOT_INSTALLED     = 101,
 };
 /* *INDENT-ON* */
 
     typedef struct svc_action_private_s svc_action_private_t;
     typedef struct svc_action_s {
         char *id;
         char *rsc;
         char *action;
         int interval;
 
         char *standard;
         char *provider;
         char *agent;
 
         int timeout;
         GHashTable *params;
 
         int rc;
         int pid;
         int cancel;
         int status;
         int sequence;
         int expected_rc;
 
         char *stderr_data;
         char *stdout_data;
 
     /**
      * Data stored by the creator of the action.
      *
      * This may be used to hold data that is needed later on by a callback,
      * for example.
      */
         void *cb_data;
 
         svc_action_private_t *opaque;
 
     } svc_action_t;
 
 /**
  * Get a list of files or directories in a given path
  *
  * \param[in] root full path to a directory to read
  * \param[in] files true to get a list of files, false for a list of directories
  *
  * \return a list of what was found.  The list items are gchar *.  This list _must_
  *         be destroyed using g_list_free_full(list, free).
  */
     GList *get_directory_list(const char *root, gboolean files, gboolean executable);
 
 /**
  * Get a list of services
  *
  * \return a list of services.  The list items are gchar *.  This list _must_
  *         be destroyed using g_list_free_full(list, free).
  */
     GList *services_list(void);
 
 /**
  * Get a list of providers
  *
  * \param[in] the standard for providers to check for (such as "ocf")
  *
  * \return a list of providers.  The list items are gchar *.  This list _must_
  *         be destroyed using g_list_free_full(list, free).
  */
     GList *resources_list_providers(const char *standard);
 
 /**
  * Get a list of resource agents
  *
  * \param[in] the standard for research agents to check for
  *            (such as "ocf", "lsb", or "windows")
  *
  * \return a list of resource agents.  The list items are gchar *.  This list _must_
  *         be destroyed using g_list_free_full(list, free).
  */
     GList *resources_list_agents(const char *standard, const char *provider);
 
 /**
  * Get list of available standards
  *
  * \return a list of resource standards. The list items are char *. This list _must_
  *         be destroyed using g_list_free_full(list, free).
  */
     GList *resources_list_standards(void);
 
     svc_action_t *services_action_create(const char *name, const char *action,
                                          int interval /* ms */ , int timeout /* ms */ );
 
 /**
  * Create a resources action.
  *
  * \param[in] timeout the timeout in milliseconds
  * \param[in] interval how often to repeat this action, in milliseconds.
  *            If this value is 0, only execute this action one time.
  *
  * \post After the call, 'params' is owned, and later free'd by the svc_action_t result
  */
     svc_action_t *resources_action_create(const char *name, const char *standard,
                                           const char *provider, const char *agent,
                                           const char *action, int interval /* ms */ ,
                                           int timeout /* ms */ , GHashTable * params);
 
+/**
+ * Kick a recurring action so it is scheduled immediately for re-execution
+ */
+    gboolean services_action_kick(const char *name, const char *action, int interval /* ms */);
+
 /**
  * Find the first class that can provide service::${agent}
  *
  * \param[in] agent which agent to search for
  * \return NULL, or the first class that provides the named agent
  */
     const char *resources_find_service_class(const char *agent);
 
 /**
  * Utilize services API to execute an arbitrary command.
  *
  * This API has useful infrastructure in place to be able to run a command
  * in the background and get notified via a callback when the command finishes.
  *
  * \param[in] exec command to execute
  * \param[in] args arguments to the command, NULL terminated
  *
  * \return a svc_action_t object, used to pass to the execute function
  * (services_action_sync() or services_action_async()) and is
  * provided to the callback.
  */
     svc_action_t *services_action_create_generic(const char *exec, const char *args[]);
 
     void
      services_action_free(svc_action_t * op);
 
     gboolean services_action_sync(svc_action_t * op);
 
 /**
  * Run an action asynchronously.
  *
  * \param[in] op services action data
  * \param[in] action_callback callback for when the action completes
  *
  * \retval TRUE succesfully started execution
  * \retval FALSE failed to start execution, no callback will be received
  */
     gboolean services_action_async(svc_action_t * op, void (*action_callback) (svc_action_t *));
 
     gboolean services_action_cancel(const char *name, const char *action, int interval);
 
     static inline const char *services_lrm_status_str(enum op_status status) {
         switch (status) {
             case PCMK_LRM_OP_PENDING:
                 return "pending";
                 case PCMK_LRM_OP_DONE:return "complete";
                 case PCMK_LRM_OP_CANCELLED:return "Cancelled";
                 case PCMK_LRM_OP_TIMEOUT:return "Timed Out";
                 case PCMK_LRM_OP_NOTSUPPORTED:return "NOT SUPPORTED";
                 case PCMK_LRM_OP_ERROR:return "Error";
                 case PCMK_LRM_OP_NOT_INSTALLED:return "Not installed";
                 default:return "UNKNOWN!";
     }} static inline const char *services_ocf_exitcode_str(enum ocf_exitcode code) {
         switch (code) {
             case PCMK_OCF_OK:
                 return "ok";
             case PCMK_OCF_UNKNOWN_ERROR:
                 return "unknown error";
             case PCMK_OCF_INVALID_PARAM:
                 return "invalid parameter";
             case PCMK_OCF_UNIMPLEMENT_FEATURE:
                 return "unimplemented feature";
             case PCMK_OCF_INSUFFICIENT_PRIV:
                 return "insufficient privileges";
             case PCMK_OCF_NOT_INSTALLED:
                 return "not installed";
             case PCMK_OCF_NOT_CONFIGURED:
                 return "not configured";
             case PCMK_OCF_NOT_RUNNING:
                 return "not running";
             case PCMK_OCF_RUNNING_MASTER:
                 return "master";
             case PCMK_OCF_FAILED_MASTER:
                 return "master (failed)";
             case PCMK_OCF_SIGNAL:
                 return "OCF_SIGNAL";
             case PCMK_OCF_NOT_SUPPORTED:
                 return "OCF_NOT_SUPPORTED";
             case PCMK_OCF_PENDING:
                 return "OCF_PENDING";
             case PCMK_OCF_CANCELLED:
                 return "OCF_CANCELLED";
             case PCMK_OCF_TIMEOUT:
                 return "OCF_TIMEOUT";
             case PCMK_OCF_OTHER_ERROR:
                 return "OCF_OTHER_ERROR";
             default:
                 return "unknown";
         }
     }
 
     static inline enum ocf_exitcode
      services_get_ocf_exitcode(char *action, int lsb_exitcode) {
         if (action != NULL && strcmp("status", action) == 0) {
             switch (lsb_exitcode) {
                 case PCMK_LSB_STATUS_OK:
                     return PCMK_OCF_OK;
                 case PCMK_LSB_STATUS_VAR_PID:
                     return PCMK_OCF_NOT_RUNNING;
                 case PCMK_LSB_STATUS_VAR_LOCK:
                     return PCMK_OCF_NOT_RUNNING;
                 case PCMK_LSB_STATUS_NOT_RUNNING:
                     return PCMK_OCF_NOT_RUNNING;
                 case PCMK_LSB_STATUS_NOT_INSTALLED:
                     return PCMK_OCF_UNKNOWN_ERROR;
                 default:
                     return PCMK_OCF_UNKNOWN_ERROR;
             }
 
         } else if (lsb_exitcode > PCMK_LSB_NOT_RUNNING) {
             return PCMK_OCF_UNKNOWN_ERROR;
         }
 
         /* For non-status operations, the PCMK_LSB and PCMK_OCF share error code meaning
          * for rc <= 7 */
         return (enum ocf_exitcode)lsb_exitcode;
     }
 
 #  ifdef __cplusplus
 }
 #  endif
 
 #endif                          /* __PCMK_SERVICES__ */
diff --git a/lib/cluster/cluster.c b/lib/cluster/cluster.c
index 299878e0ae..ada773e817 100644
--- a/lib/cluster/cluster.c
+++ b/lib/cluster/cluster.c
@@ -1,637 +1,647 @@
 /*
  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 #include <dlfcn.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <time.h>
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 
 #include <crm/common/ipc.h>
 #include <crm/cluster/internal.h>
 
 CRM_TRACE_INIT_DATA(cluster);
 
 #if SUPPORT_HEARTBEAT
 void *hb_library = NULL;
 #endif
 
 static char *
 get_heartbeat_uuid(const char *uname)
 {
     char *uuid_calc = NULL;
 
 #if SUPPORT_HEARTBEAT
     cl_uuid_t uuid_raw;
     const char *unknown = "00000000-0000-0000-0000-000000000000";
 
     if (heartbeat_cluster == NULL) {
         crm_warn("No connection to heartbeat, using uuid=uname");
         return NULL;
     } else if(uname == NULL) {
         return NULL;
     }
 
     if (heartbeat_cluster->llc_ops->get_uuid_by_name(heartbeat_cluster, uname, &uuid_raw) ==
         HA_FAIL) {
         crm_err("get_uuid_by_name() call failed for host %s", uname);
         free(uuid_calc);
         return NULL;
     }
 
     uuid_calc = calloc(1, 50);
     cl_uuid_unparse(&uuid_raw, uuid_calc);
 
     if (safe_str_eq(uuid_calc, unknown)) {
         crm_warn("Could not calculate UUID for %s", uname);
         free(uuid_calc);
         return NULL;
     }
 #endif
     return uuid_calc;
 }
 
 static gboolean
 uname_is_uuid(void)
 {
     static const char *uuid_pref = NULL;
 
     if (uuid_pref == NULL) {
         uuid_pref = getenv("PCMK_uname_is_uuid");
     }
 
     if (uuid_pref == NULL) {
         /* true is legacy mode */
         uuid_pref = "false";
     }
 
     return crm_is_true(uuid_pref);
 }
 
 int
 get_corosync_id(int id, const char *uuid)
 {
     if (id == 0 && !uname_is_uuid() && is_corosync_cluster()) {
         id = crm_atoi(uuid, "0");
     }
 
     return id;
 }
 
 char *
 get_corosync_uuid(crm_node_t *node)
 {
     if(node == NULL) {
         return NULL;
 
     } else if (!uname_is_uuid() && is_corosync_cluster()) {
         if (node->id > 0) {
             int len = 32;
             char *buffer = NULL;
 
             buffer = calloc(1, (len + 1));
             if (buffer != NULL) {
                 snprintf(buffer, len, "%u", node->id);
             }
 
             return buffer;
 
         } else {
             crm_info("Node %s is not yet known by corosync", node->uname);
         }
 
     } else if (node->uname != NULL) {
         return strdup(node->uname);
     }
 
     return NULL;
 }
 
 const char *
 crm_peer_uuid(crm_node_t *peer)
 {
     char *uuid = NULL;
     enum cluster_type_e type = get_cluster_type();
 
     /* avoid blocking heartbeat calls where possible */
     if(peer == NULL) {
         return NULL;
 
     } else if (peer->uuid) {
         return peer->uuid;
     }
 
     switch (type) {
         case pcmk_cluster_corosync:
             uuid = get_corosync_uuid(peer);
             break;
 
         case pcmk_cluster_cman:
         case pcmk_cluster_classic_ais:
             if (peer->uname) {
                 uuid = strdup(peer->uname);
             }
             break;
 
         case pcmk_cluster_heartbeat:
             uuid = get_heartbeat_uuid(peer->uname);
             break;
 
         case pcmk_cluster_unknown:
         case pcmk_cluster_invalid:
             crm_err("Unsupported cluster type");
             break;
     }
 
     peer->uuid = uuid;
     return peer->uuid;
 }
 
 gboolean
 crm_cluster_connect(crm_cluster_t * cluster)
 {
     enum cluster_type_e type = get_cluster_type();
 
     crm_notice("Connecting to cluster infrastructure: %s", name_for_cluster_type(type));
 #if SUPPORT_COROSYNC
     if (is_openais_cluster()) {
         crm_peer_init();
         return init_cs_connection(cluster);
     }
 #endif
 
 #if SUPPORT_HEARTBEAT
     if (is_heartbeat_cluster()) {
         int rv;
 
         /* coverity[var_deref_op] False positive */
         if (cluster->hb_conn == NULL) {
             /* No object passed in, create a new one. */
             ll_cluster_t *(*new_cluster) (const char *llctype) =
                 find_library_function(&hb_library, HEARTBEAT_LIBRARY, "ll_cluster_new", 1);
 
             cluster->hb_conn = (*new_cluster) ("heartbeat");
             /* dlclose(handle); */
 
         } else {
             /* Object passed in. Disconnect first, then reconnect below. */
             cluster->hb_conn->llc_ops->signoff(cluster->hb_conn, FALSE);
         }
 
         /* make sure we are disconnected first with the old object, if any. */
         if (heartbeat_cluster && heartbeat_cluster != cluster->hb_conn) {
             heartbeat_cluster->llc_ops->signoff(heartbeat_cluster, FALSE);
         }
 
         CRM_ASSERT(cluster->hb_conn != NULL);
         heartbeat_cluster = cluster->hb_conn;
 
         rv = register_heartbeat_conn(cluster);
         if (rv) {
             /* we'll benefit from a bigger queue length on heartbeat side.
              * Otherwise, if peers send messages faster than we can consume
              * them right now, heartbeat messaging layer will kick us out once
              * it's (small) default queue fills up :(
              * If we fail to adjust the sendq length, that's not yet fatal, though.
              */
             if (HA_OK != heartbeat_cluster->llc_ops->set_sendq_len(heartbeat_cluster, 1024)) {
                 crm_warn("Cannot set sendq length: %s",
                          heartbeat_cluster->llc_ops->errmsg(heartbeat_cluster));
             }
         }
         return rv;
     }
 #endif
     crm_info("Unsupported cluster stack: %s", getenv("HA_cluster_type"));
     return FALSE;
 }
 
 void
 crm_cluster_disconnect(crm_cluster_t * cluster)
 {
     enum cluster_type_e type = get_cluster_type();
     const char *type_str = name_for_cluster_type(type);
 
     crm_info("Disconnecting from cluster infrastructure: %s", type_str);
 #if SUPPORT_COROSYNC
     if (is_openais_cluster()) {
         crm_peer_destroy();
         terminate_cs_connection(cluster);
         crm_info("Disconnected from %s", type_str);
         return;
     }
 #endif
 
 #if SUPPORT_HEARTBEAT
     if (is_heartbeat_cluster()) {
         if (cluster == NULL) {
             crm_info("No cluster connection");
             return;
 
         } else if (cluster->hb_conn) {
             cluster->hb_conn->llc_ops->signoff(cluster->hb_conn, TRUE);
             cluster->hb_conn = NULL;
             crm_info("Disconnected from %s", type_str);
             return;
 
         } else {
             crm_info("No %s connection", type_str);
             return;
         }
     }
 #endif
     crm_info("Unsupported cluster stack: %s", getenv("HA_cluster_type"));
 }
 
 gboolean
 send_cluster_message(crm_node_t * node, enum crm_ais_msg_types service, xmlNode * data,
                      gboolean ordered)
 {
 
 #if SUPPORT_COROSYNC
     if (is_openais_cluster()) {
         return send_cluster_message_cs(data, FALSE, node, service);
     }
 #endif
 #if SUPPORT_HEARTBEAT
     if (is_heartbeat_cluster()) {
         return send_ha_message(heartbeat_cluster, data, node ? node->uname : NULL, ordered);
     }
 #endif
     return FALSE;
 }
 
 const char *
 get_local_node_name(void)
 {
     static char *name = NULL;
 
     if(name) {
         return name;
     }
     name = get_node_name(0);
     return name;
 }
 
 char *
 get_node_name(uint32_t nodeid)
 {
     char *name = NULL;
     enum cluster_type_e stack = get_cluster_type();
 
     switch (stack) {
         case pcmk_cluster_heartbeat:
             break;
 
 #if SUPPORT_PLUGIN
         case pcmk_cluster_classic_ais:
             name = classic_node_name(nodeid);
             break;
 #else
 #  if SUPPORT_COROSYNC
         case pcmk_cluster_corosync:
             name = corosync_node_name(0, nodeid);
             break;
 #  endif
 #endif
 
 #if SUPPORT_CMAN
         case pcmk_cluster_cman:
             name = cman_node_name(nodeid);
             break;
 #endif
 
         default:
             crm_err("Unknown cluster type: %s (%d)", name_for_cluster_type(stack), stack);
     }
 
     if(name == NULL && nodeid == 0) {
         struct utsname res;
         int rc = uname(&res);
 
         if (rc == 0) {
             crm_notice("Defaulting to uname -n for the local %s node name",
                        name_for_cluster_type(stack));
             name = strdup(res.nodename);
         }
 
         if (name == NULL) {
             crm_err("Could not obtain the local %s node name", name_for_cluster_type(stack));
             crm_exit(DAEMON_RESPAWN_STOP);
         }
     }
 
     if (name == NULL) {
         crm_notice("Could not obtain a node name for %s nodeid %u",
                    name_for_cluster_type(stack), nodeid);
     }
     return name;
 }
 
 /* Only used by update_failcount() in te_utils.c */
 const char *
 crm_peer_uname(const char *uuid)
 {
     GHashTableIter iter;
     crm_node_t *node = NULL;
 
     CRM_CHECK(uuid != NULL, return NULL);
 
     /* remote nodes have the same uname and uuid */
     if (g_hash_table_lookup(crm_remote_peer_cache, uuid)) {
         return uuid;
     }
 
     /* avoid blocking calls where possible */
     g_hash_table_iter_init(&iter, crm_peer_cache);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
         if(node->uuid && strcasecmp(node->uuid, uuid) == 0) {
             if(node->uname) {
                 return node->uname;
             }
             break;
         }
     }
 
 #if SUPPORT_COROSYNC
     if (is_openais_cluster()) {
         if (uname_is_uuid() == FALSE && is_corosync_cluster()) {
             uint32_t id = crm_int_helper(uuid, NULL);
 
             node = crm_get_peer(id, NULL);
 
         } else {
             node = crm_get_peer(0, uuid);
         }
 
         if (node) {
             crm_info("Setting uuid for node %s[%u] to '%s'", node->uname, node->id, uuid);
             node->uuid = strdup(uuid);
             if(node->uname) {
                 return node->uname;
             }
         }
         return NULL;
     }
 #endif
 
 #if SUPPORT_HEARTBEAT
     if (is_heartbeat_cluster()) {
         if (heartbeat_cluster != NULL) {
             cl_uuid_t uuid_raw;
             char *uuid_copy = strdup(uuid);
             char *uname = malloc(MAX_NAME);
 
             cl_uuid_parse(uuid_copy, &uuid_raw);
 
             if (heartbeat_cluster->llc_ops->get_name_by_uuid(heartbeat_cluster, &uuid_raw, uname,
                                                              MAX_NAME) == HA_FAIL) {
                 crm_err("Could not calculate uname for %s", uuid);
             } else {
                 node = crm_get_peer(0, uname);
             }
 
             free(uuid_copy);
             free(uname);
         }
 
         if (node) {
             crm_info("Setting uuid for node %s to '%s'", node->uname, uuid);
             node->uuid = strdup(uuid);
             if(node->uname) {
                 return node->uname;
             }
         }
         return NULL;
     }
 #endif
 
     return NULL;
 }
 
 void
 set_uuid(xmlNode *xml, const char *attr, crm_node_t *node)
 {
     const char *uuid_calc = crm_peer_uuid(node);
 
     crm_xml_add(xml, attr, uuid_calc);
     return;
 }
 
 const char *
 name_for_cluster_type(enum cluster_type_e type)
 {
     switch (type) {
         case pcmk_cluster_classic_ais:
             return "classic openais (with plugin)";
         case pcmk_cluster_cman:
             return "cman";
         case pcmk_cluster_corosync:
             return "corosync";
         case pcmk_cluster_heartbeat:
             return "heartbeat";
         case pcmk_cluster_unknown:
             return "unknown";
         case pcmk_cluster_invalid:
             return "invalid";
     }
     crm_err("Invalid cluster type: %d", type);
     return "invalid";
 }
 
 /* Do not expose these two */
 int set_cluster_type(enum cluster_type_e type);
 static enum cluster_type_e cluster_type = pcmk_cluster_unknown;
 
 int
 set_cluster_type(enum cluster_type_e type)
 {
     if (cluster_type == pcmk_cluster_unknown) {
         crm_info("Cluster type set to: %s", name_for_cluster_type(type));
         cluster_type = type;
         return 0;
 
     } else if (cluster_type == type) {
         return 0;
 
     } else if (pcmk_cluster_unknown == type) {
         cluster_type = type;
         return 0;
     }
 
     crm_err("Cluster type already set to %s, ignoring %s",
             name_for_cluster_type(cluster_type), name_for_cluster_type(type));
     return -1;
 }
 enum cluster_type_e
 get_cluster_type(void)
 {
     bool detected = FALSE;
     const char *cluster = NULL;
 
     /* Return the previous calculation, if any */
     if (cluster_type != pcmk_cluster_unknown) {
         return cluster_type;
     }
 
     cluster = getenv("HA_cluster_type");
 
 #if SUPPORT_HEARTBEAT
     /* If nothing is defined in the environment, try heartbeat (if supported) */
     if(cluster == NULL) {
         ll_cluster_t *hb;
         ll_cluster_t *(*new_cluster) (const char *llctype) = find_library_function(
             &hb_library, HEARTBEAT_LIBRARY, "ll_cluster_new", 1);
 
         hb = (*new_cluster) ("heartbeat");
 
         crm_debug("Testing with Heartbeat");
-        if (hb->llc_ops->signon(hb, crm_system_name) == HA_OK) {
+        /*
+         * Test as "casual" client (clientid == NULL; will be replaced by
+         * current pid).  We are trying to detect if we can communicate with
+         * heartbeat, not if we can register as some specific service.
+         * Otherwise all but one of several concurrent invocations will get
+         * HA_FAIL because of:
+         * WARN: duplicate client add request
+         * ERROR: api_process_registration_msg: cannot add client()
+         * and then likely fail :(
+         */
+        if (hb->llc_ops->signon(hb, NULL) == HA_OK) {
             hb->llc_ops->signoff(hb, FALSE);
 
             cluster_type = pcmk_cluster_heartbeat;
             detected = TRUE;
             goto done;
         }
     }
 #endif
 
 #if SUPPORT_COROSYNC
     /* If nothing is defined in the environment, try corosync (if supported) */
     if(cluster == NULL) {
         crm_debug("Testing with Corosync");
         cluster_type = find_corosync_variant();
         if (cluster_type != pcmk_cluster_unknown) {
             detected = TRUE;
             goto done;
         }
     }
 #endif
 
     /* Something was defined in the environment, test it against what we support */
     crm_info("Verifying cluster type: '%s'", cluster?cluster:"-unspecified-");
     if (cluster == NULL) {
 
 #if SUPPORT_HEARTBEAT
     } else if (safe_str_eq(cluster, "heartbeat")) {
         cluster_type = pcmk_cluster_heartbeat;
 #endif
 
 #if SUPPORT_COROSYNC
     } else if (safe_str_eq(cluster, "openais")
                || safe_str_eq(cluster, "classic openais (with plugin)")) {
         cluster_type = pcmk_cluster_classic_ais;
 
     } else if (safe_str_eq(cluster, "corosync")) {
         cluster_type = pcmk_cluster_corosync;
 #endif
 
 #if SUPPORT_CMAN
     } else if (safe_str_eq(cluster, "cman")) {
         cluster_type = pcmk_cluster_cman;
 #endif
 
     } else {
         cluster_type = pcmk_cluster_invalid;
         goto done; /* Keep the compiler happy when no stacks are supported */
     }
 
   done:
     if (cluster_type == pcmk_cluster_unknown) {
         crm_notice("Could not determin the current cluster type");
 
     } else if (cluster_type == pcmk_cluster_invalid) {
         crm_notice("This installation does not support the '%s' cluster infrastructure: terminating.",
                    cluster);
         crm_exit(DAEMON_RESPAWN_STOP);
 
     } else {
         crm_info("%s an active '%s' cluster", detected?"Detected":"Assuming", name_for_cluster_type(cluster_type));
     }
 
     return cluster_type;
 }
 
 gboolean
 is_cman_cluster(void)
 {
     return get_cluster_type() == pcmk_cluster_cman;
 }
 
 gboolean
 is_corosync_cluster(void)
 {
     return get_cluster_type() == pcmk_cluster_corosync;
 }
 
 gboolean
 is_classic_ais_cluster(void)
 {
     return get_cluster_type() == pcmk_cluster_classic_ais;
 }
 
 gboolean
 is_openais_cluster(void)
 {
     enum cluster_type_e type = get_cluster_type();
 
     if (type == pcmk_cluster_classic_ais) {
         return TRUE;
     } else if (type == pcmk_cluster_corosync) {
         return TRUE;
     } else if (type == pcmk_cluster_cman) {
         return TRUE;
     }
     return FALSE;
 }
 
 gboolean
 is_heartbeat_cluster(void)
 {
     return get_cluster_type() == pcmk_cluster_heartbeat;
 }
 
 gboolean
 node_name_is_valid(const char *key, const char *name)
 {
     int octet;
 
     if (name == NULL) {
         crm_trace("%s is empty", key);
         return FALSE;
 
     } else if (sscanf(name, "%d.%d.%d.%d", &octet, &octet, &octet, &octet) == 4) {
         crm_trace("%s contains an ipv4 address, ignoring: %s", key, name);
         return FALSE;
 
     } else if (strstr(name, ":") != NULL) {
         crm_trace("%s contains an ipv6 address, ignoring: %s", key, name);
         return FALSE;
     }
     crm_trace("%s is valid", key);
     return TRUE;
 }
diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c
index 4f21d69724..f5df4fc2d4 100644
--- a/lib/common/mainloop.c
+++ b/lib/common/mainloop.c
@@ -1,1111 +1,1180 @@
 /*
  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <stdlib.h>
 #include <signal.h>
 #include <errno.h>
 
 #include <sys/wait.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/ipcs.h>
 
 struct mainloop_child_s {
     pid_t pid;
     char *desc;
     unsigned timerid;
     unsigned watchid;
     gboolean timeout;
     void *privatedata;
 
     /* 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 wont 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;
 }
 
 typedef struct signal_s {
     crm_trigger_t trigger;      /* must be first */
     void (*handler) (int sig);
     int signal;
 
 } crm_signal_t;
 
 static crm_signal_t *crm_signals[NSIG];
 
 static gboolean
 crm_signal_dispatch(GSource * source, GSourceFunc callback, gpointer userdata)
 {
     crm_signal_t *sig = (crm_signal_t *) source;
 
     if(sig->signal != SIGCHLD) {
         crm_info("Invoking handler for signal %d: %s", sig->signal, strsignal(sig->signal));
     }
 
     sig->trigger.trigger = FALSE;
     if (sig->handler) {
         sig->handler(sig->signal);
     }
     return TRUE;
 }
 
 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]);
     }
 }
 
 static GSourceFuncs crm_signal_funcs = {
     crm_trigger_prepare,
     crm_trigger_check,
     crm_signal_dispatch,
     crm_trigger_finalize,
 };
 
 gboolean
 crm_signal(int sig, void (*dispatch) (int sig))
 {
     sigset_t mask;
     struct sigaction sa;
     struct sigaction old;
 
     if (sigemptyset(&mask) < 0) {
         crm_perror(LOG_ERR, "Call to sigemptyset failed");
         return FALSE;
     }
 
     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_perror(LOG_ERR, "Could not install signal handler for signal %d", sig);
         return FALSE;
     }
 
     return TRUE;
 }
 
 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(sig, mainloop_signal_handler) == FALSE) {
         crm_signal_t *tmp = crm_signals[sig];
 
         crm_signals[sig] = NULL;
 
         mainloop_destroy_trigger((crm_trigger_t *) tmp);
         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)
 {
     crm_signal_t *tmp = NULL;
 
     if (sig >= NSIG || sig < 0) {
         crm_err("Signal %d is out of range", sig);
         return FALSE;
 
     } else if (crm_signal(sig, NULL) == FALSE) {
         crm_perror(LOG_ERR, "Could not uninstall signal handler for signal %d", sig);
         return FALSE;
 
     } else if (crm_signals[sig] == NULL) {
         return TRUE;
     }
 
     crm_trace("Destroying signal %d", sig);
     tmp = crm_signals[sig];
     crm_signals[sig] = NULL;
     mainloop_destroy_trigger((crm_trigger_t *) tmp);
     return TRUE;
 }
 
 static qb_array_t *gio_map = NULL;
 
 void
 mainloop_cleanup(void) 
 {
     if(gio_map) {
         qb_array_free(gio_map);
     }
 }
 
 /*
  * 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 (condition & G_IO_NVAL) {
         crm_trace("Marking failed adaptor %p unused", adaptor);
         adaptor->is_used = QB_FALSE;
     }
 
     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 = QB_FALSE;
     adaptor->source = 0;
 }
 
 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)
 {
     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 adapater %p", fd, adaptor);
     if (adaptor->is_used) {
         crm_err("Adapter for descriptor %d is still in-use", fd);
         return -EEXIST;
     }
 
     /* 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;
     }
 
     /* 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 = QB_TRUE;
     adaptor->source =
         g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, 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_mod(enum qb_loop_priority p, int32_t fd, int32_t evts,
                       void *data, qb_ipcs_dispatch_fn_t fn)
 {
     return 0;
 }
 
 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) {
         crm_trace("Marking adaptor %p unused", adaptor);
         if (adaptor->source) {
             g_source_remove(adaptor->source);
             adaptor->source = 0;
         }
         adaptor->is_used = QB_FALSE;
     }
     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)
 {
     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);
 
 #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] = %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[%p] closed (I/O 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);
         client->ipc = conn;
         client->destroy_fn = callbacks->destroy;
         client->dispatch_fn_ipc = callbacks->dispatch;
     }
 
     if (conn && client == NULL) {
         crm_trace("Connection to %s failed", name);
         crm_ipc_close(conn);
         crm_ipc_destroy(conn);
     }
 
     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));
         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);
     }
 
     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);
         }
     }
 }
 
 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)
+{
+    if (kill(-child->pid, SIGKILL) < 0) {
+        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);
 
-    if (kill(child->pid, SIGKILL) < 0) {
-        if (errno == ESRCH) {
-            /* Nothing left to do */
-            return FALSE;
-        }
-        crm_perror(LOG_ERR, "kill(%d, KILL) failed", child->pid);
-    }
-
     child->timerid = g_timeout_add(5000, child_timeout_callback, child);
     return FALSE;
 }
 
 static GListPtr child_list = NULL;
 
+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) {
+        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) {
-        int rc = 0;
-        int core = 0;
-        int signo = 0;
-        int status = 0;
-        int exitcode = 0;
-
         GListPtr saved = NULL;
         mainloop_child_t *child = iter->data;
+        exited = child_waitpid(child, WNOHANG);
 
-        rc = waitpid(child->pid, &status, WNOHANG);
-        if(rc == 0) {
-            iter = iter->next;
+        saved = iter;
+        iter = iter->next;
+
+        if (exited == FALSE) {
             continue;
+        }
+        crm_trace("Removing process entry %p for %d", child, child->pid);
 
-        } else if(rc != child->pid) {
-            signo = signal;
-            exitcode = 1;
-            status = 1;
-            crm_perror(LOG_ERR, "Call to waitpid(%d) failed", child->pid);
+        child_list = g_list_remove_link(child_list, saved);
+        g_list_free(saved);
+        child_free(child);
+    }
+}
 
-        } else {
-            crm_trace("Managed process %d exited: %p", child->pid, child);
+static gboolean
+child_signal_init(gpointer p)
+{
+    /* Do NOT use g_child_watch_add() and friends, they rely on pthreads */
+    mainloop_add_signal(SIGCHLD, child_death_dispatch);
 
-            if (WIFEXITED(status)) {
-                exitcode = WEXITSTATUS(status);
-                crm_trace("Managed process %d (%s) exited with rc=%d", child->pid, child->desc, exitcode);
+    /* In case they terminated before the signal handler was installed */
+    child_death_dispatch(SIGCHLD);
+    return FALSE;
+}
 
-            } 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
-        }
+gboolean
+mainloop_child_kill(pid_t pid)
+{
+    GListPtr iter;
+    mainloop_child_t *child = NULL;
 
-        if (child->callback) {
-            child->callback(child, child->pid, core, signo, exitcode);
+    for (iter = child_list; iter != NULL; iter = iter->next) {
+        child = iter->data;
+        if (pid == child->pid) {
+            break;
         }
+    }
 
-        crm_trace("Removing process entry %p for %d", child, child->pid);
-
-        saved = iter;
-        iter = iter->next;
+    if (child == NULL) {
+        return FALSE;
+    }
 
-        child_list = g_list_remove_link(child_list, saved);
-        g_list_free(saved);
+    if (child_kill_helper(child) != 0) {
+        /* failed to terminate child process */
+        return FALSE;
+    }
 
-        if (child->timerid != 0) {
-            crm_trace("Removing timer %d", child->timerid);
-            g_source_remove(child->timerid);
-            child->timerid = 0;
-        }
-        free(child->desc);
-        free(child);
+    /* It is impossible to block SIGKILL, this allows us to
+     * call waitpid without WNOHANG here */
+    if (child_waitpid(child, 0) == FALSE) {
+        /* not much we can do if this occurs */
+        return FALSE;
     }
+
+    child_list = g_list_remove(child_list, child);
+    child_free(child);
+    return TRUE;
 }
 
 /* Create/Log a new tracked process
  * To track a process group, use -pid
  */
 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))
 {
     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;
 
     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;
-
-        /* 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);
+        /* 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);
     }
 }
 
 
 struct mainloop_timer_s {
         guint id;
         guint period_ms;
         bool repeat;
         char *name;
         GSourceFunc cb;
         void *userdata;
 };
 
 struct mainloop_timer_s mainloop;
 
 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 its 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 = g_strdup_printf("%s-%u-%d", name, period_ms, repeat);
         } else {
             t->name = g_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);
     }
 }
 
diff --git a/lib/common/utils.c b/lib/common/utils.c
index 29d7965a03..6a96396c6d 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,2520 +1,2515 @@
 /*
  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 #include <dlfcn.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <sys/param.h>
 #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 <ctype.h>
 #include <pwd.h>
 #include <grp.h>
 #include <time.h>
 #include <libgen.h>
 #include <signal.h>
 
 #include <qb/qbdefs.h>
 
 #include <crm/crm.h>
 #include <crm/lrmd.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 <crm/attrd.h>
 #include <libxml2/libxml/relaxng.h>
 
 #ifndef MAXLINE
 #  define MAXLINE 512
 #endif
 
 #ifdef HAVE_GETOPT_H
 #  include <getopt.h>
 #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 node_score_infinity = INFINITY;
 
 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;
 
 int
 crm_exit(int rc)
 {
     mainloop_cleanup();
 
 #if HAVE_LIBXML2
     crm_trace("cleaning up libxml");
     crm_xml_cleanup();
 #endif
 
     crm_trace("exit %d", rc);
     qb_log_fini();
 
     free(crm_short_options);
     free(crm_system_name);
 
     exit(ABS(rc)); /* Always exit with a positive value so that it can be passed to crm_error
                     *
                     * Otherwise the system wraps it around and people
                     * have to jump through hoops figuring out what the
                     * error was
                     */
     return rc;     /* Can never happen, but allows return crm_exit(rc)
                     * where "return rc" was used previously - which
                     * keeps compilers happy.
                     */
 }
 
 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, MINUS_INFINITY_S)) {
 
     } else if (safe_str_eq(value, INFINITY_S)) {
 
     } else {
         crm_int_helper(value, NULL);
     }
 
     if (errno != 0) {
         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;
 }
 
 int
 char2score(const char *score)
 {
     int score_f = 0;
 
     if (score == NULL) {
 
     } else if (safe_str_eq(score, MINUS_INFINITY_S)) {
         score_f = -node_score_infinity;
 
     } else if (safe_str_eq(score, INFINITY_S)) {
         score_f = node_score_infinity;
 
     } else if (safe_str_eq(score, "+" INFINITY_S)) {
         score_f = node_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 > node_score_infinity) {
             score_f = node_score_infinity;
 
         } else if (score_f < 0 && score_f < -node_score_infinity) {
             score_f = -node_score_infinity;
         }
     }
 
     return score_f;
 }
 
 char *
 score2char_stack(int score, char *buf, size_t len)
 {
     if (score >= node_score_infinity) {
         strncpy(buf, INFINITY_S, 9);
     } else if (score <= -node_score_infinity) {
         strncpy(buf, MINUS_INFINITY_S , 10);
     } else {
         return crm_itoa_stack(score, buf, len);
     }
 
     return buf;
 }
 
 char *
 score2char(int score)
 {
     if (score >= node_score_infinity) {
         return strdup(INFINITY_S);
 
     } else if (score <= -node_score_infinity) {
         return strdup("-" 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;
 
     CRM_ASSERT(name != NULL);
 
     if (options != NULL) {
         value = g_hash_table_lookup(options, name);
     }
 
     if (value == NULL && old_name && options != NULL) {
         value = g_hash_table_lookup(options, old_name);
         if (value != NULL) {
             crm_config_warn("Using deprecated name '%s' for"
                             " cluster option '%s'", old_name, name);
             g_hash_table_insert(options, strdup(name), strdup(value));
             value = g_hash_table_lookup(options, old_name);
         }
     }
 
     if (value == NULL) {
         crm_trace("Using default value '%s' for cluster option '%s'", def_value, name);
 
         if (options == NULL) {
             return def_value;
         }
 
         g_hash_table_insert(options, strdup(name), strdup(def_value));
         value = g_hash_table_lookup(options, name);
     }
 
     if (validate && validate(value) == FALSE) {
         crm_config_err("Value '%s' for cluster option '%s' is invalid."
                        "  Defaulting to %s", value, name, def_value);
         g_hash_table_replace(options, strdup(name), strdup(def_value));
         value = g_hash_table_lookup(options, name);
     }
 
     return value;
 }
 
 const char *
 get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, const char *name)
 {
     int lpc = 0;
     const char *value = NULL;
     gboolean found = FALSE;
 
     for (lpc = 0; lpc < len; lpc++) {
         if (safe_str_eq(name, option_list[lpc].name)) {
             found = TRUE;
             value = cluster_option(options,
                                    option_list[lpc].is_valid,
                                    option_list[lpc].name,
                                    option_list[lpc].alt_name, option_list[lpc].default_value);
         }
     }
     CRM_CHECK(found, crm_err("No option named: %s", name));
     CRM_ASSERT(value != NULL);
     return value;
 }
 
 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, "<?xml version=\"1.0\"?>"
             "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
             "<resource-agent name=\"%s\">\n"
             "  <version>%s</version>\n"
             "  <longdesc lang=\"en\">%s</longdesc>\n"
             "  <shortdesc lang=\"en\">%s</shortdesc>\n"
             "  <parameters>\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, "    <parameter name=\"%s\" unique=\"0\">\n"
                 "      <shortdesc lang=\"en\">%s</shortdesc>\n"
                 "      <content type=\"%s\" default=\"%s\"/>\n"
                 "      <longdesc lang=\"en\">%s%s%s</longdesc>\n"
                 "    </parameter>\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, "  </parameters>\n</resource-agent>\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 *
 crm_concat(const char *prefix, const char *suffix, char join)
 {
     int len = 0;
     char *new_str = NULL;
 
     CRM_ASSERT(prefix != NULL);
     CRM_ASSERT(suffix != NULL);
     len = strlen(prefix) + strlen(suffix) + 2;
 
     new_str = malloc(len);
     if(new_str) {
         sprintf(new_str, "%s%c%s", prefix, join, suffix);
         new_str[len - 1] = 0;
     }
     return new_str;
 }
 
 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;
 }
 
 
 char *
 crm_itoa_stack(int an_int, char *buffer, size_t len)
 {
     if (buffer != NULL) {
         snprintf(buffer, len, "%d", an_int);
     }
 
     return buffer;
 }
 
 char *
 crm_itoa(int an_int)
 {
     int len = 32;
     char *buffer = NULL;
 
     buffer = malloc(len + 1);
     if (buffer != NULL) {
         snprintf(buffer, len, "%d", an_int);
     }
 
     return buffer;
 }
 
 void
 crm_build_path(const char *path_c, mode_t mode)
 {
     int offset = 1, len = 0;
     char *path = strdup(path_c);
 
     CRM_CHECK(path != NULL, return);
     for (len = strlen(path); offset < len; offset++) {
         if (path[offset] == '/') {
             path[offset] = 0;
             if (mkdir(path, mode) < 0 && errno != EEXIST) {
                 crm_perror(LOG_ERR, "Could not create directory '%s'", path);
                 break;
             }
             path[offset] = '/';
         }
     }
     if (mkdir(path, mode) < 0 && errno != EEXIST) {
         crm_perror(LOG_ERR, "Could not create directory '%s'", path);
     }
 
     free(path);
 }
 
 int
 crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
 {
     int rc = -1;
     char *buffer = NULL;
     struct passwd pwd;
     struct passwd *pwentry = NULL;
 
     buffer = calloc(1, PW_BUFFER_LEN);
     getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry);
     if (pwentry) {
         rc = 0;
         if (uid) {
             *uid = pwentry->pw_uid;
         }
         if (gid) {
             *gid = pwentry->pw_gid;
         }
         crm_trace("Cluster user %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
 
     } else {
         crm_err("Cluster user %s does not exist", name);
     }
 
     free(buffer);
     return rc;
 }
 
 static int
 crm_version_helper(const char *text, char **end_text)
 {
     int atoi_result = -1;
 
     CRM_ASSERT(end_text != NULL);
 
     errno = 0;
 
     if (text != NULL && text[0] != 0) {
         atoi_result = (int)strtol(text, 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;
     char *ver1_copy = NULL, *ver2_copy = NULL;
     char *rest1 = NULL, *rest2 = NULL;
 
     if (version1 == NULL && version2 == NULL) {
         return 0;
     } else if (version1 == NULL) {
         return -1;
     } else if (version2 == NULL) {
         return 1;
     }
 
     ver1_copy = strdup(version1);
     ver2_copy = strdup(version2);
     rest1 = ver1_copy;
     rest2 = ver2_copy;
 
     while (1) {
         int digit1 = 0;
         int digit2 = 0;
 
         lpc++;
 
         if (rest1 == rest2) {
             break;
         }
 
         if (rest1 != NULL) {
             digit1 = crm_version_helper(rest1, &rest1);
         }
 
         if (rest2 != NULL) {
             digit2 = crm_version_helper(rest2, &rest2);
         }
 
         if (digit1 < digit2) {
             rc = -1;
             break;
 
         } else if (digit1 > digit2) {
             rc = 1;
             break;
         }
 
         if (rest1 != NULL && rest1[0] == '.') {
             rest1++;
         }
         if (rest1 != NULL && rest1[0] == 0) {
             rest1 = NULL;
         }
 
         if (rest2 != NULL && rest2[0] == '.') {
             rest2++;
         }
         if (rest2 != NULL && rest2[0] == 0) {
             rest2 = NULL;
         }
     }
 
     free(ver1_copy);
     free(ver2_copy);
 
     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;
 
 void
 g_hash_destroy_str(gpointer data)
 {
     free(data);
 }
 
 #include <sys/types.h>
 /* #include <stdlib.h> */
 /* #include <limits.h> */
 
 long long
 crm_int_helper(const char *text, char **end_text)
 {
     long long result = -1;
     char *local_end_text = NULL;
     int saved_errno = 0;
 
     errno = 0;
 
     if (text != NULL) {
 #ifdef ANSI_ONLY
         if (end_text != NULL) {
             result = strtol(text, end_text, 10);
         } else {
             result = strtol(text, &local_end_text, 10);
         }
 #else
         if (end_text != NULL) {
             result = strtoll(text, end_text, 10);
         } else {
             result = strtoll(text, &local_end_text, 10);
         }
 #endif
 
         saved_errno = errno;
 /* 		CRM_CHECK(errno != EINVAL); */
         if (errno == EINVAL) {
             crm_err("Conversion of %s failed", text);
             result = -1;
 
         } else if (errno == ERANGE) {
             crm_err("Conversion of %s was clipped: %lld", text, result);
 
         } else if (errno != 0) {
             crm_perror(LOG_ERR, "Conversion of %s failed:", text);
         }
 
         if (local_end_text != NULL && local_end_text[0] != '\0') {
             crm_err("Characters left over after parsing '%s': '%s'", text, local_end_text);
         }
 
         errno = saved_errno;
     }
     return result;
 }
 
 int
 crm_parse_int(const char *text, const char *default_text)
 {
     int atoi_result = -1;
 
     if (text != NULL) {
         atoi_result = crm_int_helper(text, NULL);
         if (errno == 0) {
             return atoi_result;
         }
     }
 
     if (default_text != NULL) {
         atoi_result = crm_int_helper(default_text, NULL);
         if (errno == 0) {
             return atoi_result;
         }
 
     } else {
         crm_err("No default conversion value supplied");
     }
 
     return -1;
 }
 
 gboolean
 safe_str_neq(const char *a, const char *b)
 {
     if (a == b) {
         return FALSE;
 
     } else if (a == NULL || b == NULL) {
         return TRUE;
 
     } else if (strcasecmp(a, b) == 0) {
         return FALSE;
     }
     return TRUE;
 }
 
 gboolean
 crm_is_true(const char *s)
 {
     gboolean ret = FALSE;
 
     if (s != NULL) {
         crm_str_to_boolean(s, &ret);
     }
     return ret;
 }
 
 int
 crm_str_to_boolean(const char *s, int *ret)
 {
     if (s == NULL) {
         return -1;
 
     } else if (strcasecmp(s, "true") == 0
                || strcasecmp(s, "on") == 0
                || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) {
         *ret = TRUE;
         return 1;
 
     } else if (strcasecmp(s, "false") == 0
                || strcasecmp(s, "off") == 0
                || strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0) {
         *ret = FALSE;
         return 1;
     }
     return -1;
 }
 
 #ifndef NUMCHARS
 #  define	NUMCHARS	"0123456789."
 #endif
 
 #ifndef WHITESPACE
 #  define	WHITESPACE	" \t\n\r\f"
 #endif
 
 unsigned long long
 crm_get_interval(const char *input)
 {
     unsigned long long msec = 0;
 
     if (input == NULL) {
         return msec;
 
     } else if (input[0] != 'P') {
         long long tmp = crm_get_msec(input);
 
         if(tmp > 0) {
             msec = tmp;
         }
 
     } else {
         crm_time_t *interval = crm_time_parse_duration(input);
 
         msec = 1000 * crm_time_get_seconds(interval);
         crm_time_free(interval);
     }
 
     return 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);
     msec *= multiplier;
     msec /= divisor;
     /* dret += 0.5; */
     /* msec = (long long)dret; */
     return msec;
 }
 
 char *
 generate_op_key(const char *rsc_id, const char *op_type, int interval)
 {
     int len = 35;
     char *op_id = NULL;
 
     CRM_CHECK(rsc_id != NULL, return NULL);
     CRM_CHECK(op_type != NULL, return NULL);
 
     len += strlen(op_type);
     len += strlen(rsc_id);
     op_id = malloc(len);
     CRM_CHECK(op_id != NULL, return NULL);
     sprintf(op_id, "%s_%s_%d", rsc_id, op_type, interval);
     return op_id;
 }
 
 gboolean
 parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval)
 {
     char *notify = NULL;
     char *mutable_key = NULL;
     char *mutable_key_ptr = NULL;
     int len = 0, offset = 0, ch = 0;
 
     CRM_CHECK(key != NULL, return FALSE);
 
     *interval = 0;
     len = strlen(key);
     offset = len - 1;
 
     crm_trace("Source: %s", key);
 
     while (offset > 0 && isdigit(key[offset])) {
         int digits = len - offset;
 
         ch = key[offset] - '0';
         CRM_CHECK(ch < 10, return FALSE);
         CRM_CHECK(ch >= 0, return FALSE);
         while (digits > 1) {
             digits--;
             ch = ch * 10;
         }
         *interval += ch;
         offset--;
     }
 
     crm_trace("  Interval: %d", *interval);
     CRM_CHECK(key[offset] == '_', return FALSE);
 
     mutable_key = strdup(key);
     mutable_key[offset] = 0;
     offset--;
 
     while (offset > 0 && key[offset] != '_') {
         offset--;
     }
 
     CRM_CHECK(key[offset] == '_', free(mutable_key);
               return FALSE);
 
     mutable_key_ptr = mutable_key + offset + 1;
 
     crm_trace("  Action: %s", mutable_key_ptr);
 
     *op_type = strdup(mutable_key_ptr);
 
     mutable_key[offset] = 0;
     offset--;
 
     CRM_CHECK(mutable_key != mutable_key_ptr, free(mutable_key);
               return FALSE);
 
     notify = strstr(mutable_key, "_post_notify");
     if (notify && safe_str_eq(notify, "_post_notify")) {
         notify[0] = 0;
     }
 
     notify = strstr(mutable_key, "_pre_notify");
     if (notify && safe_str_eq(notify, "_pre_notify")) {
         notify[0] = 0;
     }
 
     crm_trace("  Resource: %s", mutable_key);
     *rsc_id = mutable_key;
 
     return TRUE;
 }
 
 char *
 generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type)
 {
     int len = 12;
     char *op_id = NULL;
 
     CRM_CHECK(rsc_id != NULL, return NULL);
     CRM_CHECK(op_type != NULL, return NULL);
     CRM_CHECK(notify_type != NULL, return NULL);
 
     len += strlen(op_type);
     len += strlen(rsc_id);
     len += strlen(notify_type);
     if(len > 0) {
         op_id = malloc(len);
     }
     if (op_id != NULL) {
         sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type);
     }
     return op_id;
 }
 
 char *
 generate_transition_magic_v202(const char *transition_key, int op_status)
 {
     int len = 80;
     char *fail_state = NULL;
 
     CRM_CHECK(transition_key != NULL, return NULL);
 
     len += strlen(transition_key);
 
     fail_state = malloc(len);
     if (fail_state != NULL) {
         snprintf(fail_state, len, "%d:%s", op_status, transition_key);
     }
     return fail_state;
 }
 
 char *
 generate_transition_magic(const char *transition_key, int op_status, int op_rc)
 {
     int len = 80;
     char *fail_state = NULL;
 
     CRM_CHECK(transition_key != NULL, return NULL);
 
     len += strlen(transition_key);
 
     fail_state = malloc(len);
     if (fail_state != NULL) {
         snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key);
     }
     return fail_state;
 }
 
 gboolean
 decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
                         int *op_status, int *op_rc, int *target_rc)
 {
     int res = 0;
     char *key = NULL;
     gboolean result = TRUE;
 
     CRM_CHECK(magic != NULL, return FALSE);
     CRM_CHECK(op_rc != NULL, return FALSE);
     CRM_CHECK(op_status != NULL, return FALSE);
 
     key = calloc(1, strlen(magic) + 1);
     res = sscanf(magic, "%d:%d;%s", op_status, op_rc, key);
     if (res != 3) {
         crm_warn("Only found %d items in: '%s'", res, magic);
         free(key);
         return FALSE;
     }
 
     CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id, target_rc), result = FALSE);
 
     free(key);
     return result;
 }
 
 char *
 generate_transition_key(int transition_id, int action_id, int target_rc, const char *node)
 {
     int len = 40;
     char *fail_state = NULL;
 
     CRM_CHECK(node != NULL, return NULL);
 
     len += strlen(node);
 
     fail_state = malloc(len);
     if (fail_state != NULL) {
         snprintf(fail_state, len, "%d:%d:%d:%-*s", action_id, transition_id, target_rc, 36, node);
     }
     return fail_state;
 }
 
 gboolean
 decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
                       int *target_rc)
 {
     int res = 0;
     gboolean done = FALSE;
 
     CRM_CHECK(uuid != NULL, return FALSE);
     CRM_CHECK(target_rc != NULL, return FALSE);
     CRM_CHECK(action_id != NULL, return FALSE);
     CRM_CHECK(transition_id != NULL, return FALSE);
 
     *uuid = calloc(1, 37);
     res = sscanf(key, "%d:%d:%d:%36s", action_id, transition_id, target_rc, *uuid);
     switch (res) {
         case 4:
             /* Post Pacemaker 0.6 */
             done = TRUE;
             break;
         case 3:
         case 2:
             /* this can be tricky - the UUID might start with an integer */
 
             /* Until Pacemaker 0.6 */
             done = TRUE;
             *target_rc = -1;
             res = sscanf(key, "%d:%d:%36s", action_id, transition_id, *uuid);
             if (res == 2) {
                 *action_id = -1;
                 res = sscanf(key, "%d:%36s", transition_id, *uuid);
                 CRM_CHECK(res == 2, done = FALSE);
 
             } else if (res != 3) {
                 CRM_CHECK(res == 3, done = FALSE);
             }
             break;
 
         case 1:
             /* Prior to Heartbeat 2.0.8 */
             done = TRUE;
             *action_id = -1;
             *target_rc = -1;
             res = sscanf(key, "%d:%36s", transition_id, *uuid);
             CRM_CHECK(res == 2, done = FALSE);
             break;
         default:
             crm_crit("Unhandled sscanf result (%d) for %s", res, key);
     }
 
     if (strlen(*uuid) != 36) {
         crm_warn("Bad UUID (%s) in sscanf result (%d) for %s", *uuid, res, key);
     }
 
     if (done == FALSE) {
         crm_err("Cannot decode '%s' rc=%d", key, res);
 
         free(*uuid);
         *uuid = NULL;
         *target_rc = -1;
         *action_id = -1;
         *transition_id = -1;
     }
 
     return done;
 }
 
 void
 filter_action_parameters(xmlNode * param_set, const char *version)
 {
     char *key = NULL;
     char *timeout = NULL;
     char *interval = NULL;
 
     const char *attr_filter[] = {
         XML_ATTR_ID,
         XML_ATTR_CRM_VERSION,
         XML_LRM_ATTR_OP_DIGEST,
     };
 
     gboolean do_delete = FALSE;
     int lpc = 0;
     static int meta_len = 0;
 
     if (meta_len == 0) {
         meta_len = strlen(CRM_META);
     }
 
     if (param_set == NULL) {
         return;
     }
 
     for (lpc = 0; lpc < DIMOF(attr_filter); lpc++) {
         xml_remove_prop(param_set, attr_filter[lpc]);
     }
 
     key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
     interval = crm_element_value_copy(param_set, key);
     free(key);
 
     key = crm_meta_name(XML_ATTR_TIMEOUT);
     timeout = crm_element_value_copy(param_set, key);
 
     if (param_set) {
         xmlAttrPtr xIter = param_set->properties;
 
         while (xIter) {
             const char *prop_name = (const char *)xIter->name;
 
             xIter = xIter->next;
             do_delete = FALSE;
             if (strncasecmp(prop_name, CRM_META, meta_len) == 0) {
                 do_delete = TRUE;
             }
 
             if (do_delete) {
                 xml_remove_prop(param_set, prop_name);
             }
         }
     }
 
     if (crm_get_msec(interval) > 0 && compare_version(version, "1.0.8") > 0) {
         /* Re-instate the operation's timeout value */
         if (timeout != NULL) {
             crm_xml_add(param_set, key, timeout);
         }
     }
 
     free(interval);
     free(timeout);
     free(key);
 }
 
 void
 filter_reload_parameters(xmlNode * param_set, const char *restart_string)
 {
     int len = 0;
     char *name = NULL;
     char *match = NULL;
 
     if (param_set == NULL) {
         return;
     }
 
     if (param_set) {
         xmlAttrPtr xIter = param_set->properties;
 
         while (xIter) {
             const char *prop_name = (const char *)xIter->name;
 
             xIter = xIter->next;
             name = NULL;
             len = strlen(prop_name) + 3;
 
             name = malloc(len);
             if(name) {
                 sprintf(name, " %s ", prop_name);
                 name[len - 1] = 0;
                 match = strstr(restart_string, name);
             }
 
             if (match == NULL) {
                 crm_trace("%s not found in %s", prop_name, restart_string);
                 xml_remove_prop(param_set, prop_name);
             }
             free(name);
         }
     }
 }
 
 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);
 }
 
 char *
 generate_series_filename(const char *directory, const char *series, int sequence, gboolean bzip)
 {
     int len = 40;
     char *filename = NULL;
     const char *ext = "raw";
 
     CRM_CHECK(directory != NULL, return NULL);
     CRM_CHECK(series != NULL, return NULL);
 
 #if !HAVE_BZLIB_H
     bzip = FALSE;
 #endif
 
     len += strlen(directory);
     len += strlen(series);
     filename = malloc(len);
     CRM_CHECK(filename != NULL, return NULL);
 
     if (bzip) {
         ext = "bz2";
     }
     sprintf(filename, "%s/%s-%d.%s", directory, series, sequence, ext);
 
     return filename;
 }
 
 int
 get_last_sequence(const char *directory, const char *series)
 {
     FILE *file_strm = NULL;
     int start = 0, length = 0, read_len = 0;
     char *series_file = NULL;
     char *buffer = NULL;
     int seq = 0;
     int len = 36;
 
     CRM_CHECK(directory != NULL, return 0);
     CRM_CHECK(series != NULL, return 0);
 
     len += strlen(directory);
     len += strlen(series);
     series_file = malloc(len);
     CRM_CHECK(series_file != NULL, return 0);
     sprintf(series_file, "%s/%s.last", directory, series);
 
     file_strm = fopen(series_file, "r");
     if (file_strm == NULL) {
         crm_debug("Series file %s does not exist", series_file);
         free(series_file);
         return 0;
     }
 
     /* see how big the file is */
     start = ftell(file_strm);
     fseek(file_strm, 0L, SEEK_END);
     length = ftell(file_strm);
     fseek(file_strm, 0L, start);
 
     CRM_ASSERT(length >= 0);
     CRM_ASSERT(start == ftell(file_strm));
 
     if (length <= 0) {
         crm_info("%s was not valid", series_file);
         free(buffer);
         buffer = NULL;
 
     } else {
         crm_trace("Reading %d bytes from file", length);
         buffer = calloc(1, (length + 1));
         read_len = fread(buffer, 1, length, file_strm);
         if (read_len != length) {
             crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len);
             free(buffer);
             buffer = NULL;
         }
     }
 
     seq = crm_parse_int(buffer, "0");
     fclose(file_strm);
 
     crm_trace("Found %d in %s", seq, series_file);
 
     free(series_file);
     free(buffer);
     return seq;
 }
 
 void
 write_last_sequence(const char *directory, const char *series, int sequence, int max)
 {
     int rc = 0;
     int len = 36;
     FILE *file_strm = NULL;
     char *series_file = NULL;
 
     CRM_CHECK(directory != NULL, return);
     CRM_CHECK(series != NULL, return);
 
     if (max == 0) {
         return;
     }
     if (max > 0 && sequence >= max) {
         sequence = 0;
     }
 
     len += strlen(directory);
     len += strlen(series);
     series_file = malloc(len);
 
     if(series_file) {
         sprintf(series_file, "%s/%s.last", directory, series);
         file_strm = fopen(series_file, "w");
     }
 
     if (file_strm != NULL) {
         rc = fprintf(file_strm, "%d", sequence);
         if (rc < 0) {
             crm_perror(LOG_ERR, "Cannot write to series file %s", series_file);
         }
 
     } else {
         crm_err("Cannot open series file %s for writing", series_file);
     }
 
     if (file_strm != NULL) {
         fflush(file_strm);
         fclose(file_strm);
     }
 
     crm_trace("Wrote %d to %s", sequence, series_file);
     free(series_file);
 }
 
 #define	LOCKSTRLEN	11
 
 int
 crm_pid_active(long pid)
 {
     if (pid <= 0) {
         return -1;
 
     } else if (kill(pid, 0) < 0 && errno == ESRCH) {
         return 0;
     }
 #ifndef HAVE_PROC_PID
     return 1;
 #else
     {
         int rc = 0;
         int running = 0;
         char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX];
 
         /* check to make sure pid hasn't been reused by another process */
         snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid);
 
         rc = readlink(proc_path, exe_path, PATH_MAX - 1);
         if (rc < 0) {
             crm_perror(LOG_ERR, "Could not read from %s", proc_path);
             goto bail;
         }
 
         exe_path[rc] = 0;
         snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", (long unsigned int)getpid());
         rc = readlink(proc_path, myexe_path, PATH_MAX - 1);
         if (rc < 0) {
             crm_perror(LOG_ERR, "Could not read from %s", proc_path);
             goto bail;
         }
 
         myexe_path[rc] = 0;
         if (strcmp(exe_path, myexe_path) == 0) {
             running = 1;
         }
     }
 
   bail:
     return running;
 #endif
 }
 
 static int
 crm_read_pidfile(const char *filename)
 {
     int fd;
     long pid = -1;
     char buf[LOCKSTRLEN + 1];
 
     if ((fd = open(filename, O_RDONLY)) < 0) {
         goto bail;
     }
 
     if (read(fd, buf, sizeof(buf)) < 1) {
         goto bail;
     }
 
     if (sscanf(buf, "%lu", &pid) > 0) {
         if (pid <= 0) {
             pid = -ESRCH;
         }
     }
 
   bail:
     if (fd >= 0) {
         close(fd);
     }
     return pid;
 }
 
 static int
 crm_pidfile_inuse(const char *filename, long mypid)
 {
     long pid = 0;
     struct stat sbuf;
     char buf[LOCKSTRLEN + 1];
     int rc = -ENOENT, fd = 0;
 
     if ((fd = open(filename, O_RDONLY)) >= 0) {
         if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) {
             sleep(2);           /* if someone was about to create one,
                                  * give'm a sec to do so
                                  */
         }
         if (read(fd, buf, sizeof(buf)) > 0) {
             if (sscanf(buf, "%lu", &pid) > 0) {
                 crm_trace("Got pid %lu from %s\n", pid, filename);
                 if (pid <= 1) {
                     /* Invalid pid */
                     rc = -ENOENT;
                     unlink(filename);
 
                 } else if (mypid && pid == mypid) {
                     /* In use by us */
                     rc = pcmk_ok;
 
                 } else if (crm_pid_active(pid) == FALSE) {
                     /* Contains a stale value */
                     unlink(filename);
                     rc = -ENOENT;
 
                 } else if (mypid && pid != mypid) {
                     /* locked by existing process - give up */
                     rc = -EEXIST;
                 }
             }
         }
         close(fd);
     }
     return rc;
 }
 
 static int
 crm_lock_pidfile(const char *filename)
 {
     long mypid = 0;
     int fd = 0, rc = 0;
     char buf[LOCKSTRLEN + 1];
 
     mypid = (unsigned long)getpid();
 
     rc = crm_pidfile_inuse(filename, 0);
     if (rc == -ENOENT) {
         /* exists but the process is not active */
 
     } else if (rc != pcmk_ok) {
         /* locked by existing process - give up */
         return rc;
     }
 
     if ((fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) {
         /* Hmmh, why did we fail? Anyway, nothing we can do about it */
         return -errno;
     }
 
     snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN - 1, mypid);
     rc = write(fd, buf, LOCKSTRLEN);
     close(fd);
 
     if (rc != LOCKSTRLEN) {
         crm_perror(LOG_ERR, "Incomplete write to %s", filename);
         return -errno;
     }
 
     return crm_pidfile_inuse(filename, mypid);
 }
 
 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);
     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(rc);
     }
 
     pid = fork();
     if (pid < 0) {
         fprintf(stderr, "%s: could not start daemon\n", name);
         crm_perror(LOG_ERR, "fork");
         crm_exit(EINVAL);
 
     } else if (pid > 0) {
         crm_exit(pcmk_ok);
     }
 
     rc = crm_lock_pidfile(pidfile);
     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(rc);
     }
 
     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 */
 }
 
 gboolean
 crm_is_writable(const char *dir, const char *file,
                 const char *user, const char *group, gboolean need_both)
 {
     int s_res = -1;
     struct stat buf;
     char *full_file = NULL;
     const char *target = NULL;
 
     gboolean pass = TRUE;
     gboolean readwritable = FALSE;
 
     CRM_ASSERT(dir != NULL);
     if (file != NULL) {
         full_file = crm_concat(dir, file, '/');
         target = full_file;
         s_res = stat(full_file, &buf);
         if (s_res == 0 && S_ISREG(buf.st_mode) == FALSE) {
             crm_err("%s must be a regular file", target);
             pass = FALSE;
             goto out;
         }
     }
 
     if (s_res != 0) {
         target = dir;
         s_res = stat(dir, &buf);
         if (s_res != 0) {
             crm_err("%s must exist and be a directory", dir);
             pass = FALSE;
             goto out;
 
         } else if (S_ISDIR(buf.st_mode) == FALSE) {
             crm_err("%s must be a directory", dir);
             pass = FALSE;
         }
     }
 
     if (user) {
         struct passwd *sys_user = NULL;
 
         sys_user = getpwnam(user);
         readwritable = (sys_user != NULL
                         && buf.st_uid == sys_user->pw_uid && (buf.st_mode & (S_IRUSR | S_IWUSR)));
         if (readwritable == FALSE) {
             crm_err("%s must be owned and r/w by user %s", target, user);
             if (need_both) {
                 pass = FALSE;
             }
         }
     }
 
     if (group) {
         struct group *sys_grp = getgrnam(group);
 
         readwritable = (sys_grp != NULL
                         && buf.st_gid == sys_grp->gr_gid && (buf.st_mode & (S_IRGRP | S_IWGRP)));
         if (readwritable == FALSE) {
             if (need_both || user == NULL) {
                 pass = FALSE;
                 crm_err("%s must be owned and r/w by group %s", target, group);
             } else {
                 crm_warn("%s should be owned and r/w by group %s", target, group);
             }
         }
     }
 
   out:
     free(full_file);
     return pass;
 }
 
 char *
 crm_strip_trailing_newline(char *str)
 {
     int len;
 
     if (str == NULL) {
         return str;
     }
 
     for (len = strlen(str) - 1; len >= 0 && str[len] == '\n'; len--) {
         str[len] = '\0';
     }
 
     return str;
 }
 
 gboolean
 crm_str_eq(const char *a, const char *b, gboolean use_case)
 {
     if (use_case) {
         return g_strcmp0(a, b) == 0;
 
         /* TODO - Figure out which calls, if any, really need to be case independant */
     } else if (a == b) {
         return TRUE;
 
     } else if (a == NULL || b == NULL) {
         /* shouldn't be comparing NULLs */
         return FALSE;
 
     } else if (strcasecmp(a, b) == 0) {
         return TRUE;
     }
     return FALSE;
 }
 
 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(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(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(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(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);
     }
 
     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('?', 1);
                 break;
             case '?':
                 crm_help('?', *index ? 0 : 1);
                 break;
         }
         return flag;
     }
 #endif
 
     if (crm_short_options) {
         return getopt(argc, argv, crm_short_options);
     }
 
     return -1;
 }
 
 void
 crm_help(char cmd, int exit_code)
 {
     int i = 0;
     FILE *stream = (exit_code ? stderr : stdout);
 
     if (cmd == 'v' || cmd == '$') {
         fprintf(stream, "Pacemaker %s\n", VERSION);
         fprintf(stream, "Written by Andrew Beekhof\n");
         goto out;
     }
 
     if (cmd == '!') {
         fprintf(stream, "Pacemaker %s (Build: %s): %s\n", 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:
     if (exit_code >= 0) {
         crm_exit(exit_code);
     }
 }
 
 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 cib servers: exiting and inhibiting respawn.");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
         crm_exit(DAEMON_RESPAWN_STOP);
     }
 }
 
 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 attrd servers: exiting and inhibiting respawn.");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
         crm_exit(DAEMON_RESPAWN_STOP);
     }
 }
 
 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);
 
     if (*ipcs == NULL) {
         crm_err("Failed to create stonith-ng servers: exiting and inhibiting respawn.");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
         crm_exit(DAEMON_RESPAWN_STOP);
     }
 }
 
 int
 attrd_update_delegate(crm_ipc_t * ipc, char command, const char *host, const char *name,
                       const char *value, const char *section, const char *set, const char *dampen,
                       const char *user_name, gboolean is_remote)
 {
     int rc = -ENOTCONN;
     int max = 5;
     enum crm_ipc_flags flags = crm_ipc_flags_none;
     xmlNode *update = create_xml_node(NULL, __FUNCTION__);
 
     static gboolean connected = TRUE;
     static crm_ipc_t *local_ipc = NULL;
 
     if (ipc == NULL && local_ipc == NULL) {
         local_ipc = crm_ipc_new(T_ATTRD, 0);
         flags |= crm_ipc_client_response;
         connected = FALSE;
     }
 
     if (ipc == NULL) {
         ipc = local_ipc;
     }
 
     /* remap common aliases */
     if (safe_str_eq(section, "reboot")) {
         section = XML_CIB_TAG_STATUS;
 
     } else if (safe_str_eq(section, "forever")) {
         section = XML_CIB_TAG_NODES;
     }
 
     crm_xml_add(update, F_TYPE, T_ATTRD);
     crm_xml_add(update, F_ORIG, crm_system_name);
 
     if (name == NULL && command == 'U') {
         command = 'R';
     }
 
     switch (command) {
         case 'D':
         case 'U':
         case 'v':
             crm_xml_add(update, F_ATTRD_TASK, "update");
             crm_xml_add(update, F_ATTRD_ATTRIBUTE, name);
             break;
         case 'R':
             crm_xml_add(update, F_ATTRD_TASK, "refresh");
             break;
         case 'q':
             crm_xml_add(update, F_ATTRD_TASK, "query");
             break;
         case 'C':
             crm_xml_add(update, F_ATTRD_TASK, "peer-remove");
             break;
     }
 
     crm_xml_add(update, F_ATTRD_VALUE, value);
     crm_xml_add(update, F_ATTRD_DAMPEN, dampen);
     crm_xml_add(update, F_ATTRD_SECTION, section);
     crm_xml_add(update, F_ATTRD_HOST, host);
     crm_xml_add(update, F_ATTRD_SET, set);
     crm_xml_add_int(update, F_ATTRD_IS_REMOTE, is_remote);
 #if ENABLE_ACL
     if (user_name) {
         crm_xml_add(update, F_ATTRD_USER, user_name);
     }
 #endif
 
     while (max > 0) {
         if (connected == FALSE) {
             crm_info("Connecting to cluster... %d retries remaining", max);
             connected = crm_ipc_connect(ipc);
         }
 
         if (connected) {
             rc = crm_ipc_send(ipc, update, flags, 0, NULL);
         }
 
         if (ipc != local_ipc) {
             break;
 
         } else if (rc > 0) {
             break;
 
         } else if (rc == -EAGAIN || rc == -EALREADY) {
             sleep(5 - max);
             max--;
 
         } else {
             crm_ipc_close(ipc);
             connected = FALSE;
             sleep(5 - max);
             max--;
         }
     }
 
     free_xml(update);
     if (rc > 0) {
         crm_debug("Sent update: %s=%s for %s", name, value, host ? host : "localhost");
         rc = pcmk_ok;
 
     } else {
         crm_debug("Could not send update %s=%s for %s: %s (%d)", name, value,
                   host ? host : "localhost", pcmk_strerror(rc), rc);
     }
     return rc;
 }
 
 #define FAKE_TE_ID	"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
 static void
 append_digest(lrmd_event_data_t * op, xmlNode * update, const char *version, const char *magic,
               int level)
 {
     /* this will enable us to later determine that the
      *   resource's parameters have changed and we should force
      *   a restart
      */
     char *digest = NULL;
     xmlNode *args_xml = NULL;
 
     if (op->params == NULL) {
         return;
     }
 
     args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
     g_hash_table_foreach(op->params, hash2field, args_xml);
     filter_action_parameters(args_xml, version);
     digest = calculate_operation_digest(args_xml, version);
 
 #if 0
     if (level < get_crm_log_level()
         && op->interval == 0 && crm_str_eq(op->op_type, CRMD_ACTION_START, TRUE)) {
         char *digest_source = dump_xml_unformatted(args_xml);
 
         do_crm_log(level, "Calculated digest %s for %s (%s). Source: %s\n",
                    digest, ID(update), magic, digest_source);
         free(digest_source);
     }
 #endif
     crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
 
     free_xml(args_xml);
     free(digest);
 }
 
 int
 rsc_op_expected_rc(lrmd_event_data_t * op)
 {
     int rc = 0;
 
     if (op && op->user_data) {
         int dummy = 0;
         char *uuid = NULL;
 
         decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &rc);
         free(uuid);
     }
     return rc;
 }
 
 gboolean
 did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
 {
     switch (op->op_status) {
         case PCMK_LRM_OP_CANCELLED:
         case PCMK_LRM_OP_PENDING:
             return FALSE;
             break;
 
         case PCMK_LRM_OP_NOTSUPPORTED:
         case PCMK_LRM_OP_TIMEOUT:
         case PCMK_LRM_OP_ERROR:
             return TRUE;
             break;
 
         default:
             if (target_rc != op->rc) {
                 return TRUE;
             }
     }
 
     return FALSE;
 }
 
 xmlNode *
 create_operation_update(xmlNode * parent, lrmd_event_data_t * op, const char *caller_version,
                         int target_rc, const char *origin, int level)
 {
     char *key = NULL;
     char *magic = NULL;
     char *op_id = NULL;
     char *local_user_data = NULL;
 
     xmlNode *xml_op = NULL;
     const char *task = NULL;
     gboolean dc_munges_migrate_ops = (compare_version(caller_version, "3.0.3") < 0);
     gboolean dc_needs_unique_ops = (compare_version(caller_version, "3.0.6") < 0);
 
     CRM_CHECK(op != NULL, return NULL);
     do_crm_log(level, "%s: Updating resource %s after %s op %s (interval=%d)",
                origin, op->rsc_id, op->op_type, services_lrm_status_str(op->op_status),
                op->interval);
 
-    if (op->op_status == PCMK_LRM_OP_CANCELLED) {
-        crm_trace("Ignoring cancelled op");
-        return NULL;
-    }
-
     crm_trace("DC version: %s", caller_version);
 
     task = op->op_type;
     /* remap the task name under various scenarios
      * this makes life easier for the PE when its trying determin the current state
      */
     if (crm_str_eq(task, "reload", TRUE)) {
         if (op->op_status == PCMK_LRM_OP_DONE) {
             task = CRMD_ACTION_START;
         } else {
             task = CRMD_ACTION_STATUS;
         }
 
     } else if (dc_munges_migrate_ops && crm_str_eq(task, CRMD_ACTION_MIGRATE, TRUE)) {
         /* if the migrate_from fails it will have enough info to do the right thing */
         if (op->op_status == PCMK_LRM_OP_DONE) {
             task = CRMD_ACTION_STOP;
         } else {
             task = CRMD_ACTION_STATUS;
         }
 
     } else if (dc_munges_migrate_ops
                && op->op_status == PCMK_LRM_OP_DONE
                && crm_str_eq(task, CRMD_ACTION_MIGRATED, TRUE)) {
         task = CRMD_ACTION_START;
     }
 
     key = generate_op_key(op->rsc_id, task, op->interval);
     if (dc_needs_unique_ops && op->interval > 0) {
         op_id = strdup(key);
 
     } else if (crm_str_eq(task, CRMD_ACTION_NOTIFY, TRUE)) {
         const char *n_type = crm_meta_value(op->params, "notify_type");
         const char *n_task = crm_meta_value(op->params, "notify_operation");
 
         CRM_LOG_ASSERT(n_type != NULL);
         CRM_LOG_ASSERT(n_task != NULL);
         op_id = generate_notify_key(op->rsc_id, n_type, n_task);
 
         /* these are not yet allowed to fail */
         op->op_status = PCMK_LRM_OP_DONE;
         op->rc = 0;
 
     } else if (did_rsc_op_fail(op, target_rc)) {
         op_id = generate_op_key(op->rsc_id, "last_failure", 0);
 
     } else if (op->interval > 0) {
         op_id = strdup(key);
 
     } else {
         op_id = generate_op_key(op->rsc_id, "last", 0);
     }
 
     xml_op = find_entity(parent, XML_LRM_TAG_RSC_OP, op_id);
     if (xml_op == NULL) {
         xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
     }
 
     if (op->user_data == NULL) {
         crm_debug("Generating fake transition key for:"
                   " %s_%s_%d %d from %s",
                   op->rsc_id, op->op_type, op->interval, op->call_id, origin);
         local_user_data = generate_transition_key(-1, op->call_id, target_rc, FAKE_TE_ID);
         op->user_data = local_user_data;
     }
 
     magic = generate_transition_magic(op->user_data, op->op_status, op->rc);
 
     crm_xml_add(xml_op, XML_ATTR_ID, op_id);
     crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
     crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
     crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
     crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
     crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
     crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
 
     crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
     crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
     crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
     crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval);
 
     if (compare_version("2.1", caller_version) <= 0) {
         if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
             crm_trace("Timing data (%s_%s_%d): last=%lu change=%lu exec=%lu queue=%lu",
                       op->rsc_id, op->op_type, op->interval,
                       op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
 
             if (op->interval == 0) {
                 /* The values are the same for non-recurring ops */
                 crm_xml_add_int(xml_op, XML_RSC_OP_LAST_RUN, op->t_run);
                 crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
 
             } else if(op->t_rcchange) {
                 /* last-run is not accurate for recurring ops */
                 crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_rcchange);
 
             } else {
                 /* ...but is better than nothing otherwise */
                 crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
             }
 
             crm_xml_add_int(xml_op, XML_RSC_OP_T_EXEC, op->exec_time);
             crm_xml_add_int(xml_op, XML_RSC_OP_T_QUEUE, op->queue_time);
         }
     }
 
     if (crm_str_eq(op->op_type, CRMD_ACTION_MIGRATE, TRUE)
         || crm_str_eq(op->op_type, CRMD_ACTION_MIGRATED, TRUE)) {
         /*
          * Record migrate_source and migrate_target always for migrate ops.
          */
         const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
 
         crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
 
         name = XML_LRM_ATTR_MIGRATE_TARGET;
         crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
     }
 
     append_digest(op, xml_op, caller_version, magic, LOG_DEBUG);
 
     if (local_user_data) {
         free(local_user_data);
         op->user_data = NULL;
     }
     free(magic);
     free(op_id);
     free(key);
     return xml_op;
 }
 
 bool
 pcmk_acl_required(const char *user) 
 {
 #if ENABLE_ACL
     if(user == NULL || strlen(user) == 0) {
         crm_trace("no user set");
         return FALSE;
 
     } else if (strcmp(user, CRM_DAEMON_USER) == 0) {
         return FALSE;
 
     } else if (strcmp(user, "root") == 0) {
         return FALSE;
     }
     crm_trace("acls required for %s", user);
     return TRUE;
 #else
     crm_trace("acls not supported");
     return FALSE;
 #endif
 }
 
 #if ENABLE_ACL
 char *
 uid2username(uid_t uid)
 {
     struct passwd *pwent = getpwuid(uid);
 
     if (pwent == NULL) {
         crm_perror(LOG_ERR, "Cannot get password entry of uid: %d", uid);
         return NULL;
 
     } else {
         return strdup(pwent->pw_name);
     }
 }
 
 void
 determine_request_user(const char *user, xmlNode * request, const char *field)
 {
     /* Get our internal validation out of the way first */
     CRM_CHECK(user != NULL && request != NULL && field != NULL, return);
 
     /* If our peer is a privileged user, we might be doing something on behalf of someone else */
     if (is_privileged(user) == FALSE) {
         /* We're not a privileged user, set or overwrite any existing value for $field */
         crm_xml_replace(request, field, user);
 
     } else if (crm_element_value(request, field) == NULL) {
         /* Even if we're privileged, make sure there is always a value set */
         crm_xml_replace(request, field, user);
 
 /*  } else { Legal delegation */
     }
 
     crm_trace("Processing msg as user '%s'", crm_element_value(request, field));
 }
 #endif
 
 /*
  * This re-implements g_str_hash as it was prior to glib2-2.28:
  *
  *   http://git.gnome.org/browse/glib/commit/?id=354d655ba8a54b754cb5a3efb42767327775696c
  *
  * Note that the new g_str_hash is presumably a *better* hash (it's actually
  * a correct implementation of DJB's hash), but we need to preserve existing
  * behaviour, because the hash key ultimately determines the "sort" order
  * when iterating through GHashTables, which affects allocation of scores to
  * clone instances when iterating through rsc->allowed_nodes.  It (somehow)
  * also appears to have some minor impact on the ordering of a few
  * pseudo_event IDs in the transition graph.
  */
 guint
 g_str_hash_traditional(gconstpointer v)
 {
     const signed char *p;
     guint32 h = 0;
 
     for (p = v; *p != '\0'; p++)
         h = (h << 5) - h + *p;
 
     return h;
 }
 
 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(DAEMON_RESPAWN_STOP);
         }
         return NULL;
     }
 
     a_function = dlsym(*handle, fn);
     if ((error = dlerror()) != NULL) {
         crm_err("%sCould not find %s in %s: %s", fatal ? "Fatal: " : "", fn, lib, error);
         if (fatal) {
             crm_exit(DAEMON_RESPAWN_STOP);
         }
     }
 
     return a_function;
 }
 
 char *
 add_list_element(char *list, const char *value)
 {
     int len = 0;
     int last = 0;
 
     if (value == NULL) {
         return list;
     }
     if (list) {
         last = strlen(list);
     }
     len = last + 2;             /* +1 space, +1 EOS */
     len += strlen(value);
     list = realloc(list, len);
     sprintf(list + last, " %s", value);
     return list;
 }
 
 void *
 convert_const_pointer(const void *ptr)
 {
     /* Worst function ever */
     return (void *)ptr;
 }
 
 #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;
 }
 
 #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) {
         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;
 }
 
 #include <time.h>
 #include <bzlib.h>
 
 bool
 crm_compress_string(const char *data, int length, int max, char **result, unsigned int *result_len)
 {
     int rc;
     char *compressed = NULL;
     char *uncompressed = strdup(data);
     struct timespec after_t;
     struct timespec before_t;
 
     if(max == 0) {
         max = (length * 1.1) + 600; /* recomended size */
     }
 
 #ifdef CLOCK_MONOTONIC
     clock_gettime(CLOCK_MONOTONIC, &before_t);
 #endif
 
     /* coverity[returned_null] Ignore */
     compressed = malloc(max);
 
     *result_len = max;
     rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length, CRM_BZ2_BLOCKS, 0,
                                   CRM_BZ2_WORK);
 
     free(uncompressed);
 
     if (rc != BZ_OK) {
         crm_err("Compression of %d bytes failed: %s (%d)", length, bz2_strerror(rc), rc);
         free(compressed);
         return FALSE;
     }
 
 #ifdef CLOCK_MONOTONIC
     clock_gettime(CLOCK_MONOTONIC, &after_t);
 
     crm_info("Compressed %d bytes into %d (ratio %d:1) in %dms",
              length, *result_len, length / (*result_len),
              (after_t.tv_sec - before_t.tv_sec) * 1000 + (after_t.tv_nsec -
                                                           before_t.tv_nsec) / 1000000);
 #else
     crm_info("Compressed %d bytes into %d (ratio %d:1)",
              length, *result_len, length / (*result_len));
 #endif
 
     *result = compressed;
     return TRUE;
 }
diff --git a/lib/services/services.c b/lib/services/services.c
index 270341944c..b9b30f56f9 100644
--- a/lib/services/services.c
+++ b/lib/services/services.c
@@ -1,564 +1,641 @@
 /*
  * Copyright (C) 2010 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdio.h>
 
 #include <errno.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <fcntl.h>
 
 #include <crm/crm.h>
 #include <crm/common/mainloop.h>
 #include <crm/services.h>
 #include <crm/msg_xml.h>
 #include "services_private.h"
 
 #if SUPPORT_UPSTART
 #  include <upstart.h>
 #endif
 
 #if SUPPORT_SYSTEMD
 #  include <systemd.h>
 #endif
 
 /* TODO: Develop a rollover strategy */
 
 static int operations = 0;
 GHashTable *recurring_actions = NULL;
 
 svc_action_t *
 services_action_create(const char *name, const char *action, int interval, int timeout)
 {
     return resources_action_create(name, "lsb", NULL, name, action, interval, timeout, NULL);
 }
 
 const char *
 resources_find_service_class(const char *agent)
 {
     /* Priority is:
      * - lsb
      * - systemd
      * - upstart
      */
     int rc = 0;
     struct stat st;
     char *path = NULL;
 
 #ifdef LSB_ROOT_DIR
     rc = asprintf(&path, "%s/%s", LSB_ROOT_DIR, agent);
     if (rc > 0 && stat(path, &st) == 0) {
         free(path);
         return "lsb";
     }
     free(path);
 #endif
 
 #if SUPPORT_SYSTEMD
     if (systemd_unit_exists(agent)) {
         return "systemd";
     }
 #endif
 
 #if SUPPORT_UPSTART
     if (upstart_job_exists(agent)) {
         return "upstart";
     }
 #endif
     return NULL;
 }
 
 
 svc_action_t *
 resources_action_create(const char *name, const char *standard, const char *provider,
                         const char *agent, const char *action, int interval, int timeout,
                         GHashTable * params)
 {
     svc_action_t *op = NULL;
 
     /*
      * Do some up front sanity checks before we go off and
      * build the svc_action_t instance.
      */
 
     if (crm_strlen_zero(name)) {
         crm_err("A service or resource action must have a name.");
         goto return_error;
     }
 
     if (crm_strlen_zero(standard)) {
         crm_err("A service action must have a valid standard.");
         goto return_error;
     }
 
     if (!strcasecmp(standard, "ocf") && crm_strlen_zero(provider)) {
         crm_err("An OCF resource action must have a provider.");
         goto return_error;
     }
 
     if (crm_strlen_zero(agent)) {
         crm_err("A service or resource action must have an agent.");
         goto return_error;
     }
 
     if (crm_strlen_zero(action)) {
         crm_err("A service or resource action must specify an action.");
         goto return_error;
     }
 
     if (safe_str_eq(action, "monitor")
         && (safe_str_eq(standard, "lsb") || safe_str_eq(standard, "service"))) {
         action = "status";
     }
 
     /*
      * Sanity checks passed, proceed!
      */
 
     op = calloc(1, sizeof(svc_action_t));
     op->opaque = calloc(1, sizeof(svc_action_private_t));
     op->rsc = strdup(name);
     op->action = strdup(action);
     op->interval = interval;
     op->timeout = timeout;
     op->standard = strdup(standard);
     op->agent = strdup(agent);
     op->sequence = ++operations;
     if (asprintf(&op->id, "%s_%s_%d", name, action, interval) == -1) {
         goto return_error;
     }
 
     if (strcasecmp(op->standard, "service") == 0) {
         const char *expanded = resources_find_service_class(op->agent);
 
         if(expanded) {
             crm_debug("Found a %s agent for %s/%s", expanded, op->rsc, op->agent);
             free(op->standard);
             op->standard = strdup(expanded);
 
         } else {
             crm_info("Cannot determine the standard for %s (%s)", op->rsc, op->agent);
             free(op->standard);
             op->standard = strdup("lsb");
         }
     }
 
     if (strcasecmp(op->standard, "ocf") == 0) {
         op->provider = strdup(provider);
         op->params = params;
         params = NULL;
 
         if (asprintf(&op->opaque->exec, "%s/resource.d/%s/%s", OCF_ROOT_DIR, provider, agent) == -1) {
             crm_err("Internal error: cannot create agent path");
             goto return_error;
         }
         op->opaque->args[0] = strdup(op->opaque->exec);
         op->opaque->args[1] = strdup(action);
 
     } else if (strcasecmp(op->standard, "lsb") == 0) {
         if (op->agent[0] == '/') {
             /* if given an absolute path, use that instead
              * of tacking on the LSB_ROOT_DIR path to the front */
             op->opaque->exec = strdup(op->agent);
         } else if (asprintf(&op->opaque->exec, "%s/%s", LSB_ROOT_DIR, op->agent) == -1) {
             crm_err("Internal error: cannot create agent path");
             goto return_error;
         }
         op->opaque->args[0] = strdup(op->opaque->exec);
         op->opaque->args[1] = strdup(op->action);
         op->opaque->args[2] = NULL;
 
 #if SUPPORT_SYSTEMD
     } else if (strcasecmp(op->standard, "systemd") == 0) {
         op->opaque->exec = strdup("systemd-dbus");
 #endif
 #if SUPPORT_UPSTART
     } else if (strcasecmp(op->standard, "upstart") == 0) {
         op->opaque->exec = strdup("upstart-dbus");
 #endif
     } else if (strcasecmp(op->standard, "service") == 0) {
         op->opaque->exec = strdup(SERVICE_SCRIPT);
         op->opaque->args[0] = strdup(SERVICE_SCRIPT);
         op->opaque->args[1] = strdup(agent);
         op->opaque->args[2] = strdup(action);
 
 #if SUPPORT_NAGIOS
     } else if (strcasecmp(op->standard, "nagios") == 0) {
         int index = 0;
 
         if (op->agent[0] == '/') {
             /* if given an absolute path, use that instead
              * of tacking on the NAGIOS_PLUGIN_DIR path to the front */
             op->opaque->exec = strdup(op->agent);
 
         } else if (asprintf(&op->opaque->exec, "%s/%s", NAGIOS_PLUGIN_DIR, op->agent) == -1) {
             crm_err("Internal error: cannot create agent path");
             goto return_error;
         }
 
         op->opaque->args[0] = strdup(op->opaque->exec);
         index = 1;
 
         if (safe_str_eq(op->action, "monitor") && op->interval == 0) {
             /* Invoke --version for a nagios probe */
             op->opaque->args[index] = strdup("--version");
             index++;
 
         } else if (params) {
             GHashTableIter iter;
             char *key = NULL;
             char *value = NULL;
             static int args_size = sizeof(op->opaque->args) / sizeof(char *);
 
             g_hash_table_iter_init(&iter, params);
 
             while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value) &&
                    index <= args_size - 3) {
                 int len = 3;
                 char *long_opt = NULL;
 
                 if (safe_str_eq(key, XML_ATTR_CRM_VERSION) || strstr(key, CRM_META "_")) {
                     continue;
                 }
 
                 len += strlen(key);
                 long_opt = calloc(1, len);
                 sprintf(long_opt, "--%s", key);
                 long_opt[len - 1] = 0;
 
                 op->opaque->args[index] = long_opt;
                 op->opaque->args[index + 1] = strdup(value);
                 index += 2;
             }
         }
         op->opaque->args[index] = NULL;
 #endif
 
     } else {
         crm_err("Unknown resource standard: %s", op->standard);
         services_action_free(op);
         op = NULL;
     }
 
     if(params) {
         g_hash_table_destroy(params);
     }
     return op;
 
   return_error:
     if(params) {
         g_hash_table_destroy(params);
     }
     services_action_free(op);
 
     return NULL;
 }
 
 svc_action_t *
 services_action_create_generic(const char *exec, const char *args[])
 {
     svc_action_t *op;
     unsigned int cur_arg;
 
     op = calloc(1, sizeof(*op));
     op->opaque = calloc(1, sizeof(svc_action_private_t));
 
     op->opaque->exec = strdup(exec);
     op->opaque->args[0] = strdup(exec);
 
     for (cur_arg = 1; args && args[cur_arg - 1]; cur_arg++) {
         op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
 
         if (cur_arg == DIMOF(op->opaque->args) - 1) {
             crm_err("svc_action_t args list not long enough for '%s' execution request.", exec);
             break;
         }
     }
 
     return op;
 }
 
 void
 services_action_free(svc_action_t * op)
 {
     unsigned int i;
 
     if (op == NULL) {
         return;
     }
 
+    if (op->opaque->repeat_timer) {
+        g_source_remove(op->opaque->repeat_timer);
+    }
     if (op->opaque->stderr_gsource) {
         mainloop_del_fd(op->opaque->stderr_gsource);
         op->opaque->stderr_gsource = NULL;
     }
 
     if (op->opaque->stdout_gsource) {
         mainloop_del_fd(op->opaque->stdout_gsource);
         op->opaque->stdout_gsource = NULL;
     }
 
     free(op->id);
     free(op->opaque->exec);
 
     for (i = 0; i < DIMOF(op->opaque->args); i++) {
         free(op->opaque->args[i]);
     }
 
     free(op->opaque);
     free(op->rsc);
     free(op->action);
 
     free(op->standard);
     free(op->agent);
     free(op->provider);
 
     free(op->stdout_data);
     free(op->stderr_data);
 
     if (op->params) {
         g_hash_table_destroy(op->params);
         op->params = NULL;
     }
 
     free(op);
 }
 
 gboolean
 cancel_recurring_action(svc_action_t * op)
 {
     crm_info("Cancelling operation %s", op->id);
 
     if (recurring_actions) {
         g_hash_table_remove(recurring_actions, op->id);
     }
 
     if (op->opaque->repeat_timer) {
         g_source_remove(op->opaque->repeat_timer);
     }
 
     return TRUE;
 }
 
 gboolean
 services_action_cancel(const char *name, const char *action, int interval)
 {
     svc_action_t *op = NULL;
     char id[512];
 
     snprintf(id, sizeof(id), "%s_%s_%d", name, action, interval);
 
     if (!(op = g_hash_table_lookup(recurring_actions, id))) {
         return FALSE;
     }
 
     if (op->pid == 0) {
         cancel_recurring_action(op);
         op->status = PCMK_LRM_OP_CANCELLED;
         if (op->opaque->callback) {
             op->opaque->callback(op);
         }
         services_action_free(op);
     } else {
-        crm_info("Cancelling op: %s will occur once operation completes", id);
+        int rc;
+        crm_info("Cancelling in-flight op: performing early termination of %s", id);
         op->cancel = 1;
+        rc = mainloop_child_kill(op->pid);
+        if (rc != 0 ) {
+            /* even though the early termination failed,
+             * the op will be marked as cancelled once it completes. */
+            crm_err("Termination of %s failed", id);
+        }
     }
 
     return TRUE;
 }
 
 gboolean
-services_action_async(svc_action_t * op, void (*action_callback) (svc_action_t *))
+services_action_kick(const char *name, const char *action, int interval /* ms */)
 {
-    if (action_callback) {
-        op->opaque->callback = action_callback;
+    svc_action_t * op = NULL;
+    char *id = NULL;
+
+    if (asprintf(&id, "%s_%s_%d", name, action, interval) == -1) {
+        return FALSE;
     }
 
+    op = g_hash_table_lookup(recurring_actions, id);
+    free(id);
+
+    if (op == NULL) {
+        return FALSE;
+    }
+
+    if (op->pid) {
+        return TRUE;
+    } else {
+        if (op->opaque->repeat_timer) {
+            g_source_remove(op->opaque->repeat_timer);
+        }
+        recurring_action_timer(op);
+        return TRUE;
+    }
+
+}
+
+/* add new recurring operation, check for duplicates. 
+ * - if duplicate found, return TRUE, immediately reschedule op.
+ * - if no dup, return FALSE, inserve into recurring op list.*/
+static gboolean
+handle_duplicate_recurring(svc_action_t * op, void (*action_callback) (svc_action_t *))
+{
+    svc_action_t * dup = NULL;
+
     if (recurring_actions == NULL) {
         recurring_actions = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+        return FALSE;
+    }
+
+    /* check for duplicates */
+    dup = g_hash_table_lookup(recurring_actions, op->id);
+
+    if (dup && (dup != op)) {
+        /* update user data */
+        if (op->opaque->callback) {
+            dup->opaque->callback = op->opaque->callback;
+            dup->cb_data = op->cb_data;
+            op->cb_data = NULL;
+        }
+        /* immediately execute the next interval */
+        if (dup->pid != 0) {
+            if (op->opaque->repeat_timer) {
+                g_source_remove(op->opaque->repeat_timer);
+            }
+            recurring_action_timer(dup);
+        }
+        /* free the dup.  */
+        services_action_free(op);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+gboolean
+services_action_async(svc_action_t * op, void (*action_callback) (svc_action_t *))
+{
+    if (action_callback) {
+        op->opaque->callback = action_callback;
     }
 
     if (op->interval > 0) {
+        if (handle_duplicate_recurring(op, action_callback) == TRUE) {
+            /* entry rescheduled, dup freed */
+            return TRUE;
+        }
         g_hash_table_replace(recurring_actions, op->id, op);
     }
     if (op->standard && strcasecmp(op->standard, "upstart") == 0) {
 #if SUPPORT_UPSTART
         return upstart_job_exec(op, FALSE);
 #endif
     }
     if (op->standard && strcasecmp(op->standard, "systemd") == 0) {
 #if SUPPORT_SYSTEMD
         return systemd_unit_exec(op, FALSE);
 #endif
     }
     return services_os_action_execute(op, FALSE);
 }
 
 gboolean
 services_action_sync(svc_action_t * op)
 {
     gboolean rc = TRUE;
 
     if (op == NULL) {
         crm_trace("No operation to execute");
         return FALSE;
 
     } else if (op->standard && strcasecmp(op->standard, "upstart") == 0) {
 #if SUPPORT_UPSTART
         rc = upstart_job_exec(op, TRUE);
 #endif
     } else if (op->standard && strcasecmp(op->standard, "systemd") == 0) {
 #if SUPPORT_SYSTEMD
         rc = systemd_unit_exec(op, TRUE);
 #endif
     } else {
         rc = services_os_action_execute(op, TRUE);
     }
     crm_trace(" > %s_%s_%d: %s = %d", op->rsc, op->action, op->interval, op->opaque->exec, op->rc);
     if (op->stdout_data) {
         crm_trace(" >  stdout: %s", op->stdout_data);
     }
     if (op->stderr_data) {
         crm_trace(" >  stderr: %s", op->stderr_data);
     }
     return rc;
 }
 
 GList *
 get_directory_list(const char *root, gboolean files, gboolean executable)
 {
     return services_os_get_directory_list(root, files, executable);
 }
 
 GList *
 services_list(void)
 {
     return resources_list_agents("lsb", NULL);
 }
 
 GList *
 resources_list_standards(void)
 {
     GList *standards = NULL;
     GList *agents = NULL;
 
     standards = g_list_append(standards, strdup("ocf"));
     standards = g_list_append(standards, strdup("lsb"));
     standards = g_list_append(standards, strdup("service"));
 
 #if SUPPORT_SYSTEMD
     agents = systemd_unit_listall();
 #else
     agents = NULL;
 #endif
 
     if (agents) {
         standards = g_list_append(standards, strdup("systemd"));
         g_list_free_full(agents, free);
     }
 #if SUPPORT_UPSTART
     agents = upstart_job_listall();
 #else
     agents = NULL;
 #endif
 
     if (agents) {
         standards = g_list_append(standards, strdup("upstart"));
         g_list_free_full(agents, free);
     }
 #if SUPPORT_NAGIOS
     agents = resources_os_list_nagios_agents();
     if (agents) {
         standards = g_list_append(standards, strdup("nagios"));
         g_list_free_full(agents, free);
     }
 #endif
 
     return standards;
 }
 
 GList *
 resources_list_providers(const char *standard)
 {
     if (strcasecmp(standard, "ocf") == 0) {
         return resources_os_list_ocf_providers();
     }
 
     return NULL;
 }
 
 GList *
 resources_list_agents(const char *standard, const char *provider)
 {
     if (standard == NULL || strcasecmp(standard, "service") == 0) {
         GList *tmp1;
         GList *tmp2;
         GList *result = resources_os_list_lsb_agents();
 
         if (standard == NULL) {
             tmp1 = result;
             tmp2 = resources_os_list_ocf_agents(NULL);
             if (tmp2) {
                 result = g_list_concat(tmp1, tmp2);
             }
         }
 #if SUPPORT_SYSTEMD
         tmp1 = result;
         tmp2 = systemd_unit_listall();
         if (tmp2) {
             result = g_list_concat(tmp1, tmp2);
         }
 #endif
 
 #if SUPPORT_UPSTART
         tmp1 = result;
         tmp2 = upstart_job_listall();
         if (tmp2) {
             result = g_list_concat(tmp1, tmp2);
         }
 #endif
 
         return result;
 
     } else if (strcasecmp(standard, "ocf") == 0) {
         return resources_os_list_ocf_agents(provider);
     } else if (strcasecmp(standard, "lsb") == 0) {
         return resources_os_list_lsb_agents();
 #if SUPPORT_SYSTEMD
     } else if (strcasecmp(standard, "systemd") == 0) {
         return systemd_unit_listall();
 #endif
 #if SUPPORT_UPSTART
     } else if (strcasecmp(standard, "upstart") == 0) {
         return upstart_job_listall();
 #endif
 #if SUPPORT_NAGIOS
     } else if (strcasecmp(standard, "nagios") == 0) {
         return resources_os_list_nagios_agents();
 #endif
     }
 
     return NULL;
 }
diff --git a/lrmd/lrmd.c b/lrmd/lrmd.c
index d8215f094b..75a96782e8 100644
--- a/lrmd/lrmd.c
+++ b/lrmd/lrmd.c
@@ -1,1346 +1,1421 @@
 /*
  * Copyright (c) 2012 David Vossel <dvossel@redhat.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
 #include <crm_internal.h>
 
 #include <glib.h>
 #include <unistd.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/ipc.h>
 #include <crm/common/ipcs.h>
 #include <crm/msg_xml.h>
 
 #include <lrmd_private.h>
 
 #ifdef HAVE_SYS_TIMEB_H
 #  include <sys/timeb.h>
 #endif
 
 GHashTable *rsc_list = NULL;
 
 typedef struct lrmd_cmd_s {
     int timeout;
     int interval;
     int start_delay;
     int timeout_orig;
 
     int call_id;
     int exec_rc;
     int lrmd_op_status;
 
     int call_opts;
     /* Timer ids, must be removed on cmd destruction. */
     int delay_id;
     int stonith_recurring_id;
 
     int rsc_deleted;
 
     char *client_id;
     char *origin;
     char *rsc_id;
     char *action;
     char *output;
     char *userdata_str;
 
 #ifdef HAVE_SYS_TIMEB_H
     /* Timestamp of when op first ran */
     struct timeb t_first_run;
     /* Timestamp of when op ran */
     struct timeb t_run;
     /* Timestamp of when op was queued */
     struct timeb t_queue;
     /* Timestamp of last rc change */
     struct timeb t_rcchange;
 #endif
 
     int first_notify_sent;
     int last_notify_rc;
     int last_notify_op_status;
     int last_pid;
 
     GHashTable *params;
 } lrmd_cmd_t;
 
 static void cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc);
 static gboolean lrmd_rsc_dispatch(gpointer user_data);
 static void cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id);
 
 static void
 log_finished(lrmd_cmd_t * cmd, int exec_time, int queue_time)
 {
     char pid_str[32] = { 0, };
     int log_level = LOG_INFO;
 
     if (cmd->last_pid) {
         snprintf(pid_str, 32, "%d", cmd->last_pid);
     }
 
     if (safe_str_eq(cmd->action, "monitor")) {
         log_level = LOG_DEBUG;
     }
 #ifdef HAVE_SYS_TIMEB_H
     do_crm_log(log_level,
                "finished - rsc:%s action:%s call_id:%d %s%s exit-code:%d exec-time:%dms queue-time:%dms",
                cmd->rsc_id, cmd->action, cmd->call_id, cmd->last_pid ? "pid:" : "", pid_str,
                cmd->exec_rc, exec_time, queue_time);
 #else
     do_crm_log(log_level, "finished - rsc:%s action:%s call_id:%d %s%s exit-code:%d",
                cmd->rsc_id,
                cmd->action, cmd->call_id, cmd->last_pid ? "pid:" : "", pid_str, cmd->exec_rc);
 #endif
 }
 
 static void
 log_execute(lrmd_cmd_t * cmd)
 {
     int log_level = LOG_INFO;
 
     if (safe_str_eq(cmd->action, "monitor")) {
         log_level = LOG_DEBUG;
     }
 
     do_crm_log(log_level, "executing - rsc:%s action:%s call_id:%d",
                cmd->rsc_id, cmd->action, cmd->call_id);
 }
 
+static const char *
+normalize_action_name(lrmd_rsc_t * rsc, const char *action)
+{
+    if (safe_str_eq(action, "monitor") &&
+        (safe_str_eq(rsc->class, "lsb") ||
+         safe_str_eq(rsc->class, "service") || safe_str_eq(rsc->class, "systemd"))) {
+        return "status";
+    }
+    return action;
+}
+
 static lrmd_rsc_t *
 build_rsc_from_xml(xmlNode * msg)
 {
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, msg, LOG_ERR);
     lrmd_rsc_t *rsc = NULL;
 
     rsc = calloc(1, sizeof(lrmd_rsc_t));
 
     crm_element_value_int(msg, F_LRMD_CALLOPTS, &rsc->call_opts);
 
     rsc->rsc_id = crm_element_value_copy(rsc_xml, F_LRMD_RSC_ID);
     rsc->class = crm_element_value_copy(rsc_xml, F_LRMD_CLASS);
     rsc->provider = crm_element_value_copy(rsc_xml, F_LRMD_PROVIDER);
     rsc->type = crm_element_value_copy(rsc_xml, F_LRMD_TYPE);
     rsc->work = mainloop_add_trigger(G_PRIORITY_HIGH, lrmd_rsc_dispatch, rsc);
     return rsc;
 }
 
 static lrmd_cmd_t *
 create_lrmd_cmd(xmlNode * msg, crm_client_t * client)
 {
     int call_options = 0;
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, msg, LOG_ERR);
     lrmd_cmd_t *cmd = NULL;
 
     cmd = calloc(1, sizeof(lrmd_cmd_t));
 
     crm_element_value_int(msg, F_LRMD_CALLOPTS, &call_options);
     cmd->call_opts = call_options;
     cmd->client_id = strdup(client->id);
 
     crm_element_value_int(msg, F_LRMD_CALLID, &cmd->call_id);
     crm_element_value_int(rsc_xml, F_LRMD_RSC_INTERVAL, &cmd->interval);
     crm_element_value_int(rsc_xml, F_LRMD_TIMEOUT, &cmd->timeout);
     crm_element_value_int(rsc_xml, F_LRMD_RSC_START_DELAY, &cmd->start_delay);
     cmd->timeout_orig = cmd->timeout;
 
     cmd->origin = crm_element_value_copy(rsc_xml, F_LRMD_ORIGIN);
     cmd->action = crm_element_value_copy(rsc_xml, F_LRMD_RSC_ACTION);
     cmd->userdata_str = crm_element_value_copy(rsc_xml, F_LRMD_RSC_USERDATA_STR);
     cmd->rsc_id = crm_element_value_copy(rsc_xml, F_LRMD_RSC_ID);
 
     cmd->params = xml2list(rsc_xml);
 
     return cmd;
 }
 
 static void
 free_lrmd_cmd(lrmd_cmd_t * cmd)
 {
     if (cmd->stonith_recurring_id) {
         g_source_remove(cmd->stonith_recurring_id);
     }
     if (cmd->delay_id) {
         g_source_remove(cmd->delay_id);
     }
     if (cmd->params) {
         g_hash_table_destroy(cmd->params);
     }
     free(cmd->origin);
     free(cmd->action);
     free(cmd->userdata_str);
     free(cmd->rsc_id);
     free(cmd->output);
     free(cmd->client_id);
     free(cmd);
 }
 
 static gboolean
 stonith_recurring_op_helper(gpointer data)
 {
     lrmd_cmd_t *cmd = data;
     lrmd_rsc_t *rsc;
 
     cmd->stonith_recurring_id = 0;
 
     if (!cmd->rsc_id) {
         return FALSE;
     }
 
     rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
 
     CRM_ASSERT(rsc != NULL);
     /* take it out of recurring_ops list, and put it in the pending ops
      * to be executed */
     rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
     rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
 #ifdef HAVE_SYS_TIMEB_H
     ftime(&cmd->t_queue);
 #endif
     mainloop_set_trigger(rsc->work);
 
     return FALSE;
 }
 
 static gboolean
 start_delay_helper(gpointer data)
 {
     lrmd_cmd_t *cmd = data;
     lrmd_rsc_t *rsc = NULL;
 
     cmd->delay_id = 0;
     rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
 
     if (rsc) {
         mainloop_set_trigger(rsc->work);
     }
 
     return FALSE;
 }
 
+static gboolean
+merge_recurring_duplicate(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
+{
+    GListPtr gIter = NULL;
+    lrmd_cmd_t * dup = NULL;
+    gboolean dup_pending = FALSE;
+
+    if (cmd->interval == 0) {
+        return 0;
+    }
+
+    for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) {
+        dup = gIter->data;
+        if (safe_str_eq(cmd->action, dup->action) && cmd->interval == dup->interval) {
+            dup_pending = TRUE;
+            goto merge_dup;
+        }
+    }
+
+    /* if dup is in recurring_ops list, that means it has already executed
+     * and is in the interval loop. we can't just remove it in this case. */
+    for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) {
+        dup = gIter->data;
+        if (safe_str_eq(cmd->action, dup->action) && cmd->interval == dup->interval) {
+            goto merge_dup;
+        }
+    }
+
+    return FALSE;
+merge_dup:
+
+
+    /* This should not occur, if it does we need to investigate in the crmd
+     * how something like this is possible */
+    crm_warn("Duplicate recurring op entry detected (%s_%s_%d), merging with previous op entry",
+            rsc->rsc_id,
+            normalize_action_name(rsc, dup->action),
+            dup->interval);
+
+    /* merge */
+    dup->first_notify_sent = 0;
+    free(dup->userdata_str);
+    dup->userdata_str = cmd->userdata_str;
+    cmd->userdata_str = NULL;
+    dup->call_id = cmd->call_id;
+
+    if (safe_str_eq(rsc->class, "stonith")) {
+        /* if we are waiting for the next interval, kick it off now */
+        if (dup_pending == TRUE) {
+            g_source_remove(cmd->stonith_recurring_id);
+            cmd->stonith_recurring_id = 0;
+            stonith_recurring_op_helper(cmd);
+        }
+
+    } else if (dup_pending == FALSE) {
+        /* if we've already handed this to the service lib, kick off an early execution */
+        services_action_kick(rsc->rsc_id, normalize_action_name(rsc, dup->action), dup->interval);
+    }
+    free_lrmd_cmd(cmd);
+
+    return TRUE;
+}
+
 static void
 schedule_lrmd_cmd(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
+    gboolean dup_processed = FALSE;
     CRM_CHECK(cmd != NULL, return);
     CRM_CHECK(rsc != NULL, return);
 
     crm_trace("Scheduling %s on %s", cmd->action, rsc->rsc_id);
+
+    dup_processed = merge_recurring_duplicate(rsc, cmd);
+    if (dup_processed) {
+        /* duplicate recurring cmd found, cmds merged */
+        return;
+    }
+
+    /* crmd expects lrmd to automatically cancel recurring ops before rsc stops. */
+    if (rsc && safe_str_eq(cmd->action, "stop")) {
+        cancel_all_recurring(rsc, NULL);
+    }
+
     rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
 #ifdef HAVE_SYS_TIMEB_H
     ftime(&cmd->t_queue);
 #endif
     mainloop_set_trigger(rsc->work);
 
     if (cmd->start_delay) {
         cmd->delay_id = g_timeout_add(cmd->start_delay, start_delay_helper, cmd);
     }
-
 }
 
 static void
 send_reply(crm_client_t * client, int rc, uint32_t id, int call_id)
 {
     int send_rc = 0;
     xmlNode *reply = NULL;
 
     reply = create_xml_node(NULL, T_LRMD_REPLY);
     crm_xml_add(reply, F_LRMD_ORIGIN, __FUNCTION__);
     crm_xml_add_int(reply, F_LRMD_RC, rc);
     crm_xml_add_int(reply, F_LRMD_CALLID, call_id);
 
     send_rc = lrmd_server_send_reply(client, id, reply);
 
     free_xml(reply);
     if (send_rc < 0) {
         crm_warn("LRMD reply to %s failed: %d", client->name, send_rc);
     }
 }
 
 static void
 send_client_notify(gpointer key, gpointer value, gpointer user_data)
 {
     xmlNode *update_msg = user_data;
     crm_client_t *client = value;
 
     if (client == NULL) {
         crm_err("Asked to send event to  NULL client");
         return;
     } else if (client->name == NULL) {
         crm_trace("Asked to send event to client with no name");
         return;
     }
 
     if (lrmd_server_send_notify(client, update_msg) <= 0) {
         crm_warn("Notification of client %s/%s failed", client->name, client->id);
     }
 }
 
 #ifdef HAVE_SYS_TIMEB_H
 static int
 time_diff_ms(struct timeb *now, struct timeb *old)
 {
     int sec = difftime(now->time, old->time);
     int ms = now->millitm - old->millitm;
 
     if (old->time == 0) {
         return 0;
     }
 
     return (sec * 1000) + ms;
 }
 #endif
 
 static void
 send_cmd_complete_notify(lrmd_cmd_t * cmd)
 {
     int exec_time = 0;
     int queue_time = 0;
     xmlNode *notify = NULL;
 
 #ifdef HAVE_SYS_TIMEB_H
     struct timeb now = { 0, };
 
     ftime(&now);
     exec_time = time_diff_ms(&now, &cmd->t_run);
     queue_time = time_diff_ms(&cmd->t_run, &cmd->t_queue);
 #endif
 
     log_finished(cmd, exec_time, queue_time);
 
     /* if the first notify result for a cmd has already been sent earlier, and the
      * the option to only send notifies on result changes is set. Check to see
      * if the last result is the same as the new one. If so, suppress this update */
     if (cmd->first_notify_sent && (cmd->call_opts & lrmd_opt_notify_changes_only)) {
         if (cmd->last_notify_rc == cmd->exec_rc &&
             cmd->last_notify_op_status == cmd->lrmd_op_status) {
 
             /* only send changes */
             return;
         }
 
     }
 
     cmd->first_notify_sent = 1;
     cmd->last_notify_rc = cmd->exec_rc;
     cmd->last_notify_op_status = cmd->lrmd_op_status;
 
     notify = create_xml_node(NULL, T_LRMD_NOTIFY);
 
     crm_xml_add(notify, F_LRMD_ORIGIN, __FUNCTION__);
     crm_xml_add_int(notify, F_LRMD_TIMEOUT, cmd->timeout);
     crm_xml_add_int(notify, F_LRMD_RSC_INTERVAL, cmd->interval);
     crm_xml_add_int(notify, F_LRMD_RSC_START_DELAY, cmd->start_delay);
     crm_xml_add_int(notify, F_LRMD_EXEC_RC, cmd->exec_rc);
     crm_xml_add_int(notify, F_LRMD_OP_STATUS, cmd->lrmd_op_status);
     crm_xml_add_int(notify, F_LRMD_CALLID, cmd->call_id);
     crm_xml_add_int(notify, F_LRMD_RSC_DELETED, cmd->rsc_deleted);
 
 #ifdef HAVE_SYS_TIMEB_H
     crm_xml_add_int(notify, F_LRMD_RSC_RUN_TIME, cmd->t_run.time);
     crm_xml_add_int(notify, F_LRMD_RSC_RCCHANGE_TIME, cmd->t_rcchange.time);
     crm_xml_add_int(notify, F_LRMD_RSC_EXEC_TIME, exec_time);
     crm_xml_add_int(notify, F_LRMD_RSC_QUEUE_TIME, queue_time);
 #endif
 
     crm_xml_add(notify, F_LRMD_OPERATION, LRMD_OP_RSC_EXEC);
     crm_xml_add(notify, F_LRMD_RSC_ID, cmd->rsc_id);
     crm_xml_add(notify, F_LRMD_RSC_ACTION, cmd->action);
     crm_xml_add(notify, F_LRMD_RSC_USERDATA_STR, cmd->userdata_str);
     crm_xml_add(notify, F_LRMD_RSC_OUTPUT, cmd->output);
 
     if (cmd->params) {
         char *key = NULL;
         char *value = NULL;
         GHashTableIter iter;
 
         xmlNode *args = create_xml_node(notify, XML_TAG_ATTRS);
 
         g_hash_table_iter_init(&iter, cmd->params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             hash2field((gpointer) key, (gpointer) value, args);
         }
     }
 
     if (cmd->client_id && (cmd->call_opts & lrmd_opt_notify_orig_only)) {
         crm_client_t *client = crm_client_get_by_id(cmd->client_id);
 
         if (client) {
             send_client_notify(client->id, client, notify);
         }
     } else {
         g_hash_table_foreach(client_connections, send_client_notify, notify);
     }
 
     free_xml(notify);
 }
 
 static void
 send_generic_notify(int rc, xmlNode * request)
 {
     int call_id = 0;
     xmlNode *notify = NULL;
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID);
     const char *op = crm_element_value(request, F_LRMD_OPERATION);
 
     crm_element_value_int(request, F_LRMD_CALLID, &call_id);
 
     notify = create_xml_node(NULL, T_LRMD_NOTIFY);
     crm_xml_add(notify, F_LRMD_ORIGIN, __FUNCTION__);
     crm_xml_add_int(notify, F_LRMD_RC, rc);
     crm_xml_add_int(notify, F_LRMD_CALLID, call_id);
     crm_xml_add(notify, F_LRMD_OPERATION, op);
     crm_xml_add(notify, F_LRMD_RSC_ID, rsc_id);
 
     g_hash_table_foreach(client_connections, send_client_notify, notify);
 
     free_xml(notify);
 }
 
 static void
 cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc)
 {
     crm_trace("Resource operation rsc:%s action:%s completed (%p %p)", cmd->rsc_id, cmd->action,
               rsc ? rsc->active : NULL, cmd);
 
     if (rsc && (rsc->active == cmd)) {
         rsc->active = NULL;
         mainloop_set_trigger(rsc->work);
     }
 
     if (!rsc) {
         cmd->rsc_deleted = 1;
     }
 
     send_cmd_complete_notify(cmd);
 
-    /* crmd expects lrmd to automatically cancel recurring ops after rsc stops */
-    if (rsc && safe_str_eq(cmd->action, "stop")) {
-        cancel_all_recurring(rsc, NULL);
-    }
-
     if (cmd->interval && (cmd->lrmd_op_status == PCMK_LRM_OP_CANCELLED)) {
         if (rsc) {
             rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
             rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
         }
         free_lrmd_cmd(cmd);
     } else if (cmd->interval == 0) {
         if (rsc) {
             rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
         }
         free_lrmd_cmd(cmd);
     } else {
         /* Clear all the values pertaining just to the last iteration of a recurring op. */
         cmd->lrmd_op_status = 0;
         cmd->last_pid = 0;
         memset(&cmd->t_run, 0, sizeof(cmd->t_run));
         memset(&cmd->t_queue, 0, sizeof(cmd->t_queue));
         free(cmd->output);
         cmd->output = NULL;
     }
 }
 
 static int
 lsb2uniform_rc(const char *action, int rc)
 {
     if (rc < 0) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
     /* status has different return codes that everything else. */
     if (!safe_str_eq(action, "status") && !safe_str_eq(action, "monitor")) {
         if (rc > PCMK_LSB_NOT_RUNNING) {
             return PCMK_OCF_UNKNOWN_ERROR;
         }
         return rc;
     }
 
     switch (rc) {
         case PCMK_LSB_STATUS_OK:
             return PCMK_OCF_OK;
         case PCMK_LSB_STATUS_NOT_INSTALLED:
             return PCMK_OCF_NOT_INSTALLED;
         case PCMK_LSB_STATUS_VAR_PID:
         case PCMK_LSB_STATUS_VAR_LOCK:
         case PCMK_LSB_STATUS_NOT_RUNNING:
             return PCMK_OCF_NOT_RUNNING;
         default:
             return PCMK_OCF_UNKNOWN_ERROR;
     }
 
     return PCMK_OCF_UNKNOWN_ERROR;
 }
 
 static int
 ocf2uniform_rc(int rc)
 {
     if (rc < 0 || rc > PCMK_OCF_FAILED_MASTER) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
     return rc;
 }
 
 static int
 stonith2uniform_rc(const char *action, int rc)
 {
     if (rc == -ENODEV) {
         if (safe_str_eq(action, "stop")) {
             rc = PCMK_OCF_OK;
         } else if (safe_str_eq(action, "start")) {
             rc = PCMK_OCF_NOT_INSTALLED;
         } else {
             rc = PCMK_OCF_NOT_RUNNING;
         }
     } else if (rc != 0) {
         rc = PCMK_OCF_UNKNOWN_ERROR;
     }
     return rc;
 }
 
 #if SUPPORT_NAGIOS
 static int
 nagios2uniform_rc(const char *action, int rc)
 {
     if (rc < 0) {
         return PCMK_OCF_UNKNOWN_ERROR;
     }
 
     switch (rc) {
         case NAGIOS_STATE_OK:
             return PCMK_OCF_OK;
         case NAGIOS_INSUFFICIENT_PRIV:
             return PCMK_OCF_INSUFFICIENT_PRIV;
         case NAGIOS_NOT_INSTALLED:
             return PCMK_OCF_NOT_INSTALLED;
         case NAGIOS_STATE_WARNING:
         case NAGIOS_STATE_CRITICAL:
         case NAGIOS_STATE_UNKNOWN:
         case NAGIOS_STATE_DEPENDENT:
         default:
             return PCMK_OCF_UNKNOWN_ERROR;
     }
 
     return PCMK_OCF_UNKNOWN_ERROR;
 }
 #endif
 
 static int
 get_uniform_rc(const char *standard, const char *action, int rc)
 {
     if (safe_str_eq(standard, "ocf")) {
         return ocf2uniform_rc(rc);
     } else if (safe_str_eq(standard, "stonith")) {
         return stonith2uniform_rc(action, rc);
     } else if (safe_str_eq(standard, "systemd")) {
         return rc;
     } else if (safe_str_eq(standard, "upstart")) {
         return rc;
 #if SUPPORT_NAGIOS
     } else if (safe_str_eq(standard, "nagios")) {
         return nagios2uniform_rc(action, rc);
 #endif
     } else {
         return lsb2uniform_rc(action, rc);
     }
 }
 
 void
 notify_of_new_client(crm_client_t *new_client)
 {
     crm_client_t *client = NULL;
     GHashTableIter iter;
     xmlNode *notify = NULL;
     char *key = NULL;
 
     notify = create_xml_node(NULL, T_LRMD_NOTIFY);
     crm_xml_add(notify, F_LRMD_ORIGIN, __FUNCTION__);
     crm_xml_add(notify, F_LRMD_OPERATION, LRMD_OP_NEW_CLIENT);
 
     g_hash_table_iter_init(&iter, client_connections);
     while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & client)) {
 
         if (safe_str_eq(client->id, new_client->id)) {
             continue;
         }
 
         send_client_notify((gpointer) key, (gpointer) client, (gpointer) notify);
     }
     free_xml(notify);
 }
 
 void
 client_disconnect_cleanup(const char *client_id)
 {
     GHashTableIter iter;
     lrmd_rsc_t *rsc = NULL;
     char *key = NULL;
 
     g_hash_table_iter_init(&iter, rsc_list);
     while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) {
         if (rsc->call_opts & lrmd_opt_drop_recurring) {
             /* This client is disconnecting, drop any recurring operations
              * it may have initiated on the resource */
             cancel_all_recurring(rsc, client_id);
         }
     }
 }
 
 static void
 action_complete(svc_action_t * action)
 {
     lrmd_rsc_t *rsc;
     lrmd_cmd_t *cmd = action->cb_data;
 
     if (!cmd) {
         crm_err("LRMD action (%s) completed does not match any known operations.", action->id);
         return;
     }
 #ifdef HAVE_SYS_TIMEB_H
     if (cmd->exec_rc != action->rc) {
         ftime(&cmd->t_rcchange);
     }
 #endif
 
     cmd->last_pid = action->pid;
     cmd->exec_rc = get_uniform_rc(action->standard, cmd->action, action->rc);
     cmd->lrmd_op_status = action->status;
     rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
 
     if (action->stdout_data) {
         cmd->output = strdup(action->stdout_data);
     }
 #if SUPPORT_NAGIOS
     if (rsc && safe_str_eq(rsc->class, "nagios")) {
         if (safe_str_eq(cmd->action, "monitor") &&
             cmd->interval == 0 && cmd->exec_rc == PCMK_OCF_OK) {
             /* Successfully executed --version for the nagios plugin */
             cmd->exec_rc = PCMK_OCF_NOT_RUNNING;
 
         } else if (safe_str_eq(cmd->action, "start") && cmd->exec_rc != PCMK_OCF_OK) {
             int time_sum = 0;
             int timeout_left = 0;
             int delay = cmd->timeout_orig / 10;
 
 #  ifdef HAVE_SYS_TIMEB_H
             struct timeb now = { 0, };
 
             ftime(&now);
             time_sum = time_diff_ms(&now, &cmd->t_first_run);
             timeout_left = cmd->timeout_orig - time_sum;
             if (delay < timeout_left) {
                 cmd->start_delay = delay;
                 cmd->timeout = timeout_left;
 
                 crm_notice
                     ("%s %s failed (rc=%d): re-scheduling (time_sum=%dms, start_delay=%dms, timeout=%dms)",
                      cmd->rsc_id, cmd->action, cmd->exec_rc, time_sum, cmd->start_delay,
                      cmd->timeout);
 
                 cmd->lrmd_op_status = 0;
                 cmd->last_pid = 0;
                 memset(&cmd->t_run, 0, sizeof(cmd->t_run));
                 memset(&cmd->t_queue, 0, sizeof(cmd->t_queue));
                 free(cmd->output);
                 cmd->output = NULL;
 
                 rsc->active = NULL;
                 schedule_lrmd_cmd(rsc, cmd);
                 return;
             }
 #  endif
         }
     }
 #endif
 
     cmd_finalize(cmd, rsc);
 }
 
 static void
 stonith_action_complete(lrmd_cmd_t * cmd, int rc)
 {
     int recurring = cmd->interval;
     lrmd_rsc_t *rsc = NULL;
 
     cmd->exec_rc = get_uniform_rc("stonith", cmd->action, rc);
 
     rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
 
     if (cmd->lrmd_op_status == PCMK_LRM_OP_CANCELLED) {
         recurring = 0;
         /* do nothing */
     } else if (rc) {
         /* Attempt to map return codes to op status if possible */
         switch (rc) {
             case -EPROTONOSUPPORT:
                 cmd->lrmd_op_status = PCMK_LRM_OP_NOTSUPPORTED;
                 break;
             case -ETIME:
                 cmd->lrmd_op_status = PCMK_LRM_OP_TIMEOUT;
                 break;
             default:
                 cmd->lrmd_op_status = PCMK_LRM_OP_ERROR;
         }
     } else {
         /* command successful */
         cmd->lrmd_op_status = PCMK_LRM_OP_DONE;
         if (safe_str_eq(cmd->action, "start") && rsc) {
             rsc->stonith_started = 1;
         }
     }
 
     if (recurring && rsc) {
         if (cmd->stonith_recurring_id) {
             g_source_remove(cmd->stonith_recurring_id);
         }
         cmd->stonith_recurring_id = g_timeout_add(cmd->interval, stonith_recurring_op_helper, cmd);
     }
 
     cmd_finalize(cmd, rsc);
 }
 
 static void
 lrmd_stonith_callback(stonith_t * stonith, stonith_callback_data_t * data)
 {
     stonith_action_complete(data->userdata, data->rc);
 }
 
 void
 stonith_connection_failed(void)
 {
     GHashTableIter iter;
     GList *cmd_list = NULL;
     GList *cmd_iter = NULL;
     lrmd_rsc_t *rsc = NULL;
     char *key = NULL;
 
     g_hash_table_iter_init(&iter, rsc_list);
     while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) {
         if (safe_str_eq(rsc->class, "stonith")) {
             if (rsc->recurring_ops) {
                 cmd_list = g_list_concat(cmd_list, rsc->recurring_ops);
             }
             if (rsc->pending_ops) {
                 cmd_list = g_list_concat(cmd_list, rsc->pending_ops);
             }
             rsc->pending_ops = rsc->recurring_ops = NULL;
         }
     }
 
     if (!cmd_list) {
         return;
     }
 
     crm_err("STONITH connection failed, finalizing %d pending operations.",
             g_list_length(cmd_list));
     for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) {
         stonith_action_complete(cmd_iter->data, -ENOTCONN);
     }
     g_list_free(cmd_list);
 }
 
 static int
 lrmd_rsc_execute_stonith(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
     int rc = 0;
     int do_monitor = 0;
 
     stonith_t *stonith_api = get_stonith_connection();
 
     if (!stonith_api) {
         cmd->exec_rc = get_uniform_rc("stonith", cmd->action, -ENOTCONN);
         cmd->lrmd_op_status = PCMK_LRM_OP_ERROR;
         cmd_finalize(cmd, rsc);
         return -EUNATCH;
     }
 
     if (safe_str_eq(cmd->action, "start")) {
         char *key = NULL;
         char *value = NULL;
         stonith_key_value_t *device_params = NULL;
 
         if (cmd->params) {
             GHashTableIter iter;
 
             g_hash_table_iter_init(&iter, cmd->params);
             while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
                 device_params = stonith_key_value_add(device_params, key, value);
             }
         }
 
         /* Stonith automatically registers devices from the IPC when changes occur,
          * but to avoid a possible race condition between stonith receiving the IPC update
          * and the lrmd requesting that resource, the lrmd still registers the device as well.
          * Stonith knows how to handle duplicate device registrations correctly. */
         rc = stonith_api->cmds->register_device(stonith_api,
                                                 st_opt_sync_call,
                                                 cmd->rsc_id,
                                                 rsc->provider, rsc->type, device_params);
 
         stonith_key_value_freeall(device_params, 1, 1);
         if (rc == 0) {
             do_monitor = 1;
         }
     } else if (safe_str_eq(cmd->action, "stop")) {
         rc = stonith_api->cmds->remove_device(stonith_api, st_opt_sync_call, cmd->rsc_id);
         rsc->stonith_started = 0;
     } else if (safe_str_eq(cmd->action, "monitor")) {
         if (cmd->interval) {
             do_monitor = 1;
         } else {
             rc = rsc->stonith_started ? 0 : -ENODEV;
         }
     }
 
     if (!do_monitor) {
         goto cleanup_stonith_exec;
     }
 
     rc = stonith_api->cmds->monitor(stonith_api, 0, cmd->rsc_id, cmd->timeout / 1000);
 
     rc = stonith_api->cmds->register_callback(stonith_api,
                                               rc,
                                               0,
                                               0,
                                               cmd, "lrmd_stonith_callback", lrmd_stonith_callback);
 
     /* don't cleanup yet, we will find out the result of the monitor later */
     if (rc > 0) {
         rsc->active = cmd;
         return rc;
     } else if (rc == 0) {
         rc = -1;
     }
 
   cleanup_stonith_exec:
     stonith_action_complete(cmd, rc);
     return rc;
 }
 
-static const char *
-normalize_action_name(lrmd_rsc_t * rsc, const char *action)
-{
-    if (safe_str_eq(action, "monitor") &&
-        (safe_str_eq(rsc->class, "lsb") ||
-         safe_str_eq(rsc->class, "service") || safe_str_eq(rsc->class, "systemd"))) {
-        return "status";
-    }
-    return action;
-}
-
 static void
 dup_attr(gpointer key, gpointer value, gpointer user_data)
 {
     g_hash_table_replace(user_data, strdup(key), strdup(value));
 }
 
 static int
 lrmd_rsc_execute_service_lib(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
     svc_action_t *action = NULL;
     GHashTable *params_copy = NULL;
 
     CRM_ASSERT(rsc);
     CRM_ASSERT(cmd);
 
     crm_trace("Creating action, resource:%s action:%s class:%s provider:%s agent:%s",
               rsc->rsc_id, cmd->action, rsc->class, rsc->provider, rsc->type);
 
 #if SUPPORT_NAGIOS
     /* Recurring operations are cancelled anyway for a stop operation */
     if (safe_str_eq(rsc->class, "nagios") && safe_str_eq(cmd->action, "stop")) {
         cmd->exec_rc = PCMK_OCF_OK;
         goto exec_done;
     }
 #endif
 
     if (cmd->params) {
         params_copy = g_hash_table_new_full(crm_str_hash,
                                             g_str_equal, g_hash_destroy_str, g_hash_destroy_str);
 
         if (params_copy != NULL) {
             g_hash_table_foreach(cmd->params, dup_attr, params_copy);
         }
     }
 
     action = resources_action_create(rsc->rsc_id,
                                      rsc->class,
                                      rsc->provider,
                                      rsc->type,
                                      normalize_action_name(rsc, cmd->action),
                                      cmd->interval, cmd->timeout, params_copy);
 
     if (!action) {
         crm_err("Failed to create action, action:%s on resource %s", cmd->action, rsc->rsc_id);
         cmd->lrmd_op_status = PCMK_LRM_OP_ERROR;
         goto exec_done;
     }
 
     action->cb_data = cmd;
 
     /* 'cmd' may not be valid after this point if
      * services_action_async() returned TRUE
      *
      * Upstart and systemd both synchronously determine monitor/status
      * results and call action_complete (which may free 'cmd') if necessary.
      */
     if (services_action_async(action, action_complete)) {
         return TRUE;
     }
 
     cmd->exec_rc = action->rc;
     if(action->status != PCMK_LRM_OP_DONE) {
         cmd->lrmd_op_status = action->status;
     } else {
         cmd->lrmd_op_status = PCMK_LRM_OP_ERROR;
     }
     services_action_free(action);
     action = NULL;
 
   exec_done:
     cmd_finalize(cmd, rsc);
     return TRUE;
 }
 
 static gboolean
 lrmd_rsc_execute(lrmd_rsc_t * rsc)
 {
     lrmd_cmd_t *cmd = NULL;
 
     CRM_CHECK(rsc != NULL, return FALSE);
 
     if (rsc->active) {
         crm_trace("%s is still active", rsc->rsc_id);
         return TRUE;
     }
 
     if (rsc->pending_ops) {
         GList *first = rsc->pending_ops;
 
         cmd = first->data;
         if (cmd->delay_id) {
             crm_trace
                 ("Command %s %s was asked to run too early, waiting for start_delay timeout of %dms",
                  cmd->rsc_id, cmd->action, cmd->start_delay);
             return TRUE;
         }
         rsc->pending_ops = g_list_remove_link(rsc->pending_ops, first);
         g_list_free_1(first);
 
 #ifdef HAVE_SYS_TIMEB_H
         if (cmd->t_first_run.time == 0) {
             ftime(&cmd->t_first_run);
         }
         ftime(&cmd->t_run);
 #endif
     }
 
     if (!cmd) {
         crm_trace("Nothing further to do for %s", rsc->rsc_id);
         return TRUE;
     }
 
     rsc->active = cmd;          /* only one op at a time for a rsc */
     if (cmd->interval) {
         rsc->recurring_ops = g_list_append(rsc->recurring_ops, cmd);
     }
 
     log_execute(cmd);
 
     if (safe_str_eq(rsc->class, "stonith")) {
         lrmd_rsc_execute_stonith(rsc, cmd);
     } else {
         lrmd_rsc_execute_service_lib(rsc, cmd);
     }
 
     return TRUE;
 }
 
 static gboolean
 lrmd_rsc_dispatch(gpointer user_data)
 {
     return lrmd_rsc_execute(user_data);
 }
 
 void
 free_rsc(gpointer data)
 {
     GListPtr gIter = NULL;
     lrmd_rsc_t *rsc = data;
     int is_stonith = safe_str_eq(rsc->class, "stonith");
 
     for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) {
         lrmd_cmd_t *cmd = gIter->data;
 
         /* command was never executed */
         cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
         cmd_finalize(cmd, NULL);
     }
     /* frees list, but not list elements. */
     g_list_free(rsc->pending_ops);
 
     for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) {
         lrmd_cmd_t *cmd = gIter->data;
 
         if (is_stonith) {
             cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
             cmd_finalize(cmd, NULL);
         } else {
             /* This command is already handed off to service library,
              * let service library cancel it and tell us via the callback
              * when it is cancelled. The rsc can be safely destroyed
              * even if we are waiting for the cancel result */
             services_action_cancel(rsc->rsc_id, normalize_action_name(rsc, cmd->action), cmd->interval);
         }
     }
     /* frees list, but not list elements. */
     g_list_free(rsc->recurring_ops);
 
     free(rsc->rsc_id);
     free(rsc->class);
     free(rsc->provider);
     free(rsc->type);
     mainloop_destroy_trigger(rsc->work);
 
     free(rsc);
 }
 
 static int
 process_lrmd_signon(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     xmlNode *reply = create_xml_node(NULL, "reply");
     const char *is_ipc_provider = crm_element_value(request, F_LRMD_IS_IPC_PROVIDER);
     const char *protocol_version = crm_element_value(request, F_LRMD_PROTOCOL_VERSION);
 
     if (safe_str_neq(protocol_version, LRMD_PROTOCOL_VERSION)) {
         crm_xml_add_int(reply, F_LRMD_RC, -EPROTO);
         crm_xml_add(reply, F_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
     }
 
     crm_xml_add(reply, F_LRMD_OPERATION, CRM_OP_REGISTER);
     crm_xml_add(reply, F_LRMD_CLIENTID, client->id);
     lrmd_server_send_reply(client, id, reply);
 
     if (crm_is_true(is_ipc_provider)) {
         /* this is a remote connection from a cluster nodes crmd */
 #ifdef SUPPORT_REMOTE
         ipc_proxy_add_provider(client);
 #endif
     }
 
     free_xml(reply);
     return pcmk_ok;
 }
 
 static int
 process_lrmd_rsc_register(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     int rc = pcmk_ok;
     lrmd_rsc_t *rsc = build_rsc_from_xml(request);
     lrmd_rsc_t *dup = g_hash_table_lookup(rsc_list, rsc->rsc_id);
 
     if (dup &&
         safe_str_eq(rsc->class, dup->class) &&
         safe_str_eq(rsc->provider, dup->provider) && safe_str_eq(rsc->type, dup->type)) {
 
         crm_warn("Can't add, RSC '%s' already present in the rsc list (%d active resources)",
                  rsc->rsc_id, g_hash_table_size(rsc_list));
 
         free_rsc(rsc);
         return rc;
     }
 
     g_hash_table_replace(rsc_list, rsc->rsc_id, rsc);
     crm_info("Added '%s' to the rsc list (%d active resources)",
              rsc->rsc_id, g_hash_table_size(rsc_list));
 
     return rc;
 }
 
 static void
 process_lrmd_get_rsc_info(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     int rc = pcmk_ok;
     int send_rc = 0;
     int call_id = 0;
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID);
     xmlNode *reply = NULL;
     lrmd_rsc_t *rsc = NULL;
 
     crm_element_value_int(request, F_LRMD_CALLID, &call_id);
 
     if (!rsc_id) {
         rc = -ENODEV;
         goto get_rsc_done;
     }
 
     if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) {
         crm_info("Resource '%s' not found (%d active resources)",
                  rsc_id, g_hash_table_size(rsc_list));
         rc = -ENODEV;
         goto get_rsc_done;
     }
 
   get_rsc_done:
 
     reply = create_xml_node(NULL, T_LRMD_REPLY);
     crm_xml_add(reply, F_LRMD_ORIGIN, __FUNCTION__);
     crm_xml_add_int(reply, F_LRMD_RC, rc);
     crm_xml_add_int(reply, F_LRMD_CALLID, call_id);
 
     if (rsc) {
         crm_xml_add(reply, F_LRMD_RSC_ID, rsc->rsc_id);
         crm_xml_add(reply, F_LRMD_CLASS, rsc->class);
         crm_xml_add(reply, F_LRMD_PROVIDER, rsc->provider);
         crm_xml_add(reply, F_LRMD_TYPE, rsc->type);
     }
 
     send_rc = lrmd_server_send_reply(client, id, reply);
 
     if (send_rc < 0) {
         crm_warn("LRMD reply to %s failed: %d", client->name, send_rc);
     }
 
     free_xml(reply);
 }
 
 static int
 process_lrmd_rsc_unregister(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     int rc = pcmk_ok;
     lrmd_rsc_t *rsc = NULL;
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID);
 
     if (!rsc_id) {
         return -ENODEV;
     }
 
     if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) {
         crm_info("Resource '%s' not found (%d active resources)",
                  rsc_id, g_hash_table_size(rsc_list));
         return pcmk_ok;
     }
 
     if (rsc->active) {
         /* let the caller know there are still active ops on this rsc to watch for */
         crm_trace("Operation still in progress: %p", rsc->active);
         rc = -EINPROGRESS;
     }
 
     g_hash_table_remove(rsc_list, rsc_id);
 
     return rc;
 }
 
 static int
 process_lrmd_rsc_exec(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     lrmd_rsc_t *rsc = NULL;
     lrmd_cmd_t *cmd = NULL;
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID);
+    int call_id;
 
     if (!rsc_id) {
         return -EINVAL;
     }
     if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) {
         crm_info("Resource '%s' not found (%d active resources)",
                  rsc_id, g_hash_table_size(rsc_list));
         return -ENODEV;
     }
 
     cmd = create_lrmd_cmd(request, client);
+    call_id = cmd->call_id;
+
+    /* Don't reference cmd after handing it off to be scheduled.
+     * The cmd could get merged and freed. */
     schedule_lrmd_cmd(rsc, cmd);
 
-    return cmd->call_id;
+    return call_id;
 }
 
 static int
 cancel_op(const char *rsc_id, const char *action, int interval)
 {
     GListPtr gIter = NULL;
     lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, rsc_id);
 
     /* How to cancel an action.
      * 1. Check pending ops list, if it hasn't been handed off
      *    to the service library or stonith recurring list remove
      *    it there and that will stop it.
      * 2. If it isn't in the pending ops list, then its either a
      *    recurring op in the stonith recurring list, or the service
      *    library's recurring list.  Stop it there
      * 3. If not found in any lists, then this operation has either
      *    been executed already and is not a recurring operation, or
      *    never existed.
      */
     if (!rsc) {
         return -ENODEV;
     }
 
     for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) {
         lrmd_cmd_t *cmd = gIter->data;
 
         if (safe_str_eq(cmd->action, action) && cmd->interval == interval) {
             cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
             cmd_finalize(cmd, rsc);
             return pcmk_ok;
         }
     }
 
     if (safe_str_eq(rsc->class, "stonith")) {
         /* The service library does not handle stonith operations.
          * We have to handle recurring stonith opereations ourselves. */
         for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) {
             lrmd_cmd_t *cmd = gIter->data;
 
             if (safe_str_eq(cmd->action, action) && cmd->interval == interval) {
                 cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
                 if (rsc->active != cmd) {
                     cmd_finalize(cmd, rsc);
                 }
                 return pcmk_ok;
             }
         }
     } else if (services_action_cancel(rsc_id, normalize_action_name(rsc, action), interval) == TRUE) {
         /* The service library will tell the action_complete callback function
          * this action was cancelled, which will destroy the cmd and remove
          * it from the recurring_op list. Do not do that in this function
          * if the service library says it cancelled it. */
         return pcmk_ok;
     }
 
     return -EOPNOTSUPP;
 }
 
 static void
 cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id)
 {
     GList *cmd_list = NULL;
     GList *cmd_iter = NULL;
 
     /* Notice a copy of each list is created when concat is called.
      * This prevents odd behavior from occurring when the cmd_list
      * is iterated through later on.  It is possible the cancel_op
      * function may end up modifying the recurring_ops and pending_ops
      * lists.  If we did not copy those lists, our cmd_list iteration
      * could get messed up.*/
     if (rsc->recurring_ops) {
         cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->recurring_ops));
     }
     if (rsc->pending_ops) {
         cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->pending_ops));
     }
     if (!cmd_list) {
         return;
     }
 
     for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) {
         lrmd_cmd_t *cmd = cmd_iter->data;
 
         if (cmd->interval == 0) {
             continue;
         }
 
         if (client_id && safe_str_neq(cmd->client_id, client_id)) {
             continue;
         }
 
         cancel_op(rsc->rsc_id, cmd->action, cmd->interval);
     }
     /* frees only the copied list data, not the cmds */
     g_list_free(cmd_list);
 }
 
 static int
 process_lrmd_rsc_cancel(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     xmlNode *rsc_xml = get_xpath_object("//" F_LRMD_RSC, request, LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, F_LRMD_RSC_ID);
     const char *action = crm_element_value(rsc_xml, F_LRMD_RSC_ACTION);
     int interval = 0;
 
     crm_element_value_int(rsc_xml, F_LRMD_RSC_INTERVAL, &interval);
 
     if (!rsc_id || !action) {
         return -EINVAL;
     }
 
     return cancel_op(rsc_id, action, interval);
 }
 
 void
 process_lrmd_message(crm_client_t * client, uint32_t id, xmlNode * request)
 {
     int rc = pcmk_ok;
     int call_id = 0;
     const char *op = crm_element_value(request, F_LRMD_OPERATION);
     int do_reply = 0;
     int do_notify = 0;
 
     crm_trace("Processing %s operation from %s", op, client->id);
     crm_element_value_int(request, F_LRMD_CALLID, &call_id);
 
     if (crm_str_eq(op, CRM_OP_IPC_FWD, TRUE)) {
 #ifdef SUPPORT_REMOTE
         ipc_proxy_forward_client(client, request);
 #endif
         do_reply = 1;
     } else if (crm_str_eq(op, CRM_OP_REGISTER, TRUE)) {
         rc = process_lrmd_signon(client, id, request);
     } else if (crm_str_eq(op, LRMD_OP_RSC_REG, TRUE)) {
         rc = process_lrmd_rsc_register(client, id, request);
         do_notify = 1;
         do_reply = 1;
     } else if (crm_str_eq(op, LRMD_OP_RSC_INFO, TRUE)) {
         process_lrmd_get_rsc_info(client, id, request);
     } else if (crm_str_eq(op, LRMD_OP_RSC_UNREG, TRUE)) {
         rc = process_lrmd_rsc_unregister(client, id, request);
         /* don't notify anyone about failed un-registers */
         if (rc == pcmk_ok || rc == -EINPROGRESS) {
             do_notify = 1;
         }
         do_reply = 1;
     } else if (crm_str_eq(op, LRMD_OP_RSC_EXEC, TRUE)) {
         rc = process_lrmd_rsc_exec(client, id, request);
         do_reply = 1;
     } else if (crm_str_eq(op, LRMD_OP_RSC_CANCEL, TRUE)) {
         rc = process_lrmd_rsc_cancel(client, id, request);
         do_reply = 1;
     } else if (crm_str_eq(op, LRMD_OP_POKE, TRUE)) {
         do_notify = 1;
         do_reply = 1;
     } else {
         rc = -EOPNOTSUPP;
         do_reply = 1;
         crm_err("Unknown %s from %s", op, client->name);
         crm_log_xml_warn(request, "UnknownOp");
     }
 
     crm_debug("Processed %s operation from %s: rc=%d, reply=%d, notify=%d, exit=%d",
               op, client->id, rc, do_reply, do_notify, exit);
 
     if (do_reply) {
         send_reply(client, rc, id, call_id);
     }
 
     if (do_notify) {
         send_generic_notify(rc, request);
     }
 }
diff --git a/lrmd/regression.py.in b/lrmd/regression.py.in
index 1bd744c691..5b8cd44e80 100755
--- a/lrmd/regression.py.in
+++ b/lrmd/regression.py.in
@@ -1,1003 +1,1026 @@
 #!/usr/bin/python
 
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; either version 2
 # of the License, or (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
 
 import os
 import sys
 import subprocess
 import shlex
 import time
 
 # Where to find test binaries
 # Prefer the source tree if available
 build_dir="@abs_top_builddir@"
 test_dir=sys.path[0]
 
 new_path=os.environ['PATH']
 
 if os.path.exists("%s/regression.py.in" % test_dir):
     print "Running tests from the source tree: %s (%s)" % (build_dir, test_dir)
     new_path = "%s/lrmd:%s" % (build_dir, new_path)    # For lrmd, lrmd_test and pacemaker_remoted
     new_path = "%s/tools:%s" % (build_dir, new_path)   # For crm_resource
     new_path = "%s/fencing:%s" % (build_dir, new_path) # For stonithd
 else:
     print "Running tests from the install tree: @CRM_DAEMON_DIR@ (not %s)" % test_dir
     new_path = "@CRM_DAEMON_DIR@:%s" % (new_path) # For stonithd, lrmd, lrmd_test and pacemaker_remoted
 
 print new_path
 os.environ['PATH']=new_path
 
 def output_from_command(command, no_wait=0):
 	test = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
 
 	if no_wait == 0:
 		test.wait()
 	else:
 		return 0
 
 	return test.communicate()[0].split("\n")
 
 class Test:
 	def __init__(self, name, description, verbose = 0, tls = 0):
 		self.name = name
 		self.description = description
 		self.cmds = []
 
 		if tls:
 			self.daemon_location = "pacemaker_remoted"
 		else:
 			self.daemon_location = "lrmd"
 
 		self.test_tool_location = "lrmd_test"
 		self.verbose = verbose
 		self.tls = tls
 
 		self.result_txt = ""
 		self.cmd_tool_output = ""
 		self.result_exitcode = 0;
 
 		self.lrmd_process = None
 		self.stonith_process = None
 
 		self.executed = 0
 
 	def __new_cmd(self, cmd, args, exitcode, stdout_match = "", no_wait = 0, stdout_negative_match = "", kill=None):
 		if self.verbose and cmd == self.test_tool_location:
 			args = args + " -V "
 
 		if (cmd == self.test_tool_location) and self.tls:
 			args = args + " -S "
 
 		self.cmds.append(
 			{
 				"cmd" : cmd,
 				"kill" : kill,
 				"args" : args,
 				"expected_exitcode" : exitcode,
 				"stdout_match" : stdout_match,
 				"stdout_negative_match" : stdout_negative_match,
 				"no_wait" : no_wait,
 				"cmd_output" : "",
 			}
 		)
 
 	def start_environment(self):
 		### make sure we are in full control here ###
 		cmd = shlex.split("killall -q -9 stonithd lt-stonithd lrmd lt-lrmd lrmd_test lt-lrmd_test pacemaker_remoted")
 		test = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 		test.wait()
 
 		additional_args = ""
 
 		if self.tls == 0:
 			self.stonith_process = subprocess.Popen(shlex.split("stonithd -s"))
 
 		if self.verbose:
 			additional_args = additional_args + " -V"
 
 		self.lrmd_process = subprocess.Popen(shlex.split("%s %s -l /tmp/lrmd-regression.log" % (self.daemon_location, additional_args)))
 
 		time.sleep(1)
 
 	def clean_environment(self):
 		if self.lrmd_process:
 			self.lrmd_process.terminate()
 			self.lrmd_process.wait()
 
 			if self.verbose:
 				print "Daemon output"
 				f = open('/tmp/lrmd-regression.log', 'r')
 				for line in f.readlines():
 					print line.strip()
 			os.remove('/tmp/lrmd-regression.log')
 
 		if self.stonith_process:
 			self.stonith_process.terminate()
 			self.stonith_process.wait()
 
 		self.lrmd_process = None
 		self.stonith_process = None
 
 	def add_sys_cmd(self, cmd, args):
 		self.__new_cmd(cmd, args, 0, "")
 
 	def add_sys_cmd_no_wait(self, cmd, args):
 		self.__new_cmd(cmd, args, 0, "", 1)
 
 	def add_cmd_check_stdout(self, args, match, no_match = ""):
 		self.__new_cmd(self.test_tool_location, args, 0, match, 0, no_match)
 
 	def add_cmd(self, args):
 		self.__new_cmd(self.test_tool_location, args, 0, "")
 
 	def add_cmd_and_kill(self, killProc, args):
 		self.__new_cmd(self.test_tool_location, args, 0, "", kill=killProc)
 
 	def add_expected_fail_cmd(self, args):
 		self.__new_cmd(self.test_tool_location, args, 1, "")
 
 	def get_exitcode(self):
 		return self.result_exitcode
 
 	def print_result(self, filler):
 		print "%s%s" % (filler, self.result_txt)
 
 	def run_cmd(self, args):
 		cmd = shlex.split(args['args'])
 		cmd.insert(0, args['cmd'])
 		if self.verbose:
 			print "\n\nRunning: "+" ".join(cmd)
 		test = subprocess.Popen(cmd, stdout=subprocess.PIPE)
 
 		if args['kill']:
 			if self.verbose:
 				print "Also running: "+args['kill']
 			### Typically the kill argument is used to detect some sort of
 			### failure.  Without yeilding for a few seconds here the process
 			### launched earlier that is listening for the failure may not have time
 			### to connect to the lrmd.
 			time.sleep(2)
 			subprocess.Popen(shlex.split(args['kill']))
 
 		if args['no_wait'] == 0:
 			test.wait()
 		else:
 			return 0
 
 		output = test.communicate()[0]
 
 		if args['stdout_match'] != "" and output.count(args['stdout_match']) == 0:
 			test.returncode = -2
 			print "STDOUT string '%s' was not found in cmd output" % (args['stdout_match'])
 
 		if args['stdout_negative_match'] != "" and output.count(args['stdout_negative_match']) != 0:
 			test.returncode = -2
 			print "STDOUT string '%s' was found in cmd output" % (args['stdout_negative_match'])
 
 		args['cmd_output'] = output
 
 		return test.returncode;
 
 	def run(self):
 		res = 0
 		i = 1
 
 		if self.tls and self.name.count("stonith") != 0:
 			self.result_txt = "SKIPPED - '%s' - disabled when testing pacemaker_remote" % (self.name)
 			print self.result_txt
 			return res
 
 		self.start_environment()
 
 		if self.verbose:
 			print "\n--- START TEST - %s" % self.name
 
 		self.result_txt = "SUCCESS - '%s'" % (self.name)
 		self.result_exitcode = 0
 		for cmd in self.cmds:
 			res = self.run_cmd(cmd)
 			if res != cmd['expected_exitcode']:
 				print cmd['cmd_output']
 				print "Step %d FAILED - command returned %d, expected %d" % (i, res, cmd['expected_exitcode'])
 				self.result_txt = "FAILURE - '%s' failed at step %d. Command: lrmd_test %s" % (self.name, i, cmd['args'])
 				self.result_exitcode = -1
 				break
 			else:
 				if self.verbose:
 					print cmd['cmd_output'].strip()
 					print "Step %d SUCCESS" % (i)
 			i = i + 1
 		self.clean_environment()
 
 		print self.result_txt
 		if self.verbose:
 			print "--- END TEST - %s\n" % self.name
 
 		self.executed = 1
 		return res
 
 class Tests:
 	def __init__(self, verbose = 0, tls = 0):
 		self.tests = []
 		self.verbose = verbose
 		self.tls = tls;
 		self.rsc_classes = output_from_command("crm_resource --list-standards")
 		self.rsc_classes = self.rsc_classes[:-1] # Strip trailing empty line
 		self.need_authkey = 0
                 self.action_timeout = " -t 5000 "
 		if self.tls:
 			self.rsc_classes.remove("stonith")
 
 		print "Testing "+repr(self.rsc_classes)
 
 		self.common_cmds = {
 			"ocf_reg_line"      : "-c register_rsc -r ocf_test_rsc "+self.action_timeout+" -C ocf -P pacemaker -T Dummy",
 			"ocf_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:ocf_test_rsc action:none rc:ok op_status:complete\"",
 			"ocf_unreg_line"    : "-c unregister_rsc -r \"ocf_test_rsc\" "+self.action_timeout,
 			"ocf_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:ocf_test_rsc action:none rc:ok op_status:complete\"",
 			"ocf_start_line"    : "-c exec -r \"ocf_test_rsc\" -a \"start\" "+self.action_timeout,
 			"ocf_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:start rc:ok op_status:complete\" ",
 			"ocf_stop_line"     : "-c exec -r \"ocf_test_rsc\" -a \"stop\" "+self.action_timeout,
 			"ocf_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:stop rc:ok op_status:complete\" ",
 			"ocf_monitor_line"  : "-c exec -r \"ocf_test_rsc\" -a \"monitor\" -i \"2000\" "+self.action_timeout,
 			"ocf_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
 			"ocf_cancel_line"   : "-c cancel -r \"ocf_test_rsc\" -a \"monitor\" -i \"2000\" -t \"3000\" ",
 			"ocf_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:ocf_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
 			"systemd_reg_line"      : "-c register_rsc -r systemd_test_rsc "+self.action_timeout+" -C systemd -T lrmd_dummy_daemon",
 			"systemd_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:systemd_test_rsc action:none rc:ok op_status:complete\"",
 			"systemd_unreg_line"    : "-c unregister_rsc -r \"systemd_test_rsc\" "+self.action_timeout,
 			"systemd_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:systemd_test_rsc action:none rc:ok op_status:complete\"",
 			"systemd_start_line"    : "-c exec -r \"systemd_test_rsc\" -a \"start\" "+self.action_timeout,
 			"systemd_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:start rc:ok op_status:complete\" ",
 			"systemd_stop_line"     : "-c exec -r \"systemd_test_rsc\" -a \"stop\" "+self.action_timeout,
 			"systemd_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:stop rc:ok op_status:complete\" ",
 			"systemd_monitor_line"  : "-c exec -r \"systemd_test_rsc\" -a \"monitor\" -i \"2000\" "+self.action_timeout,
 			"systemd_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
 			"systemd_cancel_line"   : "-c cancel -r \"systemd_test_rsc\" -a \"monitor\" -i \"2000\" -t \"3000\" ",
 			"systemd_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:systemd_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
 			"upstart_reg_line"      : "-c register_rsc -r upstart_test_rsc "+self.action_timeout+" -C upstart -T lrmd_dummy_daemon",
 			"upstart_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:upstart_test_rsc action:none rc:ok op_status:complete\"",
 			"upstart_unreg_line"    : "-c unregister_rsc -r \"upstart_test_rsc\" "+self.action_timeout,
 			"upstart_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:upstart_test_rsc action:none rc:ok op_status:complete\"",
 			"upstart_start_line"    : "-c exec -r \"upstart_test_rsc\" -a \"start\" "+self.action_timeout,
 			"upstart_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:start rc:ok op_status:complete\" ",
 			"upstart_stop_line"     : "-c exec -r \"upstart_test_rsc\" -a \"stop\" "+self.action_timeout,
 			"upstart_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:stop rc:ok op_status:complete\" ",
 			"upstart_monitor_line"  : "-c exec -r \"upstart_test_rsc\" -a \"monitor\" -i \"2000\" "+self.action_timeout,
 			"upstart_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
 			"upstart_cancel_line"   : "-c cancel -r \"upstart_test_rsc\" -a \"monitor\" -i \"2000\" -t \"3000\" ",
 			"upstart_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:upstart_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
 			"service_reg_line"      : "-c register_rsc -r service_test_rsc "+self.action_timeout+" -C service -T LSBDummy",
 			"service_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:service_test_rsc action:none rc:ok op_status:complete\"",
 			"service_unreg_line"    : "-c unregister_rsc -r \"service_test_rsc\" "+self.action_timeout,
 			"service_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:service_test_rsc action:none rc:ok op_status:complete\"",
 			"service_start_line"    : "-c exec -r \"service_test_rsc\" -a \"start\" "+self.action_timeout,
 			"service_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:start rc:ok op_status:complete\" ",
 			"service_stop_line"     : "-c exec -r \"service_test_rsc\" -a \"stop\" "+self.action_timeout,
 			"service_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:stop rc:ok op_status:complete\" ",
 			"service_monitor_line"  : "-c exec -r \"service_test_rsc\" -a \"monitor\" -i \"2000\" "+self.action_timeout,
 			"service_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
 			"service_cancel_line"   : "-c cancel -r \"service_test_rsc\" -a \"monitor\" -i \"2000\" -t \"3000\" ",
 			"service_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:service_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 
 			"lsb_reg_line"      : "-c register_rsc -r lsb_test_rsc "+self.action_timeout+" -C lsb -T LSBDummy",
 			"lsb_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:lsb_test_rsc action:none rc:ok op_status:complete\" ",
 			"lsb_unreg_line"    : "-c unregister_rsc -r \"lsb_test_rsc\" "+self.action_timeout,
 			"lsb_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:lsb_test_rsc action:none rc:ok op_status:complete\"",
 			"lsb_start_line"    : "-c exec -r \"lsb_test_rsc\" -a \"start\" "+self.action_timeout,
 			"lsb_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:start rc:ok op_status:complete\" ",
 			"lsb_stop_line"     : "-c exec -r \"lsb_test_rsc\" -a \"stop\" "+self.action_timeout,
 			"lsb_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:stop rc:ok op_status:complete\" ",
 			"lsb_monitor_line"  : "-c exec -r \"lsb_test_rsc\" -a status -i \"2000\" "+self.action_timeout,
 			"lsb_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:status rc:ok op_status:complete\" "+self.action_timeout,
 			"lsb_cancel_line"   : "-c cancel -r \"lsb_test_rsc\" -a \"status\" -i \"2000\" -t \"3000\" ",
 			"lsb_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:lsb_test_rsc action:status rc:ok op_status:Cancelled\" ",
 
 			"stonith_reg_line"      : "-c register_rsc -r stonith_test_rsc "+self.action_timeout+" -C stonith -P pacemaker -T fence_dummy_monitor",
 			"stonith_reg_event"     : "-l \"NEW_EVENT event_type:register rsc_id:stonith_test_rsc action:none rc:ok op_status:complete\" ",
 			"stonith_unreg_line"    : "-c unregister_rsc -r \"stonith_test_rsc\" "+self.action_timeout,
 			"stonith_unreg_event"   : "-l \"NEW_EVENT event_type:unregister rsc_id:stonith_test_rsc action:none rc:ok op_status:complete\"",
 			"stonith_start_line"    : "-c exec -r \"stonith_test_rsc\" -a \"start\" -t 8000 ",
 			"stonith_start_event"   : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:start rc:ok op_status:complete\" ",
 			"stonith_stop_line"     : "-c exec -r \"stonith_test_rsc\" -a \"stop\" "+self.action_timeout,
 			"stonith_stop_event"    : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:stop rc:ok op_status:complete\" ",
 			"stonith_monitor_line"  : "-c exec -r \"stonith_test_rsc\" -a \"monitor\" -i \"2000\" "+self.action_timeout,
 			"stonith_monitor_event" : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout,
 			"stonith_cancel_line"   : "-c cancel -r \"stonith_test_rsc\" -a \"monitor\" -i \"2000\" -t \"3000\" ",
 			"stonith_cancel_event"  : "-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:Cancelled\" ",
 		}
 
 	def new_test(self, name, description):
 		test = Test(name, description, self.verbose, self.tls)
 		self.tests.append(test)
 		return test
 
 	def setup_test_environment(self):
 		os.system("service pacemaker_remote stop")
 		self.cleanup_test_environment()
 
 		if self.tls and not os.path.isfile("/etc/pacemaker/authkey"):
 			self.need_authkey = 1
 			os.system("mkdir -p /etc/pacemaker")
 			os.system("dd if=/dev/urandom of=/etc/pacemaker/authkey bs=4096 count=1")
 
 		### Make fake systemd daemon and unit file ###
 		dummy_daemon = "#!/bin/bash\nwhile true\ndo\nsleep 5\ndone"
 		dummy_service_file = ("[Unit]\n"
 			"Description=Dummy Resource\n"
 			"[Service]\n"
 			"Type=simple\n"
 			"ExecStart=/usr/sbin/lrmd_dummy_daemon\n")
 		dummy_upstart_job = ("""
 description     "Dummy service for regression tests"
 exec dd if=/dev/random of=/dev/null
 """)
 
 		dummy_fence_sleep_agent = ("""#!/usr/bin/python
 import sys
 import time
 def main():
     for line in sys.stdin.readlines():
         if line.count("monitor") > 0:
             time.sleep(30000)
             sys.exit(0)
     sys.exit(-1)
 if __name__ == "__main__":
     main()
 """)
 		dummy_fence_agent = ("""#!/usr/bin/python
 import sys
 def main():
     for line in sys.stdin.readlines():
         if line.count("monitor") > 0:
             sys.exit(0)
         if line.count("metadata") > 0:
             print '<resource-agent name="fence_dummy_monitor" shortdesc="Dummy Fence agent for testing">'
             print '  <longdesc>dummy description.</longdesc>'
             print '  <vendor-url>http://www.example.com</vendor-url>'
             print '  <parameters>'
             print '    <parameter name="action" unique="0" required="1">'
             print '      <getopt mixed="-o, --action=[action]"/>'
             print '      <content type="string" default="reboot"/>'
             print '      <shortdesc lang="en">Fencing Action</shortdesc>'
             print '    </parameter>'
             print '    <parameter name="port" unique="0" required="0">'
             print '      <getopt mixed="-n, --plug=[id]"/>'
             print '      <content type="string"/>'
             print '      <shortdesc lang="en">Physical plug number or name of virtual machine</shortdesc>'
             print '    </parameter>'
             print '  </parameters>'
             print '  <actions>'
             print '    <action name="on"/>'
             print '    <action name="off"/>'
             print '    <action name="monitor"/>'
             print '    <action name="metadata"/>'
             print '  </actions>'
             print '</resource-agent>'
             sys.exit(0)
     sys.exit(-1)
 if __name__ == "__main__":
     main()
 """)
 
 		os.system("cat <<-END >>/etc/init/lrmd_dummy_daemon.conf\n%s\nEND" % (dummy_upstart_job))
 		os.system("cat <<-END >>/usr/sbin/lrmd_dummy_daemon\n%s\nEND" % (dummy_daemon))
 		os.system("cat <<-END >>/lib/systemd/system/lrmd_dummy_daemon.service\n%s\nEND" % (dummy_service_file))
 		os.system("chmod u+x /usr/sbin/lrmd_dummy_daemon")
 
 		os.system("cat <<-END >>/usr/sbin/fence_dummy_sleep\n%s\nEND" % (dummy_fence_sleep_agent))
 		os.system("chmod 711 /usr/sbin/fence_dummy_sleep")
 
 		os.system("cat <<-END >>/usr/sbin/fence_dummy_monitor\n%s\nEND" % (dummy_fence_agent))
 		os.system("chmod 711 /usr/sbin/fence_dummy_monitor")
 
 		if os.path.exists("%s/cts/LSBDummy" % build_dir):
 			print "Using %s/cts/LSBDummy" % build_dir
 			os.system("cp %s/cts/LSBDummy /etc/init.d/LSBDummy" % build_dir)
 
 			if not os.path.exists("@OCF_RA_DIR@/pacemaker"):
 				os.system("mkdir -p @OCF_RA_DIR@/pacemaker/")
 
 			# Install helper OCF agents
 			for ra in [ "Dummy", "Stateful", "ping" ]:
 				os.system("cp %s/extra/resources/%s @OCF_RA_DIR@/pacemaker/%s" % (build_dir, ra, ra))
 				os.system("chmod a+x @OCF_RA_DIR@/pacemaker/%s" % (ra))
 			else:
 				# Assume it's installed
 				print "Using @datadir@/@PACKAGE@/tests/cts/LSBDummy"
 				os.system("cp @datadir@/@PACKAGE@/tests/cts/LSBDummy /etc/init.d/LSBDummy")
 
 				os.system("chmod a+x /etc/init.d/LSBDummy")
 				os.system("ls -al /etc/init.d/LSBDummy")
 		os.system("mkdir -p @CRM_CORE_DIR@/root")
 
 		if os.path.exists("/bin/systemctl"):
 			os.system("systemctl daemon-reload")
 
 	def cleanup_test_environment(self):
 		if self.need_authkey:
 		    os.system("rm -f /etc/pacemaker/authkey")
 
 		os.system("rm -f /lib/systemd/system/lrmd_dummy_daemon.service")
 		os.system("rm -f /etc/init.d/LSBDummy")
 		os.system("rm -f /usr/sbin/lrmd_dummy_daemon")
 		os.system("rm -f /usr/sbin/fence_dummy_monitor")
 		os.system("rm -f /usr/sbin/fence_dummy_sleep")
 		if os.path.exists("/bin/systemctl"):
                     os.system("systemctl daemon-reload")
 
 	### These are tests that should apply to all resource classes ###
 	def build_generic_tests(self):
 		common_cmds = self.common_cmds
 
 		### register/unregister tests ###
 		for rsc in self.rsc_classes:
 			test = self.new_test("generic_registration_%s" % (rsc), "Simple resource registration test for %s standard" % (rsc))
 			test.add_cmd(common_cmds["%s_reg_line" % (rsc)] + " " + common_cmds["%s_reg_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
 		### start/stop tests  ###
 		for rsc in self.rsc_classes:
 			test = self.new_test("generic_start_stop_%s" % (rsc), "Simple start and stop test for %s standard" % (rsc))
 			test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
 		### monitor cancel test ###
 		for rsc in self.rsc_classes:
 			test = self.new_test("generic_monitor_cancel_%s" % (rsc), "Simple monitor cancel test for %s standard" % (rsc))
 			test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
 			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
 			test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
 			test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this happens the monitor did not actually cancel correctly. ###
 			test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this happens the monitor did not actually cancel correctly. ###
 			test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
+		### monitor duplicate test ###
+		for rsc in self.rsc_classes:
+			test = self.new_test("generic_monitor_duplicate_%s" % (rsc), "Test creation and canceling of duplicate monitors for %s standard" % (rsc))
+			test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
+			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
+
+			# Add the duplicate monitors. 
+			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
+			# verify we still get update events
+			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
+
+			# cancel the monitor, if the duplicate merged with the original, we should no longer see monitor updates
+			test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
+			test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this happens the monitor did not actually cancel correctly. ###
+			test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this happens the monitor did not actually cancel correctly. ###
+			test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
+			test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
 		### stop implies cancel test ###
 		for rsc in self.rsc_classes:
 			test = self.new_test("generic_stop_implies_cancel_%s" % (rsc), "Verify stopping a resource implies cancel of recurring ops for %s standard" % (rsc))
 			test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
 			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
 			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor may not be getting rescheduled ####
 			test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
 			test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this happens the monitor did not actually cancel correctly. ###
 			test.add_expected_fail_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this happens the monitor did not actually cancel correctly. ###
 			test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
 
 	### These are complex tests that involve managing multiple resouces of different types ###
 	def build_multi_rsc_tests(self):
 		common_cmds = self.common_cmds
 		# do not use service and systemd at the same time, it is the same resource.
 
 		### register start monitor stop unregister resources of each type at the same time. ###
 		test = self.new_test("multi_rsc_start_stop_all", "Start, monitor, and stop resources of multiple types and classes")
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_reg_line" % (rsc)]   + " " + common_cmds["%s_reg_event" % (rsc)])
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_start_line" % (rsc)] + " " + common_cmds["%s_start_event" % (rsc)])
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_monitor_line" % (rsc)] + " " + common_cmds["%s_monitor_event" % (rsc)])
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_monitor_event" % (rsc)]) ### If this fails, that means the monitor is not being rescheduled ####
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_cancel_line" % (rsc)] + " " + common_cmds["%s_cancel_event" % (rsc)])
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_stop_line" % (rsc)]  + " " + common_cmds["%s_stop_event" % (rsc)])
 		for rsc in self.rsc_classes:
 			test.add_cmd(common_cmds["%s_unreg_line" % (rsc)] + " " + common_cmds["%s_unreg_event" % (rsc)])
 
 	### These are tests related to how the lrmd handles failures.  ###
 	def build_negative_tests(self):
 
 		### ocf start timeout test  ###
 		test = self.new_test("ocf_start_timeout", "Force start timeout to occur, verify start failure.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" -k \"op_sleep\" -v \"5\" -t 1000 -w")  # -t must be less than self.action_timeout
 		test.add_cmd("-l "
 			"\"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:unknown error op_status:Timed Out\" "+self.action_timeout)
 		test.add_cmd("-c exec -r test_rsc -a stop "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
 		test.add_cmd("-c unregister_rsc -r test_rsc "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 
 		### stonith start timeout test  ###
 		test = self.new_test("stonith_start_timeout", "Force start timeout to occur, verify start failure.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"stonith\" -P \"pacemaker\" -T \"fence_dummy_sleep\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" -t 1000 -w") # -t must be less than self.action_timeout
 		test.add_cmd("-l "
 			"\"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:unknown error op_status:Timed Out\" "+self.action_timeout)
 		test.add_cmd("-c exec -r test_rsc -a stop "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
 		test.add_cmd("-c unregister_rsc -r test_rsc "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### stonith component fail ###
 		common_cmds = self.common_cmds
 		test = self.new_test("stonith_component_fail", "Kill stonith component after lrmd connects")
 		test.add_cmd(common_cmds["stonith_reg_line"]   + " " + common_cmds["stonith_reg_event"])
 		test.add_cmd(common_cmds["stonith_start_line"] + " " + common_cmds["stonith_start_event"])
 
 		test.add_cmd("-c exec -r \"stonith_test_rsc\" -a \"monitor\" -i \"600000\" "
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 
 		test.add_cmd_and_kill("killall -9 -q stonithd lt-stonithd" ,"-l \"NEW_EVENT event_type:exec_complete rsc_id:stonith_test_rsc action:monitor rc:unknown error op_status:error\" -t 15000")
 		test.add_cmd(common_cmds["stonith_unreg_line"] + " " + common_cmds["stonith_unreg_event"])
 
 
 		### monitor fail for ocf resources ###
 		test = self.new_test("monitor_fail_ocf", "Force ocf monitor to fail, verify failure is reported.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"monitor\" -i \"100\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 		test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd_and_kill("rm -f @localstatedir@/run/Dummy-test_rsc.state", "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" -t 6000")
 		test.add_cmd("-c cancel -r \"test_rsc\" -a \"monitor\" -i \"100\" -t \"3000\" "
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
 		test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout)
 		test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### verify notify changes only for monitor operation.  ###
 		test = self.new_test("monitor_changes_only", "Verify when flag is set, only monitor changes are notified.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+" -o "
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"monitor\" -i \"100\" "+self.action_timeout+" -o "
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 		test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd_and_kill("rm -f @localstatedir@/run/Dummy-test_rsc.state", "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" -t 6000")
 		test.add_cmd("-c cancel -r \"test_rsc\" -a \"monitor\" -i \"100\" -t \"3000\" "
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
 		test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout)
 		test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### monitor fail for systemd resource ###
 		if "systemd" in self.rsc_classes:
 			test = self.new_test("monitor_fail_systemd", "Force systemd monitor to fail, verify failure is reported..")
 			test.add_cmd("-c register_rsc -r \"test_rsc\" -C systemd -T lrmd_dummy_daemon "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"monitor\" -i \"100\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 			test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 			test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 			test.add_cmd_and_kill("killall -9 -q lrmd_dummy_daemon", "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" -t 8000")
 			test.add_cmd("-c cancel -r \"test_rsc\" -a \"monitor\" -i \"100\" -t \"3000\" "
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
 			test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout)
 			test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 			test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### monitor fail for upstart resource ###
 		if "upstart" in self.rsc_classes:
 			test = self.new_test("monitor_fail_upstart", "Force upstart monitor to fail, verify failure is reported..")
 			test.add_cmd("-c register_rsc -r \"test_rsc\" -C upstart -T lrmd_dummy_daemon "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"monitor\" -i \"100\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 			test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 			test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 			test.add_cmd_and_kill("killall -9 -q dd", "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" -t 8000")
 			test.add_cmd("-c cancel -r \"test_rsc\" -a \"monitor\" -i \"100\" -t \"3000\" "
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
 			test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:complete\" "+self.action_timeout)
 			test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 			test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Cancel non-existent operation on a resource ###
 		test = self.new_test("cancel_non_existent_op", "Attempt to cancel the wrong monitor operation, verify expected failure")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"monitor\" -i \"100\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 		test.add_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_expected_fail_cmd("-c cancel -r test_rsc -a \"monitor\" -i 1234 -t \"3000\" " ### interval is wrong, should fail
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
 		test.add_expected_fail_cmd("-c cancel -r test_rsc -a stop -i 100 -t \"3000\" " ### action name is wrong, should fail
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:not running op_status:Cancelled\" ")
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Attempt to invoke non-existent rsc id ###
 		test = self.new_test("invoke_non_existent_rsc", "Attempt to perform operations on a non-existent rsc id.")
 		test.add_expected_fail_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:unknown error op_status:complete\" ")
 		test.add_expected_fail_cmd("-c exec -r test_rsc -a stop "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
 		test.add_expected_fail_cmd("-c exec -r test_rsc -a monitor -i 3000 "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 		test.add_expected_fail_cmd("-c cancel -r test_rsc -a start "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Cancelled\" ")
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Register and start a resource that doesn't exist, systemd  ###
 		if "systemd" in self.rsc_classes:
 			test = self.new_test("start_uninstalled_systemd", "Register uninstalled systemd agent, try to start, verify expected failure")
 			test.add_cmd("-c register_rsc -r \"test_rsc\" -C systemd -T this_is_fake1234 "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
 			test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		if "upstart" in self.rsc_classes:
 			test = self.new_test("start_uninstalled_upstart", "Register uninstalled upstart agent, try to start, verify expected failure")
 			test.add_cmd("-c register_rsc -r \"test_rsc\" -C upstart -T this_is_fake1234 "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 			test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
 			test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 				     "-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Register and start a resource that doesn't exist, ocf ###
 		test = self.new_test("start_uninstalled_ocf", "Register uninstalled ocf agent, try to start, verify expected failure.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C ocf -P pacemaker -T this_is_fake1234 "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Register ocf with non-existent provider  ###
 		test = self.new_test("start_ocf_bad_provider", "Register ocf agent with a non-existent provider, verify expected failure.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C ocf -P pancakes -T Dummy "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:not installed op_status:Not installed\" ")
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Register ocf with empty provider field  ###
 		test = self.new_test("start_ocf_no_provider", "Register ocf agent with a no provider, verify expected failure.")
 		test.add_expected_fail_cmd("-c register_rsc -r \"test_rsc\" -C ocf -T Dummy "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_expected_fail_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Error\" ")
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 	### These are tests that target specific cases ###
 	def build_custom_tests(self):
 
 		### verify resource temporary folder is created and used by heartbeat agents.  ###
 		test = self.new_test("rsc_tmp_dir", "Verify creation and use of rsc temporary state directory")
 		test.add_sys_cmd("ls", "-al @CRM_RSCTMP_DIR@")
 		test.add_cmd("-c register_rsc -r test_rsc -P heartbeat -C ocf -T Dummy "
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-c exec -r test_rsc -a start -t 4000")
 		test.add_sys_cmd("ls", "-al @CRM_RSCTMP_DIR@")
 		test.add_sys_cmd("ls", "@CRM_RSCTMP_DIR@/Dummy-test_rsc.state")
 		test.add_cmd("-c exec -r test_rsc -a stop -t 4000")
 		test.add_cmd("-c unregister_rsc -r test_rsc "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### start delay then stop test ###
 		test = self.new_test("start_delay", "Verify start delay works as expected.")
 		test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-c exec -r test_rsc -s 6000 -a start -w -t 6000")
 		test.add_expected_fail_cmd("-l "
 			"\"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 2000")
 		test.add_cmd("-l "
 			"\"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 6000")
 		test.add_cmd("-c exec -r test_rsc -a stop "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
 		test.add_cmd("-c unregister_rsc -r test_rsc "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### start delay, but cancel before it gets a chance to start.  ###
 		test = self.new_test("start_delay_cancel", "Using start_delay, start a rsc, but cancel the start op before execution.")
 		test.add_cmd("-c register_rsc -r test_rsc -P pacemaker -C ocf -T Dummy "
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-c exec -r test_rsc -s 5000 -a start -w -t 4000")
 		test.add_cmd("-c cancel -r test_rsc -a start "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:Cancelled\" ")
 		test.add_expected_fail_cmd("-l "
 			"\"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" -t 5000")
 		test.add_cmd("-c unregister_rsc -r test_rsc "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### Register a bunch of resources, verify we can get info on them ###
 		test = self.new_test("verify_get_rsc_info", "Register multiple resources, verify retrieval of rsc info.")
 		if "systemd" in self.rsc_classes:
 			test.add_cmd("-c register_rsc -r rsc1 -C systemd -T lrmd_dummy_daemon "+self.action_timeout)
 			test.add_cmd("-c get_rsc_info -r rsc1 ")
 			test.add_cmd("-c unregister_rsc -r rsc1 "+self.action_timeout)
 			test.add_expected_fail_cmd("-c get_rsc_info -r rsc1 ")
 
 		if "upstart" in self.rsc_classes:
 			test.add_cmd("-c register_rsc -r rsc1 -C upstart -T lrmd_dummy_daemon "+self.action_timeout)
 			test.add_cmd("-c get_rsc_info -r rsc1 ")
 			test.add_cmd("-c unregister_rsc -r rsc1 "+self.action_timeout)
 			test.add_expected_fail_cmd("-c get_rsc_info -r rsc1 ")
 
 		test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self.action_timeout)
 		test.add_cmd("-c get_rsc_info -r rsc2 ")
 		test.add_cmd("-c unregister_rsc -r rsc2 "+self.action_timeout)
 		test.add_expected_fail_cmd("-c get_rsc_info -r rsc2 ")
 
 		### Register duplicate, verify only one entry exists and can still be removed.
 		test = self.new_test("duplicate_registration", "Register resource multiple times, verify only one entry exists and can be removed.")
 		test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self.action_timeout)
 		test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Dummy")
 		test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Dummy -P pacemaker "+self.action_timeout)
 		test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Dummy")
 		test.add_cmd("-c register_rsc -r rsc2 -C ocf -T Stateful -P pacemaker "+self.action_timeout)
 		test.add_cmd_check_stdout("-c get_rsc_info -r rsc2 ", "id:rsc2 class:ocf provider:pacemaker type:Stateful")
 		test.add_cmd("-c unregister_rsc -r rsc2 "+self.action_timeout)
 		test.add_expected_fail_cmd("-c get_rsc_info -r rsc2 ")
 
 		### verify the option to only send notification to the original client. ###
 		test = self.new_test("notify_orig_client_only", "Verify option to only send notifications to the client originating the action.")
 		test.add_cmd("-c register_rsc -r \"test_rsc\" -C \"ocf\" -P \"pacemaker\" -T \"Dummy\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"start\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:ok op_status:complete\" ")
 		test.add_cmd("-c exec -r \"test_rsc\" -a \"monitor\" -i \"100\" "+self.action_timeout+" -n "
 			"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" ")
 		# this will fail because the monitor notifications should only go to the original caller, which no longer exists.
 		test.add_expected_fail_cmd("-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:monitor rc:ok op_status:complete\" "+self.action_timeout)
 		test.add_cmd("-c cancel -r \"test_rsc\" -a \"monitor\" -i \"100\" -t \"3000\" ")
 		test.add_cmd("-c unregister_rsc -r \"test_rsc\" "+self.action_timeout+
 			"-l \"NEW_EVENT event_type:unregister rsc_id:test_rsc action:none rc:ok op_status:complete\" ")
 
 		### get metadata ###
 		test = self.new_test("get_ocf_metadata", "Retrieve metadata for a resource")
 		test.add_cmd_check_stdout("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"Dummy\""
 			,"resource-agent name=\"Dummy\"")
 		test.add_cmd("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"Stateful\"")
 		test.add_expected_fail_cmd("-c metadata -P \"pacemaker\" -T \"Stateful\"")
 		test.add_expected_fail_cmd("-c metadata -C \"ocf\" -P \"pacemaker\" -T \"fake_agent\"")
 
 		### get metadata ###
 		test = self.new_test("get_lsb_metadata", "Retrieve metadata for a resource")
 		test.add_cmd_check_stdout("-c metadata -C \"lsb\" -T \"LSBDummy\""
 			,"resource-agent name='LSBDummy'")
 
 		### get stonith metadata ###
 		test = self.new_test("get_stonith_metadata", "Retrieve stonith metadata for a resource")
 		test.add_cmd_check_stdout("-c metadata -C \"stonith\" -P \"pacemaker\" -T \"fence_dummy_monitor\"",
 			"resource-agent name=\"fence_dummy_monitor\"")
 
 		### get metadata ###
 		if "systemd" in self.rsc_classes:
 			test = self.new_test("get_systemd_metadata", "Retrieve metadata for a resource")
 			test.add_cmd_check_stdout("-c metadata -C \"systemd\" -T \"lrmd_dummy_daemon\""
 				,"resource-agent name=\"lrmd_dummy_daemon\"")
 
 		### get metadata ###
 		if "upstart" in self.rsc_classes:
 			test = self.new_test("get_upstart_metadata", "Retrieve metadata for a resource")
 			test.add_cmd_check_stdout("-c metadata -C \"upstart\" -T \"lrmd_dummy_daemon\""
 				,"resource-agent name=\"lrmd_dummy_daemon\"")
 
 		### get ocf providers  ###
 		test = self.new_test("list_ocf_providers", "Retrieve list of available resource providers, verifies pacemaker is a provider.")
 		test.add_cmd_check_stdout("-c list_ocf_providers ", "pacemaker")
 		test.add_cmd_check_stdout("-c list_ocf_providers -T ping", "pacemaker")
 
 		### Verify agents only exist in their lists ###
 		test = self.new_test("verify_agent_lists", "Verify the agent lists contain the right data.")
 		test.add_cmd_check_stdout("-c list_agents ", "Stateful")                                  ### ocf ###
 		test.add_cmd_check_stdout("-c list_agents -C ocf", "Stateful")
 		test.add_cmd_check_stdout("-c list_agents -C lsb", "", "Stateful")                        ### should not exist
 		test.add_cmd_check_stdout("-c list_agents -C service", "", "Stateful")                    ### should not exist
 		test.add_cmd_check_stdout("-c list_agents ", "LSBDummy")                                  ### init.d ###
 		test.add_cmd_check_stdout("-c list_agents -C lsb", "LSBDummy")
 		test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
 		test.add_cmd_check_stdout("-c list_agents -C ocf", "", "lrmd_dummy_daemon")               ### should not exist
 
 		test.add_cmd_check_stdout("-c list_agents -C ocf", "", "lrmd_dummy_daemon")               ### should not exist
 		test.add_cmd_check_stdout("-c list_agents -C lsb", "", "fence_dummy_monitor")             ### should not exist
 		test.add_cmd_check_stdout("-c list_agents -C service", "", "fence_dummy_monitor")         ### should not exist
 		test.add_cmd_check_stdout("-c list_agents -C ocf", "", "fence_dummy_monitor")             ### should not exist
 
 		if "systemd" in self.rsc_classes:
 			test.add_cmd_check_stdout("-c list_agents ", "lrmd_dummy_daemon")                 ### systemd ###
 			test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
 			test.add_cmd_check_stdout("-c list_agents -C systemd", "", "Stateful")            ### should not exist
 			test.add_cmd_check_stdout("-c list_agents -C systemd", "lrmd_dummy_daemon")
 			test.add_cmd_check_stdout("-c list_agents -C systemd", "", "fence_dummy_monitor") ### should not exist
 
 		if "upstart" in self.rsc_classes:
 			test.add_cmd_check_stdout("-c list_agents ", "lrmd_dummy_daemon")                 ### upstart ###
 			test.add_cmd_check_stdout("-c list_agents -C service", "LSBDummy")
 			test.add_cmd_check_stdout("-c list_agents -C upstart", "", "Stateful")            ### should not exist
 			test.add_cmd_check_stdout("-c list_agents -C upstart", "lrmd_dummy_daemon")
 			test.add_cmd_check_stdout("-c list_agents -C upstart", "", "fence_dummy_monitor") ### should not exist
 
 		if "stonith" in self.rsc_classes:
 			test.add_cmd_check_stdout("-c list_agents -C stonith", "fence_dummy_monitor")     ### stonith ###
 			test.add_cmd_check_stdout("-c list_agents -C stonith", "", "lrmd_dummy_daemon")   ### should not exist
 			test.add_cmd_check_stdout("-c list_agents -C stonith", "", "Stateful")            ### should not exist
 			test.add_cmd_check_stdout("-c list_agents ", "fence_dummy_monitor")
 
 	def print_list(self):
 		print "\n==== %d TESTS FOUND ====" % (len(self.tests))
 		print "%35s - %s" % ("TEST NAME", "TEST DESCRIPTION")
 		print "%35s - %s" % ("--------------------", "--------------------")
 		for test in self.tests:
 			print "%35s - %s" % (test.name, test.description)
 		print "==== END OF LIST ====\n"
 
 	def run_single(self, name):
 		for test in self.tests:
 			if test.name == name:
 				test.run()
 				break;
 
 	def run_tests_matching(self, pattern):
 		for test in self.tests:
 			if test.name.count(pattern) != 0:
 				test.run()
 
 	def run_tests(self):
 		for test in self.tests:
 			test.run()
 
 	def exit(self):
 		for test in self.tests:
 			if test.executed == 0:
 				continue
 
 			if test.get_exitcode() != 0:
 				sys.exit(-1)
 
 		sys.exit(0);
 
 	def print_results(self):
 		failures = 0;
 		success = 0;
 		print "\n\n======= FINAL RESULTS =========="
 		print "\n--- FAILURE RESULTS:"
 		for test in self.tests:
 			if test.executed == 0:
 				continue
 
 			if test.get_exitcode() != 0:
 				failures = failures + 1
 				test.print_result("    ")
 			else:
 				success = success + 1
 
 		if failures == 0:
 			print "    None"
 
 		print "\n--- TOTALS\n    Pass:%d\n    Fail:%d\n" % (success, failures)
 
 class TestOptions:
 	def __init__(self):
 		self.options = {}
 		self.options['list-tests'] = 0
 		self.options['run-all'] = 1
 		self.options['run-only'] = ""
 		self.options['run-only-pattern'] = ""
 		self.options['verbose'] = 0
 		self.options['invalid-arg'] = ""
 		self.options['show-usage'] = 0
 		self.options['pacemaker-remote'] = 0
 
 	def build_options(self, argv):
 		args = argv[1:]
 		skip = 0
 		for i in range(0, len(args)):
 			if skip:
 				skip = 0
 				continue
 			elif args[i] == "-h" or args[i] == "--help":
 				self.options['show-usage'] = 1
 			elif args[i] == "-l" or args[i] == "--list-tests":
 				self.options['list-tests'] = 1
 			elif args[i] == "-V" or args[i] == "--verbose":
 				self.options['verbose'] = 1
 			elif args[i] == "-R" or args[i] == "--pacemaker-remote":
 				self.options['pacemaker-remote'] = 1
 			elif args[i] == "-r" or args[i] == "--run-only":
 				self.options['run-only'] = args[i+1]
 				skip = 1
 			elif args[i] == "-p" or args[i] == "--run-only-pattern":
 				self.options['run-only-pattern'] = args[i+1]
 				skip = 1
 
 	def show_usage(self):
 		print "usage: " + sys.argv[0] + " [options]"
 		print "If no options are provided, all tests will run"
 		print "Options:"
 		print "\t [--help | -h]                        Show usage"
 		print "\t [--list-tests | -l]                  Print out all registered tests."
 		print "\t [--run-only | -r 'testname']         Run a specific test"
 		print "\t [--verbose | -V]                     Verbose output"
 		print "\t [--pacemaker-remote | -R             Test pacemaker-remote binary instead of lrmd."
 		print "\t [--run-only-pattern | -p 'string']   Run only tests containing the string value"
 		print "\n\tExample: Run only the test 'start_top'"
 		print "\t\t python ./regression.py --run-only start_stop"
 		print "\n\tExample: Run only the tests with the string 'systemd' present in them"
 		print "\t\t python ./regression.py --run-only-pattern systemd"
 
 
 def main(argv):
 	o = TestOptions()
 	o.build_options(argv)
 
 	tests = Tests(o.options['verbose'], o.options['pacemaker-remote'])
 
 	tests.build_generic_tests()
 	tests.build_multi_rsc_tests()
 	tests.build_negative_tests()
 	tests.build_custom_tests()
 
 	tests.setup_test_environment()
 
 	print "Starting ..."
 
 	if o.options['list-tests']:
 		tests.print_list()
 	elif o.options['show-usage']:
 		o.show_usage()
 	elif o.options['run-only-pattern'] != "":
 		tests.run_tests_matching(o.options['run-only-pattern'])
 		tests.print_results()
 	elif o.options['run-only'] != "":
 		tests.run_single(o.options['run-only'])
 		tests.print_results()
 	else:
 		tests.run_tests()
 		tests.print_results()
 
 	tests.cleanup_test_environment()
 	tests.exit()
 
 if __name__=="__main__":
 	main(sys.argv)
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index 870c3d47be..f9bddcacc5 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,2622 +1,2662 @@
 /*
  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
  * License as published by the Free Software Foundation; either
  * version 2 of the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 
 #include <crm/crm.h>
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 #include <sys/utsname.h>
 
 #include <crm/msg_xml.h>
 #include <crm/services.h>
 #include <crm/lrmd.h>
 #include <crm/common/util.h>
 #include <crm/common/xml.h>
 #include <crm/common/ipc.h>
 #include <crm/common/mainloop.h>
 
 #include <crm/cib/internal.h>
 #include <crm/pengine/status.h>
 #include <../lib/pengine/unpack.h>
 #include <../pengine/pengine.h>
 #include <crm/stonith-ng.h>
 
 /* GMainLoop *mainloop = NULL; */
 
 void wait_for_refresh(int offset, const char *prefix, int msec);
 void clean_up(int rc);
 void crm_diff_update(const char *event, xmlNode * msg);
 gboolean mon_refresh_display(gpointer user_data);
 int cib_connect(gboolean full);
 void mon_st_callback(stonith_t * st, stonith_event_t * e);
 
 char *xml_file = NULL;
 char *as_html_file = NULL;
 int as_xml = 0;
 char *pid_file = NULL;
 char *snmp_target = NULL;
 char *snmp_community = NULL;
 
 gboolean as_console = TRUE;;
 gboolean simple_status = FALSE;
 gboolean group_by_node = FALSE;
 gboolean inactive_resources = FALSE;
 gboolean web_cgi = FALSE;
 int reconnect_msec = 5000;
 gboolean daemonize = FALSE;
 GMainLoop *mainloop = NULL;
 guint timer_id = 0;
 GList *attr_list = NULL;
 
 const char *crm_mail_host = NULL;
 const char *crm_mail_prefix = NULL;
 const char *crm_mail_from = NULL;
 const char *crm_mail_to = NULL;
 const char *external_agent = NULL;
 const char *external_recipient = NULL;
 
 cib_t *cib = NULL;
 stonith_t *st = NULL;
 xmlNode *current_cib = NULL;
 
 gboolean one_shot = FALSE;
 gboolean has_warnings = FALSE;
 gboolean print_failcount = FALSE;
 gboolean print_operations = FALSE;
 gboolean print_timing = FALSE;
 gboolean print_nodes_attr = FALSE;
 gboolean print_last_updated = TRUE;
 gboolean print_last_change = TRUE;
 gboolean print_tickets = FALSE;
 gboolean watch_fencing = FALSE;
 gboolean hide_headers = FALSE;
 gboolean print_brief = FALSE;
 gboolean print_pending = FALSE;
 gboolean print_clone_detail = FALSE;
 
 /* FIXME allow, detect, and correctly interpret glob pattern or regex? */
 const char *print_neg_location_prefix;
 const char *print_neg_location_prefix_toggle;
 
 #define FILTER_STR {"shutdown", "terminate", "standby", "fail-count",	\
 	    "last-failure", "probe_complete", "#id", "#uname",		\
 	    "#is_dc", "#kind", NULL}
 
 gboolean log_diffs = FALSE;
 gboolean log_updates = FALSE;
 
 long last_refresh = 0;
 crm_trigger_t *refresh_trigger = NULL;
 
 /*
  * 1.3.6.1.4.1.32723 has been assigned to the project by IANA
  * http://www.iana.org/assignments/enterprise-numbers
  */
 #define PACEMAKER_PREFIX "1.3.6.1.4.1.32723"
 #define PACEMAKER_TRAP_PREFIX PACEMAKER_PREFIX ".1"
 
 #define snmp_crm_trap_oid   PACEMAKER_TRAP_PREFIX
 #define snmp_crm_oid_node   PACEMAKER_TRAP_PREFIX ".1"
 #define snmp_crm_oid_rsc    PACEMAKER_TRAP_PREFIX ".2"
 #define snmp_crm_oid_task   PACEMAKER_TRAP_PREFIX ".3"
 #define snmp_crm_oid_desc   PACEMAKER_TRAP_PREFIX ".4"
 #define snmp_crm_oid_status PACEMAKER_TRAP_PREFIX ".5"
 #define snmp_crm_oid_rc     PACEMAKER_TRAP_PREFIX ".6"
 #define snmp_crm_oid_trc    PACEMAKER_TRAP_PREFIX ".7"
 
 #if CURSES_ENABLED
 #  define print_dot() if(as_console) {		\
 	printw(".");				\
 	clrtoeol();				\
 	refresh();				\
     } else {					\
 	fprintf(stdout, ".");			\
     }
 #else
 #  define print_dot() fprintf(stdout, ".");
 #endif
 
 #if CURSES_ENABLED
 #  define print_as(fmt, args...) if(as_console) {	\
 	printw(fmt, ##args);				\
 	clrtoeol();					\
 	refresh();					\
     } else {						\
 	fprintf(stdout, fmt, ##args);			\
     }
 #else
 #  define print_as(fmt, args...) fprintf(stdout, fmt, ##args);
 #endif
 
 static void
 blank_screen(void)
 {
 #if CURSES_ENABLED
     int lpc = 0;
 
     for (lpc = 0; lpc < LINES; lpc++) {
         move(lpc, 0);
         clrtoeol();
     }
     move(0, 0);
     refresh();
 #endif
 }
 
 static gboolean
 mon_timer_popped(gpointer data)
 {
     int rc = pcmk_ok;
 
 #if CURSES_ENABLED
     if(as_console) {
         clear();
         refresh();
     }
 #endif
 
     if (timer_id > 0) {
         g_source_remove(timer_id);
     }
 
     print_as("Reconnecting...\n");
     rc = cib_connect(TRUE);
 
     if (rc != pcmk_ok) {
         timer_id = g_timeout_add(reconnect_msec, mon_timer_popped, NULL);
     }
     return FALSE;
 }
 
 static void
 mon_cib_connection_destroy(gpointer user_data)
 {
     print_as("Connection to the CIB terminated\n");
     if (cib) {
         cib->cmds->signoff(cib);
         timer_id = g_timeout_add(reconnect_msec, mon_timer_popped, NULL);
     }
     return;
 }
 
 /*
  * Mainloop signal handler.
  */
 static void
 mon_shutdown(int nsig)
 {
     clean_up(EX_OK);
 }
 
 #if ON_DARWIN
 #  define sighandler_t sig_t
 #endif
 
 #if CURSES_ENABLED
 #  ifndef HAVE_SIGHANDLER_T
 typedef void (*sighandler_t) (int);
 #  endif
 static sighandler_t ncurses_winch_handler;
 static void
 mon_winresize(int nsig)
 {
     static int not_done;
     int lines = 0, cols = 0;
 
     if (!not_done++) {
         if (ncurses_winch_handler)
             /* the original ncurses WINCH signal handler does the
              * magic of retrieving the new window size;
              * otherwise, we'd have to use ioctl or tgetent */
             (*ncurses_winch_handler) (SIGWINCH);
         getmaxyx(stdscr, lines, cols);
         resizeterm(lines, cols);
         mainloop_set_trigger(refresh_trigger);
     }
     not_done--;
 }
 #endif
 
 int
 cib_connect(gboolean full)
 {
     int rc = pcmk_ok;
     static gboolean need_pass = TRUE;
 
     CRM_CHECK(cib != NULL, return -EINVAL);
 
     if (getenv("CIB_passwd") != NULL) {
         need_pass = FALSE;
     }
 
     if (watch_fencing && st == NULL) {
         st = stonith_api_new();
     }
 
     if (watch_fencing && st->state == stonith_disconnected) {
         crm_trace("Connecting to stonith");
         rc = st->cmds->connect(st, crm_system_name, NULL);
         if (rc == pcmk_ok) {
             crm_trace("Setting up stonith callbacks");
             st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback);
         }
     }
 
     if (cib->state != cib_connected_query && cib->state != cib_connected_command) {
         crm_trace("Connecting to the CIB");
         if (as_console && need_pass && cib->variant == cib_remote) {
             need_pass = FALSE;
             print_as("Password:");
         }
 
         rc = cib->cmds->signon(cib, crm_system_name, cib_query);
 
         if (rc != pcmk_ok) {
             return rc;
         }
 
         rc = cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
         if (rc == pcmk_ok) {
             mon_refresh_display(NULL);
         }
 
         if (rc == pcmk_ok && full) {
             if (rc == pcmk_ok) {
                 rc = cib->cmds->set_connection_dnotify(cib, mon_cib_connection_destroy);
                 if (rc == -EPROTONOSUPPORT) {
                     print_as
                         ("Notification setup not supported, won't be able to reconnect after failure");
                     if (as_console) {
                         sleep(2);
                     }
                     rc = pcmk_ok;
                 }
 
             }
 
             if (rc == pcmk_ok) {
                 cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
                 rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
             }
 
             if (rc != pcmk_ok) {
                 print_as("Notification setup failed, could not monitor CIB actions");
                 if (as_console) {
                     sleep(2);
                 }
                 clean_up(-rc);
             }
         }
     }
     return rc;
 }
 
 /* *INDENT-OFF* */
 static struct crm_option long_options[] = {
     /* Top-level Options */
     {"help",           0, 0, '?', "\tThis text"},
     {"version",        0, 0, '$', "\tVersion information"  },
     {"verbose",        0, 0, 'V', "\tIncrease debug output"},
     {"quiet",          0, 0, 'Q', "\tDisplay only essential output" },
 
     {"-spacer-",	1, 0, '-', "\nModes:"},
     {"as-html",        1, 0, 'h', "\tWrite cluster status to the named html file"},
     {"as-xml",         0, 0, 'X', "\t\tWrite cluster status as xml to stdout. This will enable one-shot mode."},
     {"web-cgi",        0, 0, 'w', "\t\tWeb mode with output suitable for cgi"},
     {"simple-status",  0, 0, 's', "\tDisplay the cluster status once as a simple one line output (suitable for nagios)"},
     {"snmp-traps",     1, 0, 'S', "\tSend SNMP traps to this station", !ENABLE_SNMP},
     {"snmp-community", 1, 0, 'C', "Specify community for SNMP traps(default is NULL)", !ENABLE_SNMP},
     {"mail-to",        1, 0, 'T', "\tSend Mail alerts to this user.  See also --mail-from, --mail-host, --mail-prefix", !ENABLE_ESMTP},
 
     {"-spacer-",	1, 0, '-', "\nDisplay Options:"},
     {"group-by-node",  0, 0, 'n', "\tGroup resources by node"     },
     {"inactive",       0, 0, 'r', "\t\tDisplay inactive resources"  },
     {"failcounts",     0, 0, 'f', "\tDisplay resource fail counts"},
     {"operations",     0, 0, 'o', "\tDisplay resource operation history" },
     {"timing-details", 0, 0, 't', "\tDisplay resource operation history with timing details" },
     {"tickets",        0, 0, 'c', "\t\tDisplay cluster tickets"},
     {"watch-fencing",  0, 0, 'W', "\tListen for fencing events. For use with --external-agent, --mail-to and/or --snmp-traps where supported"},
     {"neg-locations",  2, 0, 'L', "Display negative location constraints [optionally filtered by id prefix]"},
     {"show-node-attributes", 0, 0, 'A', "Display node attributes" },
     {"hide-headers",   0, 0, 'D', "\tHide all headers" },
     {"show-detail",    0, 0, 'R', "\tShow more details of cloned resources" },
     {"brief",          0, 0, 'b', "\t\tBrief output" },
     {"pending",        0, 0, 'j', "\t\tDisplay pending state if 'record-pending' is enabled" },
 
     {"-spacer-",	1, 0, '-', "\nAdditional Options:"},
     {"interval",       1, 0, 'i', "\tUpdate frequency in seconds" },
     {"one-shot",       0, 0, '1', "\t\tDisplay the cluster status once on the console and exit"},
     {"disable-ncurses",0, 0, 'N', "\tDisable the use of ncurses", !CURSES_ENABLED},
     {"daemonize",      0, 0, 'd', "\tRun in the background as a daemon"},
     {"pid-file",       1, 0, 'p', "\t(Advanced) Daemon pid file location"},
     {"mail-from",      1, 0, 'F', "\tMail alerts should come from the named user", !ENABLE_ESMTP},
     {"mail-host",      1, 0, 'H', "\tMail alerts should be sent via the named host", !ENABLE_ESMTP},
     {"mail-prefix",    1, 0, 'P', "Subjects for mail alerts should start with this string", !ENABLE_ESMTP},
     {"external-agent",    1, 0, 'E', "A program to run when resource operations take place."},
     {"external-recipient",1, 0, 'e', "A recipient for your program (assuming you want the program to send something to someone)."},
 
 
     {"xml-file",       1, 0, 'x', NULL, 1},
 
     {"-spacer-",	1, 0, '-', "\nExamples:", pcmk_option_paragraph},
     {"-spacer-",	1, 0, '-', "Display the cluster status on the console with updates as they occur:", pcmk_option_paragraph},
     {"-spacer-",	1, 0, '-', " crm_mon", pcmk_option_example},
     {"-spacer-",	1, 0, '-', "Display the cluster status on the console just once then exit:", pcmk_option_paragraph},
     {"-spacer-",	1, 0, '-', " crm_mon -1", pcmk_option_example},
     {"-spacer-",	1, 0, '-', "Display your cluster status, group resources by node, and include inactive resources in the list:", pcmk_option_paragraph},
     {"-spacer-",	1, 0, '-', " crm_mon --group-by-node --inactive", pcmk_option_example},
     {"-spacer-",	1, 0, '-', "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:", pcmk_option_paragraph},
     {"-spacer-",	1, 0, '-', " crm_mon --daemonize --as-html /path/to/docroot/filename.html", pcmk_option_example},
     {"-spacer-",	1, 0, '-', "Start crm_mon and export the current cluster status as xml to stdout, then exit.:", pcmk_option_paragraph},
     {"-spacer-",	1, 0, '-', " crm_mon --as-xml", pcmk_option_example},
     {"-spacer-",	1, 0, '-', "Start crm_mon as a background daemon and have it send email alerts:", pcmk_option_paragraph|!ENABLE_ESMTP},
     {"-spacer-",	1, 0, '-', " crm_mon --daemonize --mail-to user@example.com --mail-host mail.example.com", pcmk_option_example|!ENABLE_ESMTP},
     {"-spacer-",	1, 0, '-', "Start crm_mon as a background daemon and have it send SNMP alerts:", pcmk_option_paragraph|!ENABLE_SNMP},
     {"-spacer-",	1, 0, '-', " crm_mon --daemonize --snmp-traps snmptrapd.example.com", pcmk_option_example|!ENABLE_SNMP},
 
     {NULL, 0, 0, 0}
 };
 /* *INDENT-ON* */
 
 #if CURSES_ENABLED
 static const char *
 get_option_desc(char c)
 {
     int lpc;
 
     for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
 
         if (long_options[lpc].name[0] == '-')
             continue;
 
         if (long_options[lpc].val == c) {
             const char * tab = NULL;
             tab = strrchr(long_options[lpc].desc, '\t');
             return tab ? ++tab : long_options[lpc].desc;
         }
     }
 
     return NULL;
 }
 
 static gboolean
 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer unused)
 {
     int c;
     gboolean config_mode = FALSE;
 
     while (1) {
 
         /* Get user input */
         c = getchar();
 
         switch (c) {
             case 'c':
                 print_tickets = ! print_tickets;
                 break;
             case 'f':
                 print_failcount = ! print_failcount;
                 break;
             case 'n':
                 group_by_node = ! group_by_node;
                 break;
             case 'o':
                 print_operations = ! print_operations;
                 break;
             case 'r':
                 inactive_resources = ! inactive_resources;
                 break;
             case 'R':
                 print_clone_detail = ! print_clone_detail;
                 break;
             case 't':
                 print_timing = ! print_timing;
                 if (print_timing)
                     print_operations = TRUE;
                 break;
             case 'A':
                 print_nodes_attr = ! print_nodes_attr;
                 break;
             case 'L':
                 if (print_neg_location_prefix) {
                     /* toggle off */
                     print_neg_location_prefix_toggle = print_neg_location_prefix;
                     print_neg_location_prefix = NULL;
                 } else if (print_neg_location_prefix_toggle) {
                     /* toggle on */
                     print_neg_location_prefix = print_neg_location_prefix_toggle;
                     print_neg_location_prefix_toggle = NULL;
                 } else {
                     /* toggled on for the first time at runtime */
                     print_neg_location_prefix = "";
                 }
                 break;
             case 'D':
                 hide_headers = ! hide_headers;
                 break;
             case 'b':
                 print_brief = ! print_brief;
                 break;
             case 'j':
                 print_pending = ! print_pending;
                 break;
             case '?':
                 config_mode = TRUE;
                 break;
             default:
                 goto refresh;
         }
 
         if (!config_mode)
             goto refresh;
 
         blank_screen();
 
         print_as("Display option change mode\n");
         print_as("\n");
         print_as("%c c: \t%s\n", print_tickets ? '*': ' ', get_option_desc('c'));
         print_as("%c f: \t%s\n", print_failcount ? '*': ' ', get_option_desc('f'));
         print_as("%c n: \t%s\n", group_by_node ? '*': ' ', get_option_desc('n'));
         print_as("%c o: \t%s\n", print_operations ? '*': ' ', get_option_desc('o'));
         print_as("%c r: \t%s\n", inactive_resources ? '*': ' ', get_option_desc('r'));
         print_as("%c t: \t%s\n", print_timing ? '*': ' ', get_option_desc('t'));
         print_as("%c A: \t%s\n", print_nodes_attr ? '*': ' ', get_option_desc('A'));
         print_as("%c L: \t%s\n", print_neg_location_prefix ? '*': ' ', get_option_desc('L'));
         print_as("%c D: \t%s\n", hide_headers ? '*': ' ', get_option_desc('D'));
         print_as("%c R: \t%s\n", print_clone_detail ? '*': ' ', get_option_desc('R'));
         print_as("%c b: \t%s\n", print_brief ? '*': ' ', get_option_desc('b'));
         print_as("%c j: \t%s\n", print_pending ? '*': ' ', get_option_desc('j'));
         print_as("\n");
         print_as("Toggle fields via field letter, type any other key to return");
     }
 
 refresh:
     mon_refresh_display(NULL);
     return TRUE;
 }
 #endif
 
 int
 main(int argc, char **argv)
 {
     int flag;
     int argerr = 0;
     int exit_code = 0;
     int option_index = 0;
 
     pid_file = strdup("/tmp/ClusterMon.pid");
     crm_log_cli_init("crm_mon");
     crm_set_options(NULL, "mode [options]", long_options,
                     "Provides a summary of cluster's current state."
                     "\n\nOutputs varying levels of detail in a number of different formats.\n");
 
 #if !defined (ON_DARWIN) && !defined (ON_BSD)
     /* prevent zombies */
     signal(SIGCLD, SIG_IGN);
 #endif
 
     if (strcmp(crm_system_name, "crm_mon.cgi") == 0) {
         web_cgi = TRUE;
         one_shot = TRUE;
     }
 
     while (1) {
         flag = crm_get_option(argc, argv, &option_index);
         if (flag == -1)
             break;
 
         switch (flag) {
             case 'V':
                 crm_bump_log_level(argc, argv);
                 break;
             case 'Q':
                 print_last_updated = FALSE;
                 print_last_change = FALSE;
                 break;
             case 'i':
                 reconnect_msec = crm_get_msec(optarg);
                 break;
             case 'n':
                 group_by_node = TRUE;
                 break;
             case 'r':
                 inactive_resources = TRUE;
                 break;
             case 'W':
                 watch_fencing = TRUE;
                 break;
             case 'd':
                 daemonize = TRUE;
                 break;
             case 't':
                 print_timing = TRUE;
                 print_operations = TRUE;
                 break;
             case 'o':
                 print_operations = TRUE;
                 break;
             case 'f':
                 print_failcount = TRUE;
                 break;
             case 'A':
                 print_nodes_attr = TRUE;
                 break;
             case 'L':
                 print_neg_location_prefix = optarg ?: "";
                 break;
             case 'D':
                 hide_headers = TRUE;
                 break;
             case 'b':
                 print_brief = TRUE;
                 break;
             case 'j':
                 print_pending = TRUE;
                 break;
             case 'R':
                 print_clone_detail = TRUE;
                 break;
             case 'c':
                 print_tickets = TRUE;
                 break;
             case 'p':
                 free(pid_file);
                 pid_file = strdup(optarg);
                 break;
             case 'x':
                 xml_file = strdup(optarg);
                 one_shot = TRUE;
                 break;
             case 'h':
                 as_html_file = strdup(optarg);
                 umask(S_IWGRP | S_IWOTH);
                 break;
             case 'X':
                 as_xml = TRUE;
                 one_shot = TRUE;
                 break;
             case 'w':
                 web_cgi = TRUE;
                 one_shot = TRUE;
                 break;
             case 's':
                 simple_status = TRUE;
                 one_shot = TRUE;
                 break;
             case 'S':
                 snmp_target = optarg;
                 break;
             case 'T':
                 crm_mail_to = optarg;
                 break;
             case 'F':
                 crm_mail_from = optarg;
                 break;
             case 'H':
                 crm_mail_host = optarg;
                 break;
             case 'P':
                 crm_mail_prefix = optarg;
                 break;
             case 'E':
                 external_agent = optarg;
                 break;
             case 'e':
                 external_recipient = optarg;
                 break;
             case '1':
                 one_shot = TRUE;
                 break;
             case 'N':
                 as_console = FALSE;
                 break;
             case 'C':
                 snmp_community = optarg;
                 break;
             case '$':
             case '?':
                 crm_help(flag, EX_OK);
                 break;
             default:
                 printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag);
                 ++argerr;
                 break;
         }
     }
 
     if (optind < argc) {
         printf("non-option ARGV-elements: ");
         while (optind < argc)
             printf("%s ", argv[optind++]);
         printf("\n");
     }
     if (argerr) {
         crm_help('?', EX_USAGE);
     }
 
     if (one_shot) {
         as_console = FALSE;
 
     } else if (daemonize) {
         as_console = FALSE;
         crm_enable_stderr(FALSE);
 
         if (!as_html_file && !snmp_target && !crm_mail_to && !external_agent && !as_xml) {
             printf
                 ("Looks like you forgot to specify one or more of: --as-html, --as-xml, --mail-to, --snmp-target, --external-agent\n");
             crm_help('?', EX_USAGE);
         }
 
         crm_make_daemon(crm_system_name, TRUE, pid_file);
 
     } else if (as_console) {
 #if CURSES_ENABLED
         initscr();
         cbreak();
         noecho();
         crm_enable_stderr(FALSE);
 #else
         one_shot = TRUE;
         as_console = FALSE;
         printf("Defaulting to one-shot mode\n");
         printf("You need to have curses available at compile time to enable console mode\n");
 #endif
     }
 
     crm_info("Starting %s", crm_system_name);
     if (xml_file != NULL) {
         current_cib = filename2xml(xml_file);
         mon_refresh_display(NULL);
         return exit_code;
     }
 
     if (current_cib == NULL) {
         cib = cib_new();
 
         do {
             if (!one_shot) {
                 print_as("Attempting connection to the cluster...\n");
             }
             exit_code = cib_connect(!one_shot);
 
             if (one_shot) {
                 break;
 
             } else if (exit_code != pcmk_ok) {
                 sleep(reconnect_msec / 1000);
 #if CURSES_ENABLED
                 if(as_console) {
                     clear();
                     refresh();
                 }
 #endif
             }
 
         } while (exit_code == -ENOTCONN);
 
         if (exit_code != pcmk_ok) {
             print_as("\nConnection to cluster failed: %s\n", pcmk_strerror(exit_code));
             if (as_console) {
                 sleep(2);
             }
             clean_up(-exit_code);
         }
     }
 
     if (one_shot) {
         return exit_code;
     }
 
     mainloop = g_main_new(FALSE);
 
     mainloop_add_signal(SIGTERM, mon_shutdown);
     mainloop_add_signal(SIGINT, mon_shutdown);
 #if CURSES_ENABLED
     if (as_console) {
         ncurses_winch_handler = signal(SIGWINCH, mon_winresize);
         if (ncurses_winch_handler == SIG_DFL ||
             ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
             ncurses_winch_handler = NULL;
         g_io_add_watch(g_io_channel_unix_new(STDIN_FILENO), G_IO_IN, detect_user_input, NULL);
     }
 #endif
     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
 
     g_main_run(mainloop);
     g_main_destroy(mainloop);
 
     crm_info("Exiting %s", crm_system_name);
 
     clean_up(0);
     return 0;                   /* never reached */
 }
 
 #define mon_warn(fmt...) do {			\
 	if (!has_warnings) {			\
 	    print_as("Warning:");		\
 	} else {				\
 	    print_as(",");			\
 	}					\
 	print_as(fmt);				\
 	has_warnings = TRUE;			\
     } while(0)
 
 static int
 count_resources(pe_working_set_t * data_set, resource_t * rsc)
 {
     int count = 0;
     GListPtr gIter = NULL;
 
     if (rsc == NULL) {
         gIter = data_set->resources;
     } else if (rsc->children) {
         gIter = rsc->children;
     } else {
         return is_not_set(rsc->flags, pe_rsc_orphan);
     }
 
     for (; gIter != NULL; gIter = gIter->next) {
         count += count_resources(data_set, gIter->data);
     }
     return count;
 }
 
 static int
 print_simple_status(pe_working_set_t * data_set)
 {
     node_t *dc = NULL;
     GListPtr gIter = NULL;
     int nodes_online = 0;
     int nodes_standby = 0;
     int nodes_maintenance = 0;
 
     dc = data_set->dc_node;
 
     if (dc == NULL) {
         mon_warn("No DC ");
     }
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
 
         if (node->details->standby && node->details->online) {
             nodes_standby++;
         } else if (node->details->maintenance && node->details->online) {
             nodes_maintenance++;
         } else if (node->details->online) {
             nodes_online++;
         } else {
             mon_warn("offline node: %s", node->details->uname);
         }
     }
 
     if (!has_warnings) {
         print_as("Ok: %d nodes online", nodes_online);
         if (nodes_standby > 0) {
             print_as(", %d standby nodes", nodes_standby);
         }
         if (nodes_maintenance > 0) {
             print_as(", %d maintenance nodes", nodes_maintenance);
         }
         print_as(", %d resources configured", count_resources(data_set, NULL));
     }
 
     print_as("\n");
     return 0;
 }
 
 static void
 print_date(time_t time)
 {
     int lpc = 0;
     char date_str[26];
 
     asctime_r(localtime(&time), date_str);
     for (; lpc < 26; lpc++) {
         if (date_str[lpc] == '\n') {
             date_str[lpc] = 0;
         }
     }
     print_as("'%s'", date_str);
 }
 
 #include <crm/pengine/internal.h>
 static void
 print_rsc_summary(pe_working_set_t * data_set, node_t * node, resource_t * rsc, gboolean all)
 {
     gboolean printed = FALSE;
 
     time_t last_failure = 0;
     int failcount = get_failcount_full(node, rsc, &last_failure, FALSE, data_set);
 
     if (all || failcount || last_failure > 0) {
         printed = TRUE;
         print_as("   %s: migration-threshold=%d", rsc_printable_id(rsc), rsc->migration_threshold);
     }
 
     if (failcount > 0) {
         printed = TRUE;
         print_as(" fail-count=%d", failcount);
     }
 
     if (last_failure > 0) {
         printed = TRUE;
         print_as(" last-failure=");
         print_date(last_failure);
     }
 
     if (printed) {
         print_as("\n");
     }
 }
 
 static void
 print_rsc_history(pe_working_set_t * data_set, node_t * node, xmlNode * rsc_entry)
 {
     GListPtr gIter = NULL;
     GListPtr op_list = NULL;
     gboolean print_name = TRUE;
     GListPtr sorted_op_list = NULL;
     const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
     resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
 
     xmlNode *rsc_op = NULL;
 
     for (rsc_op = __xml_first_child(rsc_entry); rsc_op != NULL; rsc_op = __xml_next(rsc_op)) {
         if (crm_str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) {
             op_list = g_list_append(op_list, rsc_op);
         }
     }
 
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *xml_op = (xmlNode *) gIter->data;
         const char *value = NULL;
         const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
         const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
         const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC);
         const char *interval = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
         int rc = crm_parse_int(op_rc, "0");
 
         if (safe_str_eq(task, CRMD_ACTION_STATUS)
             && safe_str_eq(interval, "0")) {
             task = "probe";
         }
 
         if (rc == 7 && safe_str_eq(task, "probe")) {
             continue;
 
         } else if (safe_str_eq(task, CRMD_ACTION_NOTIFY)) {
             continue;
         }
 
         if (print_name) {
             print_name = FALSE;
             if (rsc == NULL) {
                 print_as("Orphan resource: %s", rsc_id);
             } else {
                 print_rsc_summary(data_set, node, rsc, TRUE);
             }
         }
 
         print_as("    + (%s) %s:", call, task);
         if (safe_str_neq(interval, "0")) {
             print_as(" interval=%sms", interval);
         }
 
         if (print_timing) {
             int int_value;
             const char *attr = XML_RSC_OP_LAST_CHANGE;
 
             value = crm_element_value(xml_op, attr);
             if (value) {
                 int_value = crm_parse_int(value, NULL);
                 if (int_value > 0) {
                     print_as(" %s=", attr);
                     print_date(int_value);
                 }
             }
 
             attr = XML_RSC_OP_LAST_RUN;
             value = crm_element_value(xml_op, attr);
             if (value) {
                 int_value = crm_parse_int(value, NULL);
                 if (int_value > 0) {
                     print_as(" %s=", attr);
                     print_date(int_value);
                 }
             }
 
             attr = XML_RSC_OP_T_EXEC;
             value = crm_element_value(xml_op, attr);
             if (value) {
                 int_value = crm_parse_int(value, NULL);
                 print_as(" %s=%dms", attr, int_value);
             }
 
             attr = XML_RSC_OP_T_QUEUE;
             value = crm_element_value(xml_op, attr);
             if (value) {
                 int_value = crm_parse_int(value, NULL);
                 print_as(" %s=%dms", attr, int_value);
             }
         }
 
         print_as(" rc=%s (%s)\n", op_rc, services_ocf_exitcode_str(rc));
     }
 
     /* no need to free the contents */
     g_list_free(sorted_op_list);
 }
 
 static void
 print_attr_msg(node_t * node, GListPtr rsc_list, const char *attrname, const char *attrvalue)
 {
     GListPtr gIter = NULL;
 
     for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
         resource_t *rsc = (resource_t *) gIter->data;
         const char *type = g_hash_table_lookup(rsc->meta, "type");
 
         if (rsc->children != NULL) {
             print_attr_msg(node, rsc->children, attrname, attrvalue);
         }
 
         if (safe_str_eq(type, "ping") || safe_str_eq(type, "pingd")) {
             const char *name = g_hash_table_lookup(rsc->parameters, "name");
 
             if (name == NULL) {
                 name = "pingd";
             }
 
             /* To identify the resource with the attribute name. */
             if (safe_str_eq(name, attrname)) {
                 int host_list_num = 0;
                 int expected_score = 0;
                 int value = crm_parse_int(attrvalue, "0");
                 const char *hosts = g_hash_table_lookup(rsc->parameters, "host_list");
                 const char *multiplier = g_hash_table_lookup(rsc->parameters, "multiplier");
 
                 if(hosts) {
                     char **host_list = g_strsplit(hosts, " ", 0);
                     host_list_num = g_strv_length(host_list);
                     g_strfreev(host_list);
                 }
 
                 /* pingd multiplier is the same as the default value. */
                 expected_score = host_list_num * crm_parse_int(multiplier, "1");
 
                 /* pingd is abnormal score. */
                 if (value <= 0) {
                     print_as("\t: Connectivity is lost");
                 } else if (value < expected_score) {
                     print_as("\t: Connectivity is degraded (Expected=%d)", expected_score);
                 }
             }
         }
     }
 }
 
 static int
 compare_attribute(gconstpointer a, gconstpointer b)
 {
     int rc;
 
     rc = strcmp((const char *)a, (const char *)b);
 
     return rc;
 }
 
 static void
 create_attr_list(gpointer name, gpointer value, gpointer data)
 {
     int i;
     const char *filt_str[] = FILTER_STR;
 
     CRM_CHECK(name != NULL, return);
 
     /* filtering automatic attributes */
     for (i = 0; filt_str[i] != NULL; i++) {
         if (g_str_has_prefix(name, filt_str[i])) {
             return;
         }
     }
 
     attr_list = g_list_insert_sorted(attr_list, name, compare_attribute);
 }
 
 static void
 print_node_attribute(gpointer name, gpointer node_data)
 {
     const char *value = NULL;
     node_t *node = (node_t *) node_data;
 
     value = g_hash_table_lookup(node->details->attrs, name);
     print_as("    + %-32s\t: %-10s", (char *)name, value);
     print_attr_msg(node, node->details->running_rsc, name, value);
     print_as("\n");
 }
 
 static void
 print_node_summary(pe_working_set_t * data_set, gboolean operations)
 {
     xmlNode *lrm_rsc = NULL;
     xmlNode *rsc_entry = NULL;
     xmlNode *node_state = NULL;
     xmlNode *cib_status = get_object_root(XML_CIB_TAG_STATUS, data_set->input);
 
     if (operations) {
         print_as("\nOperations:\n");
     } else {
         print_as("\nMigration summary:\n");
     }
 
     for (node_state = __xml_first_child(cib_status); node_state != NULL;
          node_state = __xml_next(node_state)) {
         if (crm_str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, TRUE)) {
             node_t *node = pe_find_node_id(data_set->nodes, ID(node_state));
 
             if (node == NULL || node->details->online == FALSE) {
                 continue;
             }
 
             print_as("* Node %s: ", crm_element_value(node_state, XML_ATTR_UNAME));
             print_as("\n");
 
             lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
             lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE);
 
             for (rsc_entry = __xml_first_child(lrm_rsc); rsc_entry != NULL;
                  rsc_entry = __xml_next(rsc_entry)) {
                 if (crm_str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, TRUE)) {
                     if (operations) {
                         print_rsc_history(data_set, node, rsc_entry);
 
                     } else {
                         const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
                         resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
 
                         if (rsc) {
                             print_rsc_summary(data_set, node, rsc, FALSE);
                         } else {
                             print_as("   %s: orphan\n", rsc_id);
                         }
                     }
                 }
             }
         }
     }
 }
 
 static void
 print_ticket(gpointer name, gpointer value, gpointer data)
 {
     ticket_t *ticket = (ticket_t *) value;
 
     print_as(" %s\t%s%10s", ticket->id,
              ticket->granted ? "granted" : "revoked", ticket->standby ? " [standby]" : "");
     if (ticket->last_granted > -1) {
         print_as(" last-granted=");
         print_date(ticket->last_granted);
     }
     print_as("\n");
 
     return;
 }
 
 static void
 print_cluster_tickets(pe_working_set_t * data_set)
 {
 
     print_as("\nTickets:\n");
     g_hash_table_foreach(data_set->tickets, print_ticket, NULL);
 
     return;
 }
 
 static void print_neg_locations(pe_working_set_t *data_set)
 {
     GListPtr gIter, gIter2;
 
     print_as("\nFencing constraints:\n");
     for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
         rsc_to_node_t *location = (rsc_to_node_t *) gIter->data;
         if (!g_str_has_prefix(location->id, print_neg_location_prefix))
             continue;
         for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
             node_t *node = (node_t *) gIter2->data;
             if (node->weight >= 0) /* != -INFINITY ??? */
                 continue;
             print_as(" %s\tprevents %s from running %son %s\n",
                     location->id, location->rsc_lh->id,
                     location->role_filter == RSC_ROLE_MASTER ? "as Master " : "",
                     node->details->uname);
         }
     }
 }
 
 static void
 crm_mon_get_parameters(resource_t *rsc, pe_working_set_t * data_set)
 {
     get_rsc_attributes(rsc->parameters, rsc, NULL, data_set);
     crm_trace("Beekhof: unpacked params for %s (%d)", rsc->id, g_hash_table_size(rsc->parameters));
     if(rsc->children) {
         GListPtr gIter = NULL;
 
         for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
             crm_mon_get_parameters(gIter->data, data_set);
         }
     }
 }
 
 static int
 print_status(pe_working_set_t * data_set)
 {
     static int updates = 0;
 
     GListPtr gIter = NULL;
     node_t *dc = NULL;
     char *since_epoch = NULL;
     char *online_nodes = NULL;
     char *online_remote_nodes = NULL;
     char *online_remote_containers = NULL;
     char *offline_nodes = NULL;
     char *offline_remote_nodes = NULL;
     const char *stack_s = NULL;
     xmlNode *dc_version = NULL;
     xmlNode *quorum_node = NULL;
     xmlNode *stack = NULL;
     time_t a_time = time(NULL);
 
     int print_opts = pe_print_ncurses;
     const char *quorum_votes = "unknown";
 
     if (as_console) {
         blank_screen();
     } else {
         print_opts = pe_print_printf;
     }
 
     if (print_pending) {
         print_opts |= pe_print_pending;
     }
 
     if (print_clone_detail) {
         print_opts |= pe_print_clone_details;
     }
 
     updates++;
     dc = data_set->dc_node;
 
     if (a_time == (time_t) - 1) {
         crm_perror(LOG_ERR, "set_node_tstamp(): Invalid time returned");
         return 1;
     }
 
     since_epoch = ctime(&a_time);
     if (since_epoch != NULL && print_last_updated && !hide_headers) {
         print_as("Last updated: %s", since_epoch);
     }
 
     if (print_last_change && !hide_headers) {
         const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
         const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
         const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
         const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
 
         print_as("Last change: %s", last_written ? last_written : "");
         if (user) {
             print_as(" by %s", user);
         }
         if (client) {
             print_as(" via %s", client);
         }
         if (origin) {
             print_as(" on %s", origin);
         }
         print_as("\n");
     }
 
     stack =
         get_xpath_object("//nvpair[@name='cluster-infrastructure']", data_set->input, LOG_DEBUG);
     if (stack) {
         stack_s = crm_element_value(stack, XML_NVPAIR_ATTR_VALUE);
         if (!hide_headers) {
             print_as("Stack: %s\n", stack_s);
         }
     }
 
     dc_version = get_xpath_object("//nvpair[@name='dc-version']", data_set->input, LOG_DEBUG);
     if (dc == NULL) {
         print_as("Current DC: NONE\n");
     } else if (!hide_headers) {
         const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
 
         if (safe_str_neq(dc->details->uname, dc->details->id)) {
             print_as("Current DC: %s (%s)", dc->details->uname, dc->details->id);
         } else {
             print_as("Current DC: %s", dc->details->uname);
         }
         print_as(" - partition %s quorum\n", crm_is_true(quorum) ? "with" : "WITHOUT");
         if (dc_version) {
             print_as("Version: %s\n", crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE));
         }
     }
 
     quorum_node =
         get_xpath_object("//nvpair[@name='" XML_ATTR_EXPECTED_VOTES "']", data_set->input,
                          LOG_DEBUG);
     if (quorum_node) {
         quorum_votes = crm_element_value(quorum_node, XML_NVPAIR_ATTR_VALUE);
     }
 
     if(!hide_headers) {
         if(stack_s && strstr(stack_s, "classic openais") != NULL) {
             print_as("%d Nodes configured, %s expected votes\n", g_list_length(data_set->nodes),
                      quorum_votes);
         } else {
             print_as("%d Nodes configured\n", g_list_length(data_set->nodes));
         }
         print_as("%d Resources configured\n", count_resources(data_set, NULL));
         print_as("\n\n");
     }
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
         const char *node_mode = NULL;
         char *node_name = NULL;
 
         if (is_container_remote_node(node)) {
             node_name = g_strdup_printf("%s:%s", node->details->uname, node->details->remote_rsc->container->id);
         } else {
             node_name = g_strdup_printf("%s", node->details->uname);
         }
 
         if (node->details->unclean) {
             if (node->details->online && node->details->unclean) {
                 node_mode = "UNCLEAN (online)";
 
             } else if (node->details->pending) {
                 node_mode = "UNCLEAN (pending)";
 
             } else {
                 node_mode = "UNCLEAN (offline)";
             }
 
         } else if (node->details->pending) {
             node_mode = "pending";
 
         } else if (node->details->standby_onfail && node->details->online) {
             node_mode = "standby (on-fail)";
 
         } else if (node->details->standby) {
             if (node->details->online) {
                 node_mode = "standby";
             } else {
                 node_mode = "OFFLINE (standby)";
             }
 
         } else if (node->details->maintenance) {
             if (node->details->online) {
                 node_mode = "maintenance";
             } else {
                 node_mode = "OFFLINE (maintenance)";
             }
 
         } else if (node->details->online) {
             node_mode = "online";
             if (group_by_node == FALSE) {
                 if (is_container_remote_node(node)) {
                     online_remote_containers = add_list_element(online_remote_containers, node_name);
                 } else if (is_baremetal_remote_node(node)) {
                     online_remote_nodes = add_list_element(online_remote_nodes, node_name);
                 } else {
                     online_nodes = add_list_element(online_nodes, node_name);
                 }
                 continue;
             }
         } else {
             node_mode = "OFFLINE";
             if (group_by_node == FALSE) {
                 if (is_baremetal_remote_node(node)) {
                     offline_remote_nodes = add_list_element(offline_remote_nodes, node_name);
                 } else if (is_container_remote_node(node)) {
                     /* ignore offline container nodes */
                 } else {
                     offline_nodes = add_list_element(offline_nodes, node_name);
                 }
                 continue;
             }
         }
 
         if (is_container_remote_node(node)) {
             print_as("ContainerNode %s: %s\n", node_name, node_mode);
         } else if (is_baremetal_remote_node(node)) {
             print_as("RemoteNode %s: %s\n", node_name, node_mode);
         } else if (safe_str_eq(node->details->uname, node->details->id)) {
             print_as("Node %s: %s\n", node_name, node_mode);
         } else {
             print_as("Node %s (%s): %s\n", node_name, node->details->id, node_mode);
         }
 
         if (print_brief && group_by_node) {
             print_rscs_brief(node->details->running_rsc, "\t", print_opts | pe_print_rsconly,
                              stdout, FALSE);
 
         } else if (group_by_node) {
             GListPtr gIter2 = NULL;
 
             for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
                 resource_t *rsc = (resource_t *) gIter2->data;
 
                 rsc->fns->print(rsc, "\t", print_opts | pe_print_rsconly, stdout);
             }
         }
         free(node_name);
     }
 
     if (online_nodes) {
         print_as("Online: [%s ]\n", online_nodes);
         free(online_nodes);
     }
     if (offline_nodes) {
         print_as("OFFLINE: [%s ]\n", offline_nodes);
         free(offline_nodes);
     }
     if (online_remote_nodes) {
         print_as("RemoteOnline: [%s ]\n", online_remote_nodes);
         free(online_remote_nodes);
     }
     if (offline_remote_nodes) {
         print_as("RemoteOFFLINE: [%s ]\n", offline_remote_nodes);
         free(offline_remote_nodes);
     }
     if (online_remote_containers) {
         print_as("Containers: [%s ]\n", online_remote_containers);
         free(online_remote_containers);
     }
 
     if (group_by_node == FALSE && inactive_resources) {
         print_as("\nFull list of resources:\n");
 
     } else if (inactive_resources) {
         print_as("\nInactive resources:\n");
     }
 
     if (group_by_node == FALSE || inactive_resources) {
         print_as("\n");
 
         if (print_brief && group_by_node == FALSE) {
             print_opts |= pe_print_brief;
             print_rscs_brief(data_set->resources, NULL, print_opts, stdout,
                              inactive_resources);
         }
 
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             resource_t *rsc = (resource_t *) gIter->data;
 
             gboolean is_active = rsc->fns->active(rsc, TRUE);
             gboolean partially_active = rsc->fns->active(rsc, FALSE);
 
             if (print_brief && group_by_node == FALSE
                 && rsc->variant == pe_native) {
                 continue;
             }
 
             if (is_set(rsc->flags, pe_rsc_orphan) && is_active == FALSE) {
                 continue;
 
             } else if (group_by_node == FALSE) {
                 if (partially_active || inactive_resources) {
                     rsc->fns->print(rsc, NULL, print_opts, stdout);
                 }
 
             } else if (is_active == FALSE && inactive_resources) {
                 rsc->fns->print(rsc, NULL, print_opts, stdout);
             }
         }
     }
 
     if (print_nodes_attr) {
         print_as("\nNode Attributes:\n");
 
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             crm_mon_get_parameters(gIter->data, data_set);
         }
 
         for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
             node_t *node = (node_t *) gIter->data;
 
             if (node == NULL || node->details->online == FALSE) {
                 continue;
             }
             print_as("* Node %s:\n", node->details->uname);
             g_hash_table_foreach(node->details->attrs, create_attr_list, NULL);
             g_list_foreach(attr_list, print_node_attribute, node);
             g_list_free(attr_list);
             attr_list = NULL;
         }
     }
 
     if (print_operations || print_failcount) {
         print_node_summary(data_set, print_operations);
     }
 
     if (xml_has_children(data_set->failed)) {
         xmlNode *xml_op = NULL;
 
         print_as("\nFailed actions:\n");
         for (xml_op = __xml_first_child(data_set->failed); xml_op != NULL;
              xml_op = __xml_next(xml_op)) {
             int status = 0;
             int rc = 0;
             const char *id = ID(xml_op);
             const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
             const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE);
             const char *node = crm_element_value(xml_op, XML_ATTR_UNAME);
             const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
             const char *rc_s = crm_element_value(xml_op, XML_LRM_ATTR_RC);
             const char *status_s = crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS);
 
             rc = crm_parse_int(rc_s, "0");
             status = crm_parse_int(status_s, "0");
 
             if (last) {
                 time_t run_at = crm_parse_int(last, "0");
                 char *run_at_s = ctime(&run_at);
                 if(run_at_s) {
                     run_at_s[24] = 0; /* Overwrite the newline */
                 }
 
                 print_as("    %s on %s '%s' (%d): call=%s, status=%s, last-rc-change='%s', queued=%sms, exec=%sms\n",
                          op_key ? op_key : id, node, services_ocf_exitcode_str(rc), rc, call, services_lrm_status_str(status),
                          run_at_s, crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
             } else {
                 print_as("    %s on %s '%s' (%d): call=%s, status=%s\n",
                          op_key ? op_key : id, node, services_ocf_exitcode_str(rc), rc, call, services_lrm_status_str(status));
             }
         }
         print_as("\n");
     }
 
     if (print_tickets || print_neg_location_prefix) {
         /* For recording the tickets that are referenced in rsc_ticket constraints
          * but have never been granted yet.
          * To be able to print negative location constraint summary,
          * we also need them to be unpacked. */
         xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
         unpack_constraints(cib_constraints, data_set);
     }
     if (print_tickets) {
         print_cluster_tickets(data_set);
     }
     if (print_neg_location_prefix) {
         print_neg_locations(data_set);
     }
 #if CURSES_ENABLED
     if (as_console) {
         refresh();
     }
 #endif
     return 0;
 }
 
 static int
 print_xml_status(pe_working_set_t * data_set)
 {
     FILE *stream = stdout;
     GListPtr gIter = NULL;
     node_t *dc = NULL;
     xmlNode *stack = NULL;
     xmlNode *quorum_node = NULL;
     const char *quorum_votes = "unknown";
     int print_opts = pe_print_xml;
 
     dc = data_set->dc_node;
 
     if (print_pending) {
         print_opts |= pe_print_pending;
     }
 
     fprintf(stream, "<?xml version=\"1.0\"?>\n");
     fprintf(stream, "<crm_mon version=\"%s\">\n", VERSION);
 
     /*** SUMMARY ***/
     fprintf(stream, "    <summary>\n");
 
     if (print_last_updated) {
         time_t now = time(NULL);
         char *now_str = ctime(&now);
 
         now_str[24] = EOS;      /* replace the newline */
         fprintf(stream, "        <last_update time=\"%s\" />\n", now_str);
     }
 
     if (print_last_change) {
         const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
         const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
         const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
         const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
 
         fprintf(stream,
                 "        <last_change time=\"%s\" user=\"%s\" client=\"%s\" origin=\"%s\" />\n",
                 last_written ? last_written : "", user ? user : "", client ? client : "",
                 origin ? origin : "");
     }
 
     stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']",
                              data_set->input, LOG_DEBUG);
     if (stack) {
         fprintf(stream, "        <stack type=\"%s\" />\n",
                 crm_element_value(stack, XML_NVPAIR_ATTR_VALUE));
     }
 
     if (!dc) {
         fprintf(stream, "        <current_dc present=\"false\" />\n");
     } else {
         const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
         const char *uname = dc->details->uname;
         const char *id = dc->details->id;
         xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
                                                data_set->input,
                                                LOG_DEBUG);
 
         fprintf(stream,
                 "        <current_dc present=\"true\" version=\"%s\" name=\"%s\" id=\"%s\" with_quorum=\"%s\" />\n",
                 dc_version ? crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE) : "", uname, id,
                 quorum ? (crm_is_true(quorum) ? "true" : "false") : "false");
     }
 
     quorum_node = get_xpath_object("//nvpair[@name='" XML_ATTR_EXPECTED_VOTES "']",
                                    data_set->input, LOG_DEBUG);
     if (quorum_node) {
         quorum_votes = crm_element_value(quorum_node, XML_NVPAIR_ATTR_VALUE);
     }
     fprintf(stream, "        <nodes_configured number=\"%d\" expected_votes=\"%s\" />\n",
             g_list_length(data_set->nodes), quorum_votes);
 
     fprintf(stream, "        <resources_configured number=\"%d\" />\n",
             count_resources(data_set, NULL));
 
     fprintf(stream, "    </summary>\n");
 
     /*** NODES ***/
     fprintf(stream, "    <nodes>\n");
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
         const char *node_type = "unknown";
 
         switch (node->details->type) {
             case node_member:
                 node_type = "member";
                 break;
             case node_remote:
                 node_type = "remote";
                 break;
             case node_ping:
                 node_type = "ping";
                 break;
         }
 
         fprintf(stream, "        <node name=\"%s\" ", node->details->uname);
         fprintf(stream, "id=\"%s\" ", node->details->id);
         fprintf(stream, "online=\"%s\" ", node->details->online ? "true" : "false");
         fprintf(stream, "standby=\"%s\" ", node->details->standby ? "true" : "false");
         fprintf(stream, "standby_onfail=\"%s\" ", node->details->standby_onfail ? "true" : "false");
         fprintf(stream, "maintenance=\"%s\" ", node->details->maintenance ? "true" : "false");
         fprintf(stream, "pending=\"%s\" ", node->details->pending ? "true" : "false");
         fprintf(stream, "unclean=\"%s\" ", node->details->unclean ? "true" : "false");
         fprintf(stream, "shutdown=\"%s\" ", node->details->shutdown ? "true" : "false");
         fprintf(stream, "expected_up=\"%s\" ", node->details->expected_up ? "true" : "false");
         fprintf(stream, "is_dc=\"%s\" ", node->details->is_dc ? "true" : "false");
         fprintf(stream, "resources_running=\"%d\" ", g_list_length(node->details->running_rsc));
         fprintf(stream, "type=\"%s\" ", node_type);
 
         if (group_by_node) {
             GListPtr lpc2 = NULL;
 
             fprintf(stream, ">\n");
             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
                 resource_t *rsc = (resource_t *) lpc2->data;
 
                 rsc->fns->print(rsc, "            ", print_opts | pe_print_rsconly, stream);
             }
             fprintf(stream, "        </node>\n");
         } else {
             fprintf(stream, "/>\n");
         }
     }
     fprintf(stream, "    </nodes>\n");
 
     /*** RESOURCES ***/
     if (group_by_node == FALSE || inactive_resources) {
         fprintf(stream, "    <resources>\n");
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             resource_t *rsc = (resource_t *) gIter->data;
             gboolean is_active = rsc->fns->active(rsc, TRUE);
             gboolean partially_active = rsc->fns->active(rsc, FALSE);
 
             if (is_set(rsc->flags, pe_rsc_orphan) && is_active == FALSE) {
                 continue;
 
             } else if (group_by_node == FALSE) {
                 if (partially_active || inactive_resources) {
                     rsc->fns->print(rsc, "        ", print_opts, stream);
                 }
 
             } else if (is_active == FALSE && inactive_resources) {
                 rsc->fns->print(rsc, "        ", print_opts, stream);
             }
         }
         fprintf(stream, "    </resources>\n");
     }
 
+    /*** FAILURES ***/
+    if (xml_has_children(data_set->failed)) {
+        xmlNode *xml_op = NULL;
+
+        fprintf(stream, "    <failures>\n");
+        for (xml_op = __xml_first_child(data_set->failed); xml_op != NULL;
+             xml_op = __xml_next(xml_op)) {
+            int status = 0;
+            int rc = 0;
+	    int interval = 0;
+            const char *id = ID(xml_op);
+            const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
+	    const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); // needed?
+            const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE);
+            const char *node = crm_element_value(xml_op, XML_ATTR_UNAME);
+            const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
+            const char *rc_s = crm_element_value(xml_op, XML_LRM_ATTR_RC);
+	    const char *interval_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
+
+            rc = crm_parse_int(rc_s, "0");
+	    interval = crm_parse_int(interval_s, "0");
+
+            if (last) {
+                time_t run_at = crm_parse_int(last, "0");
+                char *run_at_s = ctime(&run_at);
+                if(run_at_s) {
+                    run_at_s[24] = 0; /* Overwrite the newline */
+                }
+
+                fprintf(stream, "        <failure %s=\"%s\" node=\"%s\" exitstatus=\"%s\" exitcode=\"%d\" call=\"%s\" status=\"%s\" last-rc-change=\"%s\" queued=\"%s\" exec=\"%s\" interval=\"%d\" task=\"%s\" />\n",
+                        op_key ? "op_key" : "id" ,op_key ? op_key : id, node, services_ocf_exitcode_str(rc), rc, call, services_lrm_status_str(status),
+                        run_at_s, crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), crm_element_value(xml_op, XML_RSC_OP_T_EXEC), interval, task);
+            } else {
+                print_as("        <failure %s=\"%s\" node=\"%s\" exitstatus=\"%s\" exitcode=\"%d\" call=\"%s\" status=\"%s\" />\n",
+                         op_key ? "op_key" : "id", op_key ? op_key : id, node, services_ocf_exitcode_str(rc), rc, call, services_lrm_status_str(status));
+            }
+        }
+        fprintf(stream, "    </failures>\n");
+    }
+
     fprintf(stream, "</crm_mon>\n");
     fflush(stream);
     fclose(stream);
 
     return 0;
 }
 
 static int
 print_html_status(pe_working_set_t * data_set, const char *filename, gboolean web_cgi)
 {
     FILE *stream;
     GListPtr gIter = NULL;
     node_t *dc = NULL;
     static int updates = 0;
     char *filename_tmp = NULL;
     int print_opts = pe_print_html;
 
     if (print_pending) {
         print_opts |= pe_print_pending;
     }
 
     if (web_cgi) {
         stream = stdout;
         fprintf(stream, "Content-type: text/html\n\n");
 
     } else {
         filename_tmp = crm_concat(filename, "tmp", '.');
         stream = fopen(filename_tmp, "w");
         if (stream == NULL) {
             crm_perror(LOG_ERR, "Cannot open %s for writing", filename_tmp);
             free(filename_tmp);
             return -1;
         }
     }
 
     updates++;
     dc = data_set->dc_node;
 
     fprintf(stream, "<html>");
     fprintf(stream, "<head>");
     fprintf(stream, "<title>Cluster status</title>");
 /* content="%d;url=http://webdesign.about.com" */
     fprintf(stream, "<meta http-equiv=\"refresh\" content=\"%d\">", reconnect_msec / 1000);
     fprintf(stream, "</head>");
 
     /*** SUMMARY ***/
 
     fprintf(stream, "<h2>Cluster summary</h2>");
     {
         char *now_str = NULL;
         time_t now = time(NULL);
 
         now_str = ctime(&now);
         now_str[24] = EOS;      /* replace the newline */
         fprintf(stream, "Last updated: <b>%s</b><br/>\n", now_str);
     }
 
     if (dc == NULL) {
         fprintf(stream, "Current DC: <font color=\"red\"><b>NONE</b></font><br/>");
     } else {
         fprintf(stream, "Current DC: %s (%s)<br/>", dc->details->uname, dc->details->id);
     }
     fprintf(stream, "%d Nodes configured.<br/>", g_list_length(data_set->nodes));
     fprintf(stream, "%d Resources configured.<br/>", count_resources(data_set, NULL));
 
     /*** CONFIG ***/
 
     fprintf(stream, "<h3>Config Options</h3>\n");
 
     fprintf(stream, "<table>\n");
     fprintf(stream, "<tr><td>STONITH of failed nodes</td><td>:</td><td>%s</td></tr>\n",
             is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled");
 
     fprintf(stream, "<tr><td>Cluster is</td><td>:</td><td>%ssymmetric</td></tr>\n",
             is_set(data_set->flags, pe_flag_symmetric_cluster) ? "" : "a-");
 
     fprintf(stream, "<tr><td>No Quorum Policy</td><td>:</td><td>");
     switch (data_set->no_quorum_policy) {
         case no_quorum_freeze:
             fprintf(stream, "Freeze resources");
             break;
         case no_quorum_stop:
             fprintf(stream, "Stop ALL resources");
             break;
         case no_quorum_ignore:
             fprintf(stream, "Ignore");
             break;
         case no_quorum_suicide:
             fprintf(stream, "Suicide");
             break;
     }
     fprintf(stream, "\n</td></tr>\n</table>\n");
 
     /*** NODE LIST ***/
 
     fprintf(stream, "<h2>Node List</h2>\n");
     fprintf(stream, "<ul>\n");
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
 
         fprintf(stream, "<li>");
         if (node->details->standby_onfail && node->details->online) {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"orange\">standby (on-fail)</font>\n");
         } else if (node->details->standby && node->details->online) {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"orange\">standby</font>\n");
         } else if (node->details->standby) {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"red\">OFFLINE (standby)</font>\n");
         } else if (node->details->maintenance && node->details->online) {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"blue\">maintenance</font>\n");
         } else if (node->details->maintenance) {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"red\">OFFLINE (maintenance)</font>\n");
         } else if (node->details->online) {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"green\">online</font>\n");
         } else {
             fprintf(stream, "Node: %s (%s): %s", node->details->uname, node->details->id,
                     "<font color=\"red\">OFFLINE</font>\n");
         }
         if (print_brief && group_by_node) {
             fprintf(stream, "<ul>\n");
             print_rscs_brief(node->details->running_rsc, NULL, print_opts | pe_print_rsconly,
                              stream, FALSE);
             fprintf(stream, "</ul>\n");
 
         } else if (group_by_node) {
             GListPtr lpc2 = NULL;
 
             fprintf(stream, "<ul>\n");
             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
                 resource_t *rsc = (resource_t *) lpc2->data;
 
                 fprintf(stream, "<li>");
                 rsc->fns->print(rsc, NULL, print_opts | pe_print_rsconly, stream);
                 fprintf(stream, "</li>\n");
             }
             fprintf(stream, "</ul>\n");
         }
         fprintf(stream, "</li>\n");
     }
     fprintf(stream, "</ul>\n");
 
     if (group_by_node && inactive_resources) {
         fprintf(stream, "<h2>Inactive Resources</h2>\n");
 
     } else if (group_by_node == FALSE) {
         fprintf(stream, "<h2>Resource List</h2>\n");
     }
 
     if (group_by_node == FALSE || inactive_resources) {
         if (print_brief && group_by_node == FALSE) {
             print_opts |= pe_print_brief;
             print_rscs_brief(data_set->resources, NULL, print_opts, stream,
                              inactive_resources);
         }
 
         for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
             resource_t *rsc = (resource_t *) gIter->data;
             gboolean is_active = rsc->fns->active(rsc, TRUE);
             gboolean partially_active = rsc->fns->active(rsc, FALSE);
 
             if (print_brief && group_by_node == FALSE
                 && rsc->variant == pe_native) {
                 continue;
             }
 
             if (is_set(rsc->flags, pe_rsc_orphan) && is_active == FALSE) {
                 continue;
 
             } else if (group_by_node == FALSE) {
                 if (partially_active || inactive_resources) {
                     rsc->fns->print(rsc, NULL, print_opts, stream);
                 }
 
             } else if (is_active == FALSE && inactive_resources) {
                 rsc->fns->print(rsc, NULL, print_opts, stream);
             }
         }
     }
 
     fprintf(stream, "</html>");
     fflush(stream);
     fclose(stream);
 
     if (!web_cgi) {
         if (rename(filename_tmp, filename) != 0) {
             crm_perror(LOG_ERR, "Unable to rename %s->%s", filename_tmp, filename);
         }
         free(filename_tmp);
     }
     return 0;
 }
 
 #if ENABLE_SNMP
 #  include <net-snmp/net-snmp-config.h>
 #  include <net-snmp/snmpv3_api.h>
 #  include <net-snmp/agent/agent_trap.h>
 #  include <net-snmp/library/snmp_client.h>
 #  include <net-snmp/library/mib.h>
 #  include <net-snmp/library/snmp_debug.h>
 
 #  define add_snmp_field(list, oid_string, value) do {			\
 	oid name[MAX_OID_LEN];						\
         size_t name_length = MAX_OID_LEN;				\
 	if (snmp_parse_oid(oid_string, name, &name_length)) {		\
 	    int s_rc = snmp_add_var(list, name, name_length, 's', (value)); \
 	    if(s_rc != 0) {						\
 		crm_err("Could not add %s=%s rc=%d", oid_string, value, s_rc); \
 	    } else {							\
 		crm_trace("Added %s=%s", oid_string, value);		\
 	    }								\
 	} else {							\
 	    crm_err("Could not parse OID: %s", oid_string);		\
 	}								\
     } while(0)								\
 
 #  define add_snmp_field_int(list, oid_string, value) do {		\
 	oid name[MAX_OID_LEN];						\
         size_t name_length = MAX_OID_LEN;				\
 	if (snmp_parse_oid(oid_string, name, &name_length)) {		\
 	    if(NULL == snmp_pdu_add_variable(				\
 		   list, name, name_length, ASN_INTEGER,		\
 		   (u_char *) & value, sizeof(value))) {		\
 		crm_err("Could not add %s=%d", oid_string, value);	\
 	    } else {							\
 		crm_trace("Added %s=%d", oid_string, value);		\
 	    }								\
 	} else {							\
 	    crm_err("Could not parse OID: %s", oid_string);		\
 	}								\
     } while(0)								\
 
 static int
 snmp_input(int operation, netsnmp_session * session, int reqid, netsnmp_pdu * pdu, void *magic)
 {
     return 1;
 }
 
 static netsnmp_session *
 crm_snmp_init(const char *target, char *community)
 {
     static netsnmp_session *session = NULL;
 
 #  ifdef NETSNMPV53
     char target53[128];
 
     snprintf(target53, sizeof(target53), "%s:162", target);
 #  endif
 
     if (session) {
         return session;
     }
 
     if (target == NULL) {
         return NULL;
     }
 
     if (get_crm_log_level() > LOG_INFO) {
         char *debug_tokens = strdup("run:shell,snmptrap,tdomain");
 
         debug_register_tokens(debug_tokens);
         snmp_set_do_debugging(1);
     }
 
     session = calloc(1, sizeof(netsnmp_session));
     snmp_sess_init(session);
     session->version = SNMP_VERSION_2c;
     session->callback = snmp_input;
     session->callback_magic = NULL;
 
     if (community) {
         session->community_len = strlen(community);
         session->community = (unsigned char *)community;
     }
 
     session = snmp_add(session,
 #  ifdef NETSNMPV53
                        netsnmp_tdomain_transport(target53, 0, "udp"),
 #  else
                        netsnmp_transport_open_client("snmptrap", target),
 #  endif
                        NULL, NULL);
 
     if (session == NULL) {
         snmp_sess_perror("Could not create snmp transport", session);
     }
     return session;
 }
 
 #endif
 
 static int
 send_snmp_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
                int status, const char *desc)
 {
     int ret = 1;
 
 #if ENABLE_SNMP
     static oid snmptrap_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
     static oid sysuptime_oid[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 };
 
     netsnmp_pdu *trap_pdu;
     netsnmp_session *session = crm_snmp_init(snmp_target, snmp_community);
 
     trap_pdu = snmp_pdu_create(SNMP_MSG_TRAP2);
     if (!trap_pdu) {
         crm_err("Failed to create SNMP notification");
         return SNMPERR_GENERR;
     }
 
     if (1) {
         /* send uptime */
         char csysuptime[20];
         time_t now = time(NULL);
 
         sprintf(csysuptime, "%ld", now);
         snmp_add_var(trap_pdu, sysuptime_oid, sizeof(sysuptime_oid) / sizeof(oid), 't', csysuptime);
     }
 
     /* Indicate what the trap is by setting snmpTrapOid.0 */
     ret =
         snmp_add_var(trap_pdu, snmptrap_oid, sizeof(snmptrap_oid) / sizeof(oid), 'o',
                      snmp_crm_trap_oid);
     if (ret != 0) {
         crm_err("Failed set snmpTrapOid.0=%s", snmp_crm_trap_oid);
         return ret;
     }
 
     /* Add extries to the trap */
     if (rsc) {
         add_snmp_field(trap_pdu, snmp_crm_oid_rsc, rsc);
     }
     add_snmp_field(trap_pdu, snmp_crm_oid_node, node);
     add_snmp_field(trap_pdu, snmp_crm_oid_task, task);
     add_snmp_field(trap_pdu, snmp_crm_oid_desc, desc);
 
     add_snmp_field_int(trap_pdu, snmp_crm_oid_rc, rc);
     add_snmp_field_int(trap_pdu, snmp_crm_oid_trc, target_rc);
     add_snmp_field_int(trap_pdu, snmp_crm_oid_status, status);
 
     /* Send and cleanup */
     ret = snmp_send(session, trap_pdu);
     if (ret == 0) {
         /* error */
         snmp_sess_perror("Could not send SNMP trap", session);
         snmp_free_pdu(trap_pdu);
         ret = SNMPERR_GENERR;
     } else {
         ret = SNMPERR_SUCCESS;
     }
 #else
     crm_err("Sending SNMP traps is not supported by this installation");
 #endif
     return ret;
 }
 
 #if ENABLE_ESMTP
 #  include <auth-client.h>
 #  include <libesmtp.h>
 
 static void
 print_recipient_status(smtp_recipient_t recipient, const char *mailbox, void *arg)
 {
     const smtp_status_t *status;
 
     status = smtp_recipient_status(recipient);
     printf("%s: %d %s", mailbox, status->code, status->text);
 }
 
 static void
 event_cb(smtp_session_t session, int event_no, void *arg, ...)
 {
     int *ok;
     va_list alist;
 
     va_start(alist, arg);
     switch (event_no) {
         case SMTP_EV_CONNECT:
         case SMTP_EV_MAILSTATUS:
         case SMTP_EV_RCPTSTATUS:
         case SMTP_EV_MESSAGEDATA:
         case SMTP_EV_MESSAGESENT:
         case SMTP_EV_DISCONNECT:
             break;
 
         case SMTP_EV_WEAK_CIPHER:{
                 int bits = va_arg(alist, long);
                 ok = va_arg(alist, int *);
 
                 crm_debug("SMTP_EV_WEAK_CIPHER, bits=%d - accepted.", bits);
                 *ok = 1;
                 break;
             }
         case SMTP_EV_STARTTLS_OK:
             crm_debug("SMTP_EV_STARTTLS_OK - TLS started here.");
             break;
 
         case SMTP_EV_INVALID_PEER_CERTIFICATE:{
                 long vfy_result = va_arg(alist, long);
                 ok = va_arg(alist, int *);
 
                 /* There is a table in handle_invalid_peer_certificate() of mail-file.c */
                 crm_err("SMTP_EV_INVALID_PEER_CERTIFICATE: %ld", vfy_result);
                 *ok = 1;
                 break;
             }
         case SMTP_EV_NO_PEER_CERTIFICATE:
             ok = va_arg(alist, int *);
 
             crm_debug("SMTP_EV_NO_PEER_CERTIFICATE - accepted.");
             *ok = 1;
             break;
         case SMTP_EV_WRONG_PEER_CERTIFICATE:
             ok = va_arg(alist, int *);
 
             crm_debug("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted.");
             *ok = 1;
             break;
         case SMTP_EV_NO_CLIENT_CERTIFICATE:
             ok = va_arg(alist, int *);
 
             crm_debug("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted.");
             *ok = 1;
             break;
         default:
             crm_debug("Got event: %d - ignored.\n", event_no);
     }
     va_end(alist);
 }
 #endif
 
 #define BODY_MAX 2048
 
 #if ENABLE_ESMTP
 static void
 crm_smtp_debug(const char *buf, int buflen, int writing, void *arg)
 {
     char type = 0;
     int lpc = 0, last = 0, level = *(int *)arg;
 
     if (writing == SMTP_CB_HEADERS) {
         type = 'H';
     } else if (writing) {
         type = 'C';
     } else {
         type = 'S';
     }
 
     for (; lpc < buflen; lpc++) {
         switch (buf[lpc]) {
             case 0:
             case '\n':
                 if (last > 0) {
                     do_crm_log(level, "   %.*s", lpc - last, buf + last);
                 } else {
                     do_crm_log(level, "%c: %.*s", type, lpc - last, buf + last);
                 }
                 last = lpc + 1;
                 break;
         }
     }
 }
 #endif
 
 static int
 send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
                  int status, const char *desc)
 {
     pid_t pid;
 
     /*setenv needs chars, these are ints */
     char *rc_s = crm_itoa(rc);
     char *status_s = crm_itoa(status);
     char *target_rc_s = crm_itoa(target_rc);
 
     crm_debug("Sending external notification to '%s' via '%s'", external_recipient, external_agent);
 
     setenv("CRM_notify_recipient", external_recipient, 1);
     setenv("CRM_notify_node", node, 1);
     setenv("CRM_notify_rsc", rsc, 1);
     setenv("CRM_notify_task", task, 1);
     setenv("CRM_notify_desc", desc, 1);
     setenv("CRM_notify_rc", rc_s, 1);
     setenv("CRM_notify_target_rc", target_rc_s, 1);
     setenv("CRM_notify_status", status_s, 1);
 
     pid = fork();
     if (pid == -1) {
         crm_perror(LOG_ERR, "notification fork() failed.");
     }
     if (pid == 0) {
         /* crm_debug("notification: I am the child. Executing the nofitication program."); */
         execl(external_agent, external_agent, NULL);
     }
 
     crm_trace("Finished running custom notification program '%s'.", external_agent);
     free(target_rc_s);
     free(status_s);
     free(rc_s);
     return 0;
 }
 
 static int
 send_smtp_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
                int status, const char *desc)
 {
 #if ENABLE_ESMTP
     smtp_session_t session;
     smtp_message_t message;
     auth_context_t authctx;
     struct sigaction sa;
 
     int len = 25; /* Note: Check extra padding on the Subject line below */
     int noauth = 1;
     int smtp_debug = LOG_DEBUG;
     char crm_mail_body[BODY_MAX];
     char *crm_mail_subject = NULL;
 
     memset(&sa, 0, sizeof(struct sigaction));
 
     if (node == NULL) {
         node = "-";
     }
     if (rsc == NULL) {
         rsc = "-";
     }
     if (desc == NULL) {
         desc = "-";
     }
 
     if (crm_mail_to == NULL) {
         return 1;
     }
 
     if (crm_mail_host == NULL) {
         crm_mail_host = "localhost:25";
     }
 
     if (crm_mail_prefix == NULL) {
         crm_mail_prefix = "Cluster notification";
     }
 
     crm_debug("Sending '%s' mail to %s via %s", crm_mail_prefix, crm_mail_to, crm_mail_host);
 
     len += strlen(crm_mail_prefix);
     len += strlen(task);
     len += strlen(rsc);
     len += strlen(node);
     len += strlen(desc);
     len++;
 
     crm_mail_subject = calloc(1, len);
     /* If you edit this line, ensure you allocate enough memory for it by altering 'len' above */
     snprintf(crm_mail_subject, len, "%s - %s event for %s on %s: %s\r\n", crm_mail_prefix, task,
              rsc, node, desc);
 
     len = 0;
     len += snprintf(crm_mail_body + len, BODY_MAX - len, "\r\n%s\r\n", crm_mail_prefix);
     len += snprintf(crm_mail_body + len, BODY_MAX - len, "====\r\n\r\n");
     if (rc == target_rc) {
         len += snprintf(crm_mail_body + len, BODY_MAX - len,
                         "Completed operation %s for resource %s on %s\r\n", task, rsc, node);
     } else {
         len += snprintf(crm_mail_body + len, BODY_MAX - len,
                         "Operation %s for resource %s on %s failed: %s\r\n", task, rsc, node, desc);
     }
 
     len += snprintf(crm_mail_body + len, BODY_MAX - len, "\r\nDetails:\r\n");
     len += snprintf(crm_mail_body + len, BODY_MAX - len,
                     "\toperation status: (%d) %s\r\n", status, services_lrm_status_str(status));
     if (status == PCMK_LRM_OP_DONE) {
         len += snprintf(crm_mail_body + len, BODY_MAX - len,
                         "\tscript returned: (%d) %s\r\n", rc, services_ocf_exitcode_str(rc));
         len += snprintf(crm_mail_body + len, BODY_MAX - len,
                         "\texpected return value: (%d) %s\r\n", target_rc,
                         services_ocf_exitcode_str(target_rc));
     }
 
     auth_client_init();
     session = smtp_create_session();
     message = smtp_add_message(session);
 
     smtp_starttls_enable(session, Starttls_ENABLED);
 
     sa.sa_handler = SIG_IGN;
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = 0;
     sigaction(SIGPIPE, &sa, NULL);
 
     smtp_set_server(session, crm_mail_host);
 
     authctx = auth_create_context();
     auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0);
 
     smtp_set_eventcb(session, event_cb, NULL);
 
     /* Now tell libESMTP it can use the SMTP AUTH extension.
      */
     if (!noauth) {
         crm_debug("Adding authentication context");
         smtp_auth_set_context(session, authctx);
     }
 
     if (crm_mail_from == NULL) {
         struct utsname us;
         char auto_from[BODY_MAX];
 
         CRM_ASSERT(uname(&us) == 0);
         snprintf(auto_from, BODY_MAX, "crm_mon@%s", us.nodename);
         smtp_set_reverse_path(message, auto_from);
 
     } else {
         /* NULL is ok */
         smtp_set_reverse_path(message, crm_mail_from);
     }
 
     smtp_set_header(message, "To", NULL /*phrase */ , NULL /*addr */ ); /* "Phrase" <addr> */
     smtp_add_recipient(message, crm_mail_to);
 
     /* Set the Subject: header and override any subject line in the message headers. */
     smtp_set_header(message, "Subject", crm_mail_subject);
     smtp_set_header_option(message, "Subject", Hdr_OVERRIDE, 1);
 
     smtp_set_message_str(message, crm_mail_body);
     smtp_set_monitorcb(session, crm_smtp_debug, &smtp_debug, 1);
 
     if (smtp_start_session(session)) {
         char buf[128];
         int rc = smtp_errno();
 
         crm_err("SMTP server problem: %s (%d)", smtp_strerror(rc, buf, sizeof buf), rc);
 
     } else {
         char buf[128];
         int rc = smtp_errno();
         const smtp_status_t *smtp_status = smtp_message_transfer_status(message);
 
         if (rc != 0) {
             crm_err("SMTP server problem: %s (%d)", smtp_strerror(rc, buf, sizeof buf), rc);
         }
         crm_info("Send status: %d %s", smtp_status->code, crm_str(smtp_status->text));
         smtp_enumerate_recipients(message, print_recipient_status, NULL);
     }
 
     smtp_destroy_session(session);
     auth_destroy_context(authctx);
     auth_client_exit();
 #endif
     return 0;
 }
 
 static void
 handle_rsc_op(xmlNode * rsc_op)
 {
     int rc = -1;
     int status = -1;
     int action = -1;
     int interval = 0;
     int target_rc = -1;
     int transition_num = -1;
     gboolean notify = TRUE;
 
     char *rsc = NULL;
     char *task = NULL;
     const char *desc = NULL;
     const char *node = NULL;
     const char *magic = NULL;
     const char *id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY);
     char *update_te_uuid = NULL;
 
     xmlNode *n = rsc_op;
 
     if (id == NULL) {
         /* Compatability with <= 1.1.5 */
         id = ID(rsc_op);
     }
 
     magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
     if (magic == NULL) {
         /* non-change */
         return;
     }
 
     if (FALSE == decode_transition_magic(magic, &update_te_uuid, &transition_num, &action,
                                          &status, &rc, &target_rc)) {
         crm_err("Invalid event %s detected for %s", magic, id);
         return;
     }
 
     if (parse_op_key(id, &rsc, &task, &interval) == FALSE) {
         crm_err("Invalid event detected for %s", id);
         goto bail;
     }
 
     while (n != NULL && safe_str_neq(XML_CIB_TAG_STATE, TYPE(n))) {
         n = n->parent;
     }
 
     node = crm_element_value(n, XML_ATTR_UNAME);
     if (node == NULL) {
         node = ID(n);
     }
     if (node == NULL) {
         crm_err("No node detected for event %s (%s)", magic, id);
         goto bail;
     }
 
     /* look up where we expected it to be? */
     desc = pcmk_strerror(pcmk_ok);
     if (status == PCMK_LRM_OP_DONE && target_rc == rc) {
         crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
         if (rc == PCMK_OCF_NOT_RUNNING) {
             notify = FALSE;
         }
 
     } else if (status == PCMK_LRM_OP_DONE) {
         desc = services_ocf_exitcode_str(rc);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
 
     } else {
         desc = services_lrm_status_str(status);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
     }
 
     if (notify && snmp_target) {
         send_snmp_trap(node, rsc, task, target_rc, rc, status, desc);
     }
     if (notify && crm_mail_to) {
         send_smtp_trap(node, rsc, task, target_rc, rc, status, desc);
     }
     if (notify && external_agent) {
         send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
     }
   bail:
     free(update_te_uuid);
     free(rsc);
     free(task);
 }
 
 static gboolean
 mon_trigger_refresh(gpointer user_data)
 {
     mainloop_set_trigger(refresh_trigger);
     return FALSE;
 }
 
 void
 crm_diff_update(const char *event, xmlNode * msg)
 {
     int rc = -1;
     long now = time(NULL);
     static bool stale = FALSE;
     static int updates = 0;
     static mainloop_timer_t *refresh_timer = NULL;
 
     print_dot();
 
     if(refresh_timer == NULL) {
         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
     }
 
     if (current_cib != NULL) {
         xmlNode *cib_last = current_cib;
 
         current_cib = NULL;
 
         rc = cib_apply_patch_event(msg, cib_last, &current_cib, LOG_DEBUG);
         free_xml(cib_last);
 
         switch (rc) {
             case -pcmk_err_diff_resync:
             case -pcmk_err_diff_failed:
                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
                 break;
             case pcmk_ok:
                 updates++;
                 break;
             default:
                 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
         }
     }
 
     if (current_cib == NULL) {
         cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
     }
 
     if (crm_mail_to || snmp_target || external_agent) {
         /* Process operation updates */
         xmlXPathObject *xpathObj = xpath_search(msg,
                                                 "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
                                                 "//" XML_LRM_TAG_RSC_OP);
         int lpc = 0, max = numXpathResults(xpathObj);
 
         for (lpc = 0; lpc < max; lpc++) {
             xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
 
             handle_rsc_op(rsc_op);
         }
         freeXpathObject(xpathObj);
     }
 
     if (current_cib == NULL) {
         if(!stale) {
             print_as("--- Stale data ---");
         }
         stale = TRUE;
         return;
     }
 
     stale = FALSE;
     /* Refresh
      * - immediately if the last update was more than 5s ago
      * - every 10 updates
      * - at most 2s after the last update
      */
     if ((now - last_refresh) > (reconnect_msec / 1000)) {
         mainloop_set_trigger(refresh_trigger);
         mainloop_timer_stop(refresh_timer);
         updates = 0;
 
     } else if(updates > 10) {
         mainloop_set_trigger(refresh_trigger);
         mainloop_timer_stop(refresh_timer);
         updates = 0;
 
     } else {
         mainloop_timer_start(refresh_timer);
     }
 }
 
 gboolean
 mon_refresh_display(gpointer user_data)
 {
     xmlNode *cib_copy = copy_xml(current_cib);
     pe_working_set_t data_set;
 
     last_refresh = time(NULL);
 
     if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
         if (cib) {
             cib->cmds->signoff(cib);
         }
         print_as("Upgrade failed: %s", pcmk_strerror(-pcmk_err_dtd_validation));
         if (as_console) {
             sleep(2);
         }
         clean_up(EX_USAGE);
         return FALSE;
     }
 
     set_working_set_defaults(&data_set);
     data_set.input = cib_copy;
     cluster_status(&data_set);
 
     if (as_html_file || web_cgi) {
         if (print_html_status(&data_set, as_html_file, web_cgi) != 0) {
             fprintf(stderr, "Critical: Unable to output html file\n");
             clean_up(EX_USAGE);
         }
     } else if (as_xml) {
         if (print_xml_status(&data_set) != 0) {
             fprintf(stderr, "Critical: Unable to output xml file\n");
             clean_up(EX_USAGE);
         }
     } else if (daemonize) {
         /* do nothing */
 
     } else if (simple_status) {
         print_simple_status(&data_set);
         if (has_warnings) {
             clean_up(EX_USAGE);
         }
 
     } else {
         print_status(&data_set);
     }
 
     cleanup_calculations(&data_set);
     return TRUE;
 }
 
 void
 mon_st_callback(stonith_t * st, stonith_event_t * e)
 {
     char *desc = g_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
                                  e->operation, e->origin, e->target, pcmk_strerror(e->result),
                                  e->id);
 
     if (snmp_target) {
         send_snmp_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
     }
     if (crm_mail_to) {
         send_smtp_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
     }
     if (external_agent) {
         send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
     }
     g_free(desc);
 }
 
 /*
  * De-init ncurses, signoff from the CIB and deallocate memory.
  */
 void
 clean_up(int rc)
 {
 #if ENABLE_SNMP
     netsnmp_session *session = crm_snmp_init(NULL, NULL);
 
     if (session) {
         snmp_close(session);
         snmp_shutdown("snmpapp");
     }
 #endif
 
 #if CURSES_ENABLED
     if (as_console) {
         as_console = FALSE;
         echo();
         nocbreak();
         endwin();
     }
 #endif
 
     if (cib != NULL) {
         cib->cmds->signoff(cib);
         cib_delete(cib);
         cib = NULL;
     }
 
     free(as_html_file);
     free(xml_file);
     free(pid_file);
 
     if (rc >= 0) {
         crm_exit(rc);
     }
     return;
 }
diff --git a/xml/constraints-1.2.rng b/xml/constraints-1.2.rng
index 221140c764..35756f0119 100644
--- a/xml/constraints-1.2.rng
+++ b/xml/constraints-1.2.rng
@@ -1,219 +1,226 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0" 
          datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
   <start>
       <ref name="element-constraints"/>
   </start>
 
   <define name="element-constraints">
       <zeroOrMore>
 	<choice>
 	  <ref name="element-location"/>
 	  <ref name="element-colocation"/>
 	  <ref name="element-order"/>
 	  <ref name="element-rsc_ticket"/>
 	</choice>
       </zeroOrMore>
   </define>
 
   <define name="element-location">
     <element name="rsc_location">
       <attribute name="id"><data type="ID"/></attribute>
-      <attribute name="rsc"><data type="IDREF"/></attribute>
-      <optional>
-	<attribute name="role">
-	  <ref name="attribute-roles"/>
-	</attribute>
-      </optional>
+      <choice>
+	<group>
+	  <attribute name="rsc"><data type="IDREF"/></attribute>
+	  <optional>
+	    <attribute name="role">
+	      <ref name="attribute-roles"/>
+	    </attribute>
+	  </optional>
+	</group>
+	<oneOrMore>
+	  <ref name="element-resource-set"/>
+	</oneOrMore>
+      </choice>
       <choice>
 	<group>
 	  <externalRef href="score.rng"/>
 	  <attribute name="node"><text/></attribute>
 	</group>
 	<oneOrMore>
 	  <externalRef href="rule.rng"/>
 	</oneOrMore>
       </choice>
       <optional>
 	<ref name="element-lifetime"/>
       </optional>
     </element>
   </define>
 
   <define name="element-resource-set">
     <element name="resource_set">
       <choice>
 	<attribute name="id-ref"><data type="IDREF"/></attribute>
 	<group>
 	  <attribute name="id"><data type="ID"/></attribute>
 	  <optional>
 	    <attribute name="sequential"><data type="boolean"/></attribute>
 	  </optional>
 	  <optional>
 	    <attribute name="require-all"><data type="boolean"/></attribute>
 	  </optional>
 	  <optional>
 	    <attribute name="action">
 	      <ref name="attribute-actions"/>
 	    </attribute>
 	  </optional>
 	  <optional>
 	    <attribute name="role">
 	      <ref name="attribute-roles"/>
 	    </attribute>
 	  </optional>
 	  <optional>
 	    <externalRef href="score.rng"/>
 	  </optional>
 	  <oneOrMore>
 	    <element name="resource_ref">
 	      <attribute name="id"><data type="IDREF"/></attribute>
 	    </element>
 	  </oneOrMore>
 	</group>
       </choice>
     </element>
   </define>
 
   <define name="element-colocation">
     <element name="rsc_colocation">
       <attribute name="id"><data type="ID"/></attribute>
       <optional>
 	<choice>
 	  <externalRef href="score.rng"/>
 	  <attribute name="score-attribute"><text/></attribute>
 	  <attribute name="score-attribute-mangle"><text/></attribute>
 	</choice>
       </optional>
       <optional>
 	<ref name="element-lifetime"/>
       </optional>
       <choice>
 	<oneOrMore>
 	  <ref name="element-resource-set"/>
 	</oneOrMore>
 	<group>
 	  <attribute name="rsc"><data type="IDREF"/></attribute>
 	  <attribute name="with-rsc"><data type="IDREF"/></attribute>
 	  <optional>
 	    <attribute name="node-attribute"><text/></attribute>
 	  </optional>
 	  <optional>
 	    <attribute name="rsc-role">
 	      <ref name="attribute-roles"/>
 	    </attribute>
 	  </optional>
 	  <optional>
 	    <attribute name="with-rsc-role">
 	      <ref name="attribute-roles"/>
 	    </attribute>
 	  </optional>
 	</group>
       </choice>
     </element>
   </define>
 
   <define name="element-order">
     <element name="rsc_order">
       <attribute name="id"><data type="ID"/></attribute>
       <optional>
 	<ref name="element-lifetime"/>
       </optional>
       <optional>
 	<attribute name="symmetrical"><data type="boolean"/></attribute>
       </optional>
       <optional>
 	<choice>
 	  <externalRef href="score.rng"/>
 	  <attribute name="kind">
 	    <ref name="order-types"/>
 	  </attribute>
 	</choice>
       </optional>
       <choice>
 	<oneOrMore>
 	  <ref name="element-resource-set"/>
 	</oneOrMore>
 	<group>
 	  <attribute name="first"><data type="IDREF"/></attribute>
 	  <attribute name="then"><data type="IDREF"/></attribute>
 	  <optional>
 	    <attribute name="first-action">
 	      <ref name="attribute-actions"/>
 	    </attribute>
 	  </optional>
 	  <optional>
 	    <attribute name="then-action">
 	      <ref name="attribute-actions"/>
 	    </attribute>
 	  </optional>
 	</group>
       </choice>
     </element>
   </define>
  
   <define name="element-rsc_ticket">
     <element name="rsc_ticket">
       <attribute name="id"><data type="ID"/></attribute>
       <choice>
 	<oneOrMore>
 	  <ref name="element-resource-set"/>
 	</oneOrMore>
 	<group>
 	  <attribute name="rsc"><data type="IDREF"/></attribute>
 	  <optional>
 	    <attribute name="rsc-role">
 	      <ref name="attribute-roles"/>
 	    </attribute>
 	  </optional>
 	</group>
       </choice>
       <attribute name="ticket"><text/></attribute>
       <optional>
 	<attribute name="loss-policy">
 	  <choice>
 	    <value>stop</value>
 	    <value>demote</value>
 	    <value>fence</value>
 	    <value>freeze</value>
 	  </choice>
 	</attribute>
       </optional>
     </element>
   </define>
 
   <define name="attribute-actions">
     <choice>
       <value>start</value>
       <value>promote</value>
       <value>demote</value>
       <value>stop</value>
     </choice>
   </define>
       
   <define name="attribute-roles">
     <choice>
       <value>Stopped</value>
       <value>Started</value>
       <value>Master</value>
       <value>Slave</value>
     </choice>
   </define>
 
   <define name="order-types">
     <choice>
       <value>Optional</value>
       <value>Mandatory</value>
       <value>Serialize</value>
     </choice>
   </define>
 
   <define name="element-lifetime">
     <element name="lifetime">
       <oneOrMore>
 	<externalRef href="rule.rng"/>
       </oneOrMore>
     </element>
   </define>
   
 </grammar>