Blob Blame History Raw
From 55ebd895ba2c64713c3db2590ffe22c15b8563e3 Mon Sep 17 00:00:00 2001
From: Ken Gaillot <kgaillot@redhat.com>
Date: Fri, 13 Dec 2019 16:05:05 -0600
Subject: [PATCH] Refactor: libcrmcommon: introduce new set of return codes

Since we plan to introduce a high-level public API, it's a good time to
introduce some best practices.

Most Pacemaker API functions currently return an integer return code, such that
its absolute value is either a system error number or a custom pcmk_err_*
number. This is less than ideal because system error numbers are constrained
only to the positive int range, so there's the possibility (though not noticed
in the wild) that system errors and custom errors could collide.

The new method being introduced here still uses an integer return code,
but negative values are from a new enumeration, and positive values are
system error numbers. 0 still represents success.

It is expected that the new method will be used with new functions, and
existing internal functions will be gradually refactored to use it as well.
Existing public API functions can be addressed at the next backward
compatibility break (2.1.0).
---
 include/crm/common/results.h |  59 ++++-
 lib/common/results.c         | 536 ++++++++++++++++++++++++++++++-------------
 tools/crm_error.c            | 100 +++++---
 3 files changed, 493 insertions(+), 202 deletions(-)

diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index 7a32110..b29a016 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2019 the Pacemaker project contributors
+ * Copyright 2012-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
@@ -49,11 +49,21 @@ extern "C" {
 /*
  * Function return codes
  *
+ * Most Pacemaker API functions return an integer return code. There are two
+ * alternative interpretations. The legacy interpration is that the absolute
+ * value of the return code is either a system error number or a custom
+ * pcmk_err_* number. This is less than ideal because system error numbers are
+ * constrained only to the positive int range, so there's the possibility
+ * (though not noticed in the wild) that system errors and custom errors could
+ * collide. The new intepretation is that negative values are from the pcmk_rc_e
+ * enum, and positive values are system error numbers. Both use 0 for success.
+ *
  * For system error codes, see:
  * - /usr/include/asm-generic/errno.h
  * - /usr/include/asm-generic/errno-base.h
  */
 
+// Legacy custom return codes for Pacemaker API functions (deprecated)
 #  define pcmk_ok                       0
 #  define PCMK_ERROR_OFFSET             190    /* Replacements on non-linux systems, see include/portability.h */
 #  define PCMK_CUSTOM_OFFSET            200    /* Purely custom codes */
@@ -75,6 +85,48 @@ extern "C" {
 #  define pcmk_err_bad_nvpair           216
 #  define pcmk_err_unknown_format       217
 
+/*!
+ * \enum pcmk_rc_e
+ * \brief Return codes for Pacemaker API functions
+ *
+ * Any Pacemaker API function documented as returning a "standard Pacemaker
+ * return code" will return pcmk_rc_ok (0) on success, and one of this
+ * enumeration's other (negative) values or a (positive) system error number
+ * otherwise. The custom codes are at -1001 and lower, so that the caller may
+ * use -1 through -1000 for their own custom values if desired. While generally
+ * referred to as "errors", nonzero values simply indicate a result, which might
+ * or might not be an error depending on the calling context.
+ */
+enum pcmk_rc_e {
+    /* When adding new values, use consecutively lower numbers, update the array
+     * in lib/common/results.c, and test with crm_error.
+     */
+    pcmk_rc_no_quorum           = -1017,
+    pcmk_rc_schema_validation   = -1016,
+    pcmk_rc_schema_unchanged    = -1015,
+    pcmk_rc_transform_failed    = -1014,
+    pcmk_rc_old_data            = -1013,
+    pcmk_rc_diff_failed         = -1012,
+    pcmk_rc_diff_resync         = -1011,
+    pcmk_rc_cib_modified        = -1010,
+    pcmk_rc_cib_backup          = -1009,
+    pcmk_rc_cib_save            = -1008,
+    pcmk_rc_cib_corrupt         = -1007,
+    pcmk_rc_multiple            = -1006,
+    pcmk_rc_node_unknown        = -1005,
+    pcmk_rc_already             = -1004,
+    pcmk_rc_bad_nvpair          = -1003,
+    pcmk_rc_unknown_format      = -1002,
+    // Developers: Use a more specific code than pcmk_rc_error whenever possible
+    pcmk_rc_error               = -1001,
+
+    // Values -1 through -1000 reserved for caller use
+
+    pcmk_rc_ok                  =     0
+
+    // Positive values reserved for system error numbers
+};
+
 /*
  * Exit status codes
  *
@@ -150,6 +202,11 @@ typedef enum crm_exit_e {
     CRM_EX_MAX                  = 255, // ensure crm_exit_t can hold this
 } crm_exit_t;
 
+const char *pcmk_rc_name(int rc);
+const char *pcmk_rc_str(int rc);
+crm_exit_t pcmk_rc2exitc(int rc);
+int pcmk_rc2legacy(int rc);
+int pcmk_legacy2rc(int legacy_rc);
 const char *pcmk_strerror(int rc);
 const char *pcmk_errorname(int rc);
 const char *bz2_strerror(int rc);
diff --git a/lib/common/results.c b/lib/common/results.c
index b80191c..189648f 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2004-2019 the Pacemaker project contributors
+ * Copyright 2004-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
@@ -22,148 +22,14 @@
 #include <crm/common/mainloop.h>
 #include <crm/common/xml.h>
 
+// @COMPAT Legacy function return codes
+
+//! \deprecated Use standard return codes and pcmk_rc_name() instead
 const char *
 pcmk_errorname(int rc)
 {
-    int error = abs(rc);
-
-    switch (error) {
-        case E2BIG: return "E2BIG";
-        case EACCES: return "EACCES";
-        case EADDRINUSE: return "EADDRINUSE";
-        case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
-        case EAFNOSUPPORT: return "EAFNOSUPPORT";
-        case EAGAIN: return "EAGAIN";
-        case EALREADY: return "EALREADY";
-        case EBADF: return "EBADF";
-        case EBADMSG: return "EBADMSG";
-        case EBUSY: return "EBUSY";
-        case ECANCELED: return "ECANCELED";
-        case ECHILD: return "ECHILD";
-        case ECOMM: return "ECOMM";
-        case ECONNABORTED: return "ECONNABORTED";
-        case ECONNREFUSED: return "ECONNREFUSED";
-        case ECONNRESET: return "ECONNRESET";
-        /* case EDEADLK: return "EDEADLK"; */
-        case EDESTADDRREQ: return "EDESTADDRREQ";
-        case EDOM: return "EDOM";
-        case EDQUOT: return "EDQUOT";
-        case EEXIST: return "EEXIST";
-        case EFAULT: return "EFAULT";
-        case EFBIG: return "EFBIG";
-        case EHOSTDOWN: return "EHOSTDOWN";
-        case EHOSTUNREACH: return "EHOSTUNREACH";
-        case EIDRM: return "EIDRM";
-        case EILSEQ: return "EILSEQ";
-        case EINPROGRESS: return "EINPROGRESS";
-        case EINTR: return "EINTR";
-        case EINVAL: return "EINVAL";
-        case EIO: return "EIO";
-        case EISCONN: return "EISCONN";
-        case EISDIR: return "EISDIR";
-        case ELIBACC: return "ELIBACC";
-        case ELOOP: return "ELOOP";
-        case EMFILE: return "EMFILE";
-        case EMLINK: return "EMLINK";
-        case EMSGSIZE: return "EMSGSIZE";
-#ifdef EMULTIHOP // Not available on OpenBSD
-        case EMULTIHOP: return "EMULTIHOP";
-#endif
-        case ENAMETOOLONG: return "ENAMETOOLONG";
-        case ENETDOWN: return "ENETDOWN";
-        case ENETRESET: return "ENETRESET";
-        case ENETUNREACH: return "ENETUNREACH";
-        case ENFILE: return "ENFILE";
-        case ENOBUFS: return "ENOBUFS";
-        case ENODATA: return "ENODATA";
-        case ENODEV: return "ENODEV";
-        case ENOENT: return "ENOENT";
-        case ENOEXEC: return "ENOEXEC";
-        case ENOKEY: return "ENOKEY";
-        case ENOLCK: return "ENOLCK";
-#ifdef ENOLINK // Not available on OpenBSD
-        case ENOLINK: return "ENOLINK";
-#endif
-        case ENOMEM: return "ENOMEM";
-        case ENOMSG: return "ENOMSG";
-        case ENOPROTOOPT: return "ENOPROTOOPT";
-        case ENOSPC: return "ENOSPC";
-        case ENOSR: return "ENOSR";
-        case ENOSTR: return "ENOSTR";
-        case ENOSYS: return "ENOSYS";
-        case ENOTBLK: return "ENOTBLK";
-        case ENOTCONN: return "ENOTCONN";
-        case ENOTDIR: return "ENOTDIR";
-        case ENOTEMPTY: return "ENOTEMPTY";
-        case ENOTSOCK: return "ENOTSOCK";
-        /* case ENOTSUP: return "ENOTSUP"; */
-        case ENOTTY: return "ENOTTY";
-        case ENOTUNIQ: return "ENOTUNIQ";
-        case ENXIO: return "ENXIO";
-        case EOPNOTSUPP: return "EOPNOTSUPP";
-        case EOVERFLOW: return "EOVERFLOW";
-        case EPERM: return "EPERM";
-        case EPFNOSUPPORT: return "EPFNOSUPPORT";
-        case EPIPE: return "EPIPE";
-        case EPROTO: return "EPROTO";
-        case EPROTONOSUPPORT: return "EPROTONOSUPPORT";
-        case EPROTOTYPE: return "EPROTOTYPE";
-        case ERANGE: return "ERANGE";
-        case EREMOTE: return "EREMOTE";
-        case EREMOTEIO: return "EREMOTEIO";
-
-        case EROFS: return "EROFS";
-        case ESHUTDOWN: return "ESHUTDOWN";
-        case ESPIPE: return "ESPIPE";
-        case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
-        case ESRCH: return "ESRCH";
-        case ESTALE: return "ESTALE";
-        case ETIME: return "ETIME";
-        case ETIMEDOUT: return "ETIMEDOUT";
-        case ETXTBSY: return "ETXTBSY";
-        case EUNATCH: return "EUNATCH";
-        case EUSERS: return "EUSERS";
-        /* case EWOULDBLOCK: return "EWOULDBLOCK"; */
-        case EXDEV: return "EXDEV";
-
-#ifdef EBADE
-            /* Not available on OSX */
-        case EBADE: return "EBADE";
-        case EBADFD: return "EBADFD";
-        case EBADSLT: return "EBADSLT";
-        case EDEADLOCK: return "EDEADLOCK";
-        case EBADR: return "EBADR";
-        case EBADRQC: return "EBADRQC";
-        case ECHRNG: return "ECHRNG";
-#ifdef EISNAM /* Not available on Illumos/Solaris */
-        case EISNAM: return "EISNAM";
-        case EKEYEXPIRED: return "EKEYEXPIRED";
-        case EKEYREJECTED: return "EKEYREJECTED";
-        case EKEYREVOKED: return "EKEYREVOKED";
-#endif
-        case EL2HLT: return "EL2HLT";
-        case EL2NSYNC: return "EL2NSYNC";
-        case EL3HLT: return "EL3HLT";
-        case EL3RST: return "EL3RST";
-        case ELIBBAD: return "ELIBBAD";
-        case ELIBMAX: return "ELIBMAX";
-        case ELIBSCN: return "ELIBSCN";
-        case ELIBEXEC: return "ELIBEXEC";
-#ifdef ENOMEDIUM  /* Not available on Illumos/Solaris */
-        case ENOMEDIUM: return "ENOMEDIUM";
-        case EMEDIUMTYPE: return "EMEDIUMTYPE";
-#endif
-        case ENONET: return "ENONET";
-        case ENOPKG: return "ENOPKG";
-        case EREMCHG: return "EREMCHG";
-        case ERESTART: return "ERESTART";
-        case ESTRPIPE: return "ESTRPIPE";
-#ifdef EUCLEAN  /* Not available on Illumos/Solaris */
-        case EUCLEAN: return "EUCLEAN";
-#endif
-        case EXFULL: return "EXFULL";
-#endif
-
+    rc = abs(rc);
+    switch (rc) {
         case pcmk_err_generic: return "pcmk_err_generic";
         case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
         case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
@@ -180,24 +46,26 @@ pcmk_errorname(int rc)
         case pcmk_err_already: return "pcmk_err_already";
         case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
         case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
+        default: return pcmk_rc_name(rc); // system errno
     }
-    return "Unknown";
 }
 
+//! \deprecated Use standard return codes and pcmk_rc_str() instead
 const char *
 pcmk_strerror(int rc)
 {
-    int error = abs(rc);
-
-    if (error == 0) {
+    if (rc == 0) {
         return "OK";
+    }
 
-    // Of course error > 0 ... unless someone passed INT_MIN as rc
-    } else if ((error > 0) && (error < PCMK_ERROR_OFFSET)) {
-        return strerror(error);
+    rc = abs(rc);
+
+    // Of course rc > 0 ... unless someone passed INT_MIN as rc
+    if ((rc > 0) && (rc < PCMK_ERROR_OFFSET)) {
+        return strerror(rc);
     }
 
-    switch (error) {
+    switch (rc) {
         case pcmk_err_generic:
             return "Generic Pacemaker error";
         case pcmk_err_no_quorum:
@@ -253,11 +121,313 @@ pcmk_strerror(int rc)
         case ENOKEY:
             return "Required key not available";
     }
-
     crm_err("Unknown error code: %d", rc);
     return "Unknown error";
 }
 
+// Standard Pacemaker API return codes
+
+/* This array is used only for nonzero values of pcmk_rc_e. Its values must be
+ * kept in the exact reverse order of the enum value numbering (i.e. add new
+ * values to the end of the array).
+ */
+static struct pcmk__rc_info {
+    const char *name;
+    const char *desc;
+    int legacy_rc;
+} pcmk__rcs[] = {
+    { "pcmk_rc_error",
+      "Error",
+      -pcmk_err_generic,
+    },
+    { "pcmk_rc_unknown_format",
+      "Unknown output format",
+      -pcmk_err_unknown_format,
+    },
+    { "pcmk_rc_bad_nvpair",
+      "Bad name/value pair given",
+      -pcmk_err_bad_nvpair,
+    },
+    { "pcmk_rc_already",
+      "Already in requested state",
+      -pcmk_err_already,
+    },
+    { "pcmk_rc_node_unknown",
+      "Node not found",
+      -pcmk_err_node_unknown,
+    },
+    { "pcmk_rc_multiple",
+      "Resource active on multiple nodes",
+      -pcmk_err_multiple,
+    },
+    { "pcmk_rc_cib_corrupt",
+      "Could not parse on-disk configuration",
+      -pcmk_err_cib_corrupt,
+    },
+    { "pcmk_rc_cib_save",
+      "Could not save new configuration to disk",
+      -pcmk_err_cib_save,
+    },
+    { "pcmk_rc_cib_backup",
+      "Could not archive previous configuration",
+      -pcmk_err_cib_backup,
+    },
+    { "pcmk_rc_cib_modified",
+      "On-disk configuration was manually modified",
+      -pcmk_err_cib_modified,
+    },
+    { "pcmk_rc_diff_resync",
+      "Application of update diff failed, requesting full refresh",
+      -pcmk_err_diff_resync,
+    },
+    { "pcmk_rc_diff_failed",
+      "Application of update diff failed",
+      -pcmk_err_diff_failed,
+    },
+    { "pcmk_rc_old_data",
+      "Update was older than existing configuration",
+      -pcmk_err_old_data,
+    },
+    { "pcmk_rc_transform_failed",
+      "Schema transform failed",
+      -pcmk_err_transform_failed,
+    },
+    { "pcmk_rc_schema_unchanged",
+      "Schema is already the latest available",
+      -pcmk_err_schema_unchanged,
+    },
+    { "pcmk_rc_schema_validation",
+      "Update does not conform to the configured schema",
+      -pcmk_err_schema_validation,
+    },
+    { "pcmk_rc_no_quorum",
+      "Operation requires quorum",
+      -pcmk_err_no_quorum,
+    },
+};
+
+#define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info))
+
+/*!
+ * \brief Get a return code constant name as a string
+ *
+ * \param[in] rc  Integer return code to convert
+ *
+ * \return String of constant name corresponding to rc
+ */
+const char *
+pcmk_rc_name(int rc)
+{
+    if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
+        return pcmk__rcs[pcmk_rc_error - rc].name;
+    }
+    switch (rc) {
+        case pcmk_rc_ok:        return "pcmk_rc_ok";
+        case E2BIG:             return "E2BIG";
+        case EACCES:            return "EACCES";
+        case EADDRINUSE:        return "EADDRINUSE";
+        case EADDRNOTAVAIL:     return "EADDRNOTAVAIL";
+        case EAFNOSUPPORT:      return "EAFNOSUPPORT";
+        case EAGAIN:            return "EAGAIN";
+        case EALREADY:          return "EALREADY";
+        case EBADF:             return "EBADF";
+        case EBADMSG:           return "EBADMSG";
+        case EBUSY:             return "EBUSY";
+        case ECANCELED:         return "ECANCELED";
+        case ECHILD:            return "ECHILD";
+        case ECOMM:             return "ECOMM";
+        case ECONNABORTED:      return "ECONNABORTED";
+        case ECONNREFUSED:      return "ECONNREFUSED";
+        case ECONNRESET:        return "ECONNRESET";
+        /* case EDEADLK:        return "EDEADLK"; */
+        case EDESTADDRREQ:      return "EDESTADDRREQ";
+        case EDOM:              return "EDOM";
+        case EDQUOT:            return "EDQUOT";
+        case EEXIST:            return "EEXIST";
+        case EFAULT:            return "EFAULT";
+        case EFBIG:             return "EFBIG";
+        case EHOSTDOWN:         return "EHOSTDOWN";
+        case EHOSTUNREACH:      return "EHOSTUNREACH";
+        case EIDRM:             return "EIDRM";
+        case EILSEQ:            return "EILSEQ";
+        case EINPROGRESS:       return "EINPROGRESS";
+        case EINTR:             return "EINTR";
+        case EINVAL:            return "EINVAL";
+        case EIO:               return "EIO";
+        case EISCONN:           return "EISCONN";
+        case EISDIR:            return "EISDIR";
+        case ELIBACC:           return "ELIBACC";
+        case ELOOP:             return "ELOOP";
+        case EMFILE:            return "EMFILE";
+        case EMLINK:            return "EMLINK";
+        case EMSGSIZE:          return "EMSGSIZE";
+#ifdef EMULTIHOP // Not available on OpenBSD
+        case EMULTIHOP:         return "EMULTIHOP";
+#endif
+        case ENAMETOOLONG:      return "ENAMETOOLONG";
+        case ENETDOWN:          return "ENETDOWN";
+        case ENETRESET:         return "ENETRESET";
+        case ENETUNREACH:       return "ENETUNREACH";
+        case ENFILE:            return "ENFILE";
+        case ENOBUFS:           return "ENOBUFS";
+        case ENODATA:           return "ENODATA";
+        case ENODEV:            return "ENODEV";
+        case ENOENT:            return "ENOENT";
+        case ENOEXEC:           return "ENOEXEC";
+        case ENOKEY:            return "ENOKEY";
+        case ENOLCK:            return "ENOLCK";
+#ifdef ENOLINK // Not available on OpenBSD
+        case ENOLINK:           return "ENOLINK";
+#endif
+        case ENOMEM:            return "ENOMEM";
+        case ENOMSG:            return "ENOMSG";
+        case ENOPROTOOPT:       return "ENOPROTOOPT";
+        case ENOSPC:            return "ENOSPC";
+        case ENOSR:             return "ENOSR";
+        case ENOSTR:            return "ENOSTR";
+        case ENOSYS:            return "ENOSYS";
+        case ENOTBLK:           return "ENOTBLK";
+        case ENOTCONN:          return "ENOTCONN";
+        case ENOTDIR:           return "ENOTDIR";
+        case ENOTEMPTY:         return "ENOTEMPTY";
+        case ENOTSOCK:          return "ENOTSOCK";
+#if ENOTSUP != EOPNOTSUPP
+        case ENOTSUP:           return "ENOTSUP";
+#endif
+        case ENOTTY:            return "ENOTTY";
+        case ENOTUNIQ:          return "ENOTUNIQ";
+        case ENXIO:             return "ENXIO";
+        case EOPNOTSUPP:        return "EOPNOTSUPP";
+        case EOVERFLOW:         return "EOVERFLOW";
+        case EPERM:             return "EPERM";
+        case EPFNOSUPPORT:      return "EPFNOSUPPORT";
+        case EPIPE:             return "EPIPE";
+        case EPROTO:            return "EPROTO";
+        case EPROTONOSUPPORT:   return "EPROTONOSUPPORT";
+        case EPROTOTYPE:        return "EPROTOTYPE";
+        case ERANGE:            return "ERANGE";
+        case EREMOTE:           return "EREMOTE";
+        case EREMOTEIO:         return "EREMOTEIO";
+        case EROFS:             return "EROFS";
+        case ESHUTDOWN:         return "ESHUTDOWN";
+        case ESPIPE:            return "ESPIPE";
+        case ESOCKTNOSUPPORT:   return "ESOCKTNOSUPPORT";
+        case ESRCH:             return "ESRCH";
+        case ESTALE:            return "ESTALE";
+        case ETIME:             return "ETIME";
+        case ETIMEDOUT:         return "ETIMEDOUT";
+        case ETXTBSY:           return "ETXTBSY";
+        case EUNATCH:           return "EUNATCH";
+        case EUSERS:            return "EUSERS";
+        /* case EWOULDBLOCK:    return "EWOULDBLOCK"; */
+        case EXDEV:             return "EXDEV";
+
+#ifdef EBADE // Not available on OS X
+        case EBADE:             return "EBADE";
+        case EBADFD:            return "EBADFD";
+        case EBADSLT:           return "EBADSLT";
+        case EDEADLOCK:         return "EDEADLOCK";
+        case EBADR:             return "EBADR";
+        case EBADRQC:           return "EBADRQC";
+        case ECHRNG:            return "ECHRNG";
+#ifdef EISNAM // Not available on OS X, Illumos, Solaris
+        case EISNAM:            return "EISNAM";
+        case EKEYEXPIRED:       return "EKEYEXPIRED";
+        case EKEYREJECTED:      return "EKEYREJECTED";
+        case EKEYREVOKED:       return "EKEYREVOKED";
+#endif
+        case EL2HLT:            return "EL2HLT";
+        case EL2NSYNC:          return "EL2NSYNC";
+        case EL3HLT:            return "EL3HLT";
+        case EL3RST:            return "EL3RST";
+        case ELIBBAD:           return "ELIBBAD";
+        case ELIBMAX:           return "ELIBMAX";
+        case ELIBSCN:           return "ELIBSCN";
+        case ELIBEXEC:          return "ELIBEXEC";
+#ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
+        case ENOMEDIUM:         return "ENOMEDIUM";
+        case EMEDIUMTYPE:       return "EMEDIUMTYPE";
+#endif
+        case ENONET:            return "ENONET";
+        case ENOPKG:            return "ENOPKG";
+        case EREMCHG:           return "EREMCHG";
+        case ERESTART:          return "ERESTART";
+        case ESTRPIPE:          return "ESTRPIPE";
+#ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
+        case EUCLEAN:           return "EUCLEAN";
+#endif
+        case EXFULL:            return "EXFULL";
+#endif // EBADE
+        default:                return "Unknown";
+    }
+}
+
+/*!
+ * \brief Get a user-friendly description of a return code
+ *
+ * \param[in] rc  Integer return code to convert
+ *
+ * \return String description of rc
+ */
+const char *
+pcmk_rc_str(int rc)
+{
+    if (rc == pcmk_rc_ok) {
+        return "OK";
+    }
+    if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
+        return pcmk__rcs[pcmk_rc_error - rc].desc;
+    }
+    if (rc < 0) {
+        return "Unknown error";
+    }
+    return strerror(rc);
+}
+
+// This returns negative values for errors
+//! \deprecated Use standard return codes instead
+int
+pcmk_rc2legacy(int rc)
+{
+    if (rc >= 0) {
+        return -rc; // OK or system errno
+    }
+    if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
+        return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
+    }
+    return -pcmk_err_generic;
+}
+
+//! \deprecated Use standard return codes instead
+int
+pcmk_legacy2rc(int legacy_rc)
+{
+    legacy_rc = abs(legacy_rc);
+    switch (legacy_rc) {
+        case pcmk_err_no_quorum:            return pcmk_rc_no_quorum;
+        case pcmk_err_schema_validation:    return pcmk_rc_schema_validation;
+        case pcmk_err_schema_unchanged:     return pcmk_rc_schema_unchanged;
+        case pcmk_err_transform_failed:     return pcmk_rc_transform_failed;
+        case pcmk_err_old_data:             return pcmk_rc_old_data;
+        case pcmk_err_diff_failed:          return pcmk_rc_diff_failed;
+        case pcmk_err_diff_resync:          return pcmk_rc_diff_resync;
+        case pcmk_err_cib_modified:         return pcmk_rc_cib_modified;
+        case pcmk_err_cib_backup:           return pcmk_rc_cib_backup;
+        case pcmk_err_cib_save:             return pcmk_rc_cib_save;
+        case pcmk_err_cib_corrupt:          return pcmk_rc_cib_corrupt;
+        case pcmk_err_multiple:             return pcmk_rc_multiple;
+        case pcmk_err_node_unknown:         return pcmk_rc_node_unknown;
+        case pcmk_err_already:              return pcmk_rc_already;
+        case pcmk_err_bad_nvpair:           return pcmk_rc_bad_nvpair;
+        case pcmk_err_unknown_format:       return pcmk_rc_unknown_format;
+        case pcmk_err_generic:              return pcmk_rc_error;
+        case pcmk_ok:                       return pcmk_rc_ok;
+        default:                            return legacy_rc; // system errno
+    }
+}
+
+// Exit status codes
+
 const char *
 crm_exit_name(crm_exit_t exit_code)
 {
@@ -347,26 +517,17 @@ crm_exit_str(crm_exit_t exit_code)
         case CRM_EX_TIMEOUT: return "Timeout occurred";
         case CRM_EX_MAX: return "Error occurred";
     }
-    if (exit_code > 128) {
+    if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
         return "Interrupted by signal";
     }
     return "Unknown exit status";
 }
 
-/*!
- * \brief Map an errno to a similar exit status
- *
- * \param[in] errno  Error number to map
- *
- * \return Exit status corresponding to errno
- */
+//! \deprecated Use standard return codes and pcmk_rc2exitc() instead
 crm_exit_t
 crm_errno2exit(int rc)
 {
     rc = abs(rc); // Convenience for functions that return -errno
-    if (rc == EOPNOTSUPP) {
-        rc = ENOTSUP; // Values are same on Linux, can't use both in case
-    }
     switch (rc) {
         case pcmk_ok:
             return CRM_EX_OK;
@@ -384,6 +545,48 @@ crm_errno2exit(int rc)
         case pcmk_err_bad_nvpair:
             return CRM_EX_INVALID_PARAM;
 
+        case pcmk_err_already:
+            return CRM_EX_EXISTS;
+
+        case pcmk_err_multiple:
+            return CRM_EX_MULTIPLE;
+
+        case pcmk_err_node_unknown:
+        case pcmk_err_unknown_format:
+            return CRM_EX_NOSUCH;
+
+        default:
+            return pcmk_rc2exitc(rc); // system errno
+    }
+}
+
+/*!
+ * \brief Map a function return code to the most similar exit code
+ *
+ * \param[in] rc  Function return code
+ *
+ * \return Most similar exit code
+ */
+crm_exit_t
+pcmk_rc2exitc(int rc)
+{
+    switch (rc) {
+        case pcmk_rc_ok:
+            return CRM_EX_OK;
+
+        case pcmk_rc_no_quorum:
+            return CRM_EX_QUORUM;
+
+        case pcmk_rc_old_data:
+            return CRM_EX_OLD;
+
+        case pcmk_rc_schema_validation:
+        case pcmk_rc_transform_failed:
+            return CRM_EX_CONFIG;
+
+        case pcmk_rc_bad_nvpair:
+            return CRM_EX_INVALID_PARAM;
+
         case EACCES:
             return CRM_EX_INSUFFICIENT_PRIV;
 
@@ -414,22 +617,25 @@ crm_errno2exit(int rc)
             return CRM_EX_DISCONNECT;
 
         case EEXIST:
-        case pcmk_err_already:
+        case pcmk_rc_already:
             return CRM_EX_EXISTS;
 
         case EIO:
             return CRM_EX_IOERR;
 
         case ENOTSUP:
+#if EOPNOTSUPP != ENOTSUP
+        case EOPNOTSUPP:
+#endif
             return CRM_EX_UNIMPLEMENT_FEATURE;
 
         case ENOTUNIQ:
-        case pcmk_err_multiple:
+        case pcmk_rc_multiple:
             return CRM_EX_MULTIPLE;
 
         case ENXIO:
-        case pcmk_err_node_unknown:
-        case pcmk_err_unknown_format:
+        case pcmk_rc_node_unknown:
+        case pcmk_rc_unknown_format:
             return CRM_EX_NOSUCH;
 
         case ETIME:
@@ -441,6 +647,8 @@ crm_errno2exit(int rc)
     }
 }
 
+// Other functions
+
 const char *
 bz2_strerror(int rc)
 {
diff --git a/tools/crm_error.c b/tools/crm_error.c
index f6dc73c..0dcae05 100644
--- a/tools/crm_error.c
+++ b/tools/crm_error.c
@@ -1,21 +1,10 @@
-/* 
- * Copyright 2012-2018 the Pacemaker project contributors
+/*
+ * Copyright 2012-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
- * 
- * 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
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
@@ -33,12 +22,31 @@ static struct crm_option long_options[] = {
      "\n\t\t\tUseful for looking for sources of the error in source code"},
 
     {"list",    0, 0, 'l', "\tShow all known errors."},
-    {"exit",    0, 0, 'X', "\tInterpret as exit code rather than function return value"},
+    {"exit",    0, 0, 'X', "\tInterpret as exit code rather than legacy function return value"},
+    {"rc",      0, 0, 'r', "\tInterpret as return code rather than legacy function return value"},
 
     {0, 0, 0, 0}
 };
 /* *INDENT-ON* */
 
+static bool as_exit_code = false;
+static bool as_rc = false;
+
+static void
+get_strings(int rc, const char **name, const char **str)
+{
+    if (as_exit_code) {
+        *str = crm_exit_str((crm_exit_t) rc);
+        *name = crm_exit_name(rc);
+    } else if (as_rc) {
+        *str = pcmk_rc_str(rc);
+        *name = pcmk_rc_name(rc);
+    } else {
+        *str = pcmk_strerror(rc);
+        *name = pcmk_errorname(rc);
+    }
+}
+
 int
 main(int argc, char **argv)
 {
@@ -49,10 +57,12 @@ main(int argc, char **argv)
 
     bool do_list = FALSE;
     bool with_name = FALSE;
-    bool as_exit_code = FALSE;
+
+    const char *name = NULL;
+    const char *desc = NULL;
 
     crm_log_cli_init("crm_error");
-    crm_set_options(NULL, "[options] -- rc", long_options,
+    crm_set_options(NULL, "[options] -- <rc> [...]", long_options,
                     "Tool for displaying the textual name or description of a reported error code");
 
     while (flag >= 0) {
@@ -73,6 +83,9 @@ main(int argc, char **argv)
             case 'l':
                 do_list = TRUE;
                 break;
+            case 'r':
+                as_rc = true;
+                break;
             case 'X':
                 as_exit_code = TRUE;
                 break;
@@ -83,30 +96,43 @@ main(int argc, char **argv)
     }
 
     if(do_list) {
-        for (rc = 0; rc < 256; rc++) {
-            const char *name = as_exit_code? crm_exit_name(rc) : pcmk_errorname(rc);
-            const char *desc = as_exit_code? crm_exit_str(rc) : pcmk_strerror(rc);
+        int start, end, width;
+
+        // 256 is a hacky magic number that "should" be enough
+        if (as_rc) {
+            start = pcmk_rc_error - 256;
+            end = PCMK_CUSTOM_OFFSET;
+            width = 4;
+        } else {
+            start = 0;
+            end = 256;
+            width = 3;
+        }
+
+        for (rc = start; rc < end; rc++) {
+            if (rc == (pcmk_rc_error + 1)) {
+                // Values in between are reserved for callers, no use iterating
+                rc = pcmk_rc_ok;
+            }
+            get_strings(rc, &name, &desc);
             if (!name || !strcmp(name, "Unknown") || !strcmp(name, "CRM_EX_UNKNOWN")) {
-                /* Unknown */
+                // Undefined
             } else if(with_name) {
-                printf("%.3d: %-26s  %s\n", rc, name, desc);
+                printf("% .*d: %-26s  %s\n", width, rc, name, desc);
             } else {
-                printf("%.3d: %s\n", rc, desc);
+                printf("% .*d: %s\n", width, rc, desc);
             }
         }
-        return CRM_EX_OK;
-    }
 
-    for (lpc = optind; lpc < argc; lpc++) {
-        const char *str, *name;
-
-        rc = crm_atoi(argv[lpc], NULL);
-        str = as_exit_code? crm_exit_str(rc) : pcmk_strerror(rc);
-        if(with_name) {
-            name = as_exit_code? crm_exit_name(rc) : pcmk_errorname(rc);
-            printf("%s - %s\n", name, str);
-        } else {
-            printf("%s\n", str);
+    } else {
+        for (lpc = optind; lpc < argc; lpc++) {
+            rc = crm_atoi(argv[lpc], NULL);
+            get_strings(rc, &name, &desc);
+            if (with_name) {
+                printf("%s - %s\n", name, desc);
+            } else {
+                printf("%s\n", desc);
+            }
         }
     }
     return CRM_EX_OK;
-- 
1.8.3.1