diff --git a/doc/sphinx/Pacemaker_Development/c.rst b/doc/sphinx/Pacemaker_Development/c.rst index bfdff64633..8d879617f1 100644 --- a/doc/sphinx/Pacemaker_Development/c.rst +++ b/doc/sphinx/Pacemaker_Development/c.rst @@ -1,1149 +1,1151 @@ .. index:: single: C pair: C; guidelines C Coding Guidelines ------------------- Pacemaker is a large project accepting contributions from developers with a wide range of skill levels and organizational affiliations, and maintained by multiple people over long periods of time. Following consistent guidelines makes reading, writing, and reviewing code easier, and helps avoid common mistakes. Some existing Pacemaker code does not follow these guidelines, for historical reasons and API backward compatibility, but new code should. Code Organization ################# Pacemaker's C code is organized as follows: +-----------------+-----------------------------------------------------------+ | Directory | Contents | +=================+===========================================================+ | daemons | the Pacemaker daemons (pacemakerd, pacemaker-based, etc.) | +-----------------+-----------------------------------------------------------+ | include | header files for library APIs | +-----------------+-----------------------------------------------------------+ | lib | libraries | +-----------------+-----------------------------------------------------------+ | tools | command-line tools | +-----------------+-----------------------------------------------------------+ Source file names should be unique across the entire project, to allow for individual tracing via ``PCMK_trace_files``. .. index:: single: C; library single: C library Pacemaker Libraries ################### +---------------+---------+---------------+---------------------------+-------------------------------------+ | Library | Symbol | Source | API Headers | Description | | | prefix | location | | | +===============+=========+===============+===========================+=====================================+ | libcib | cib | lib/cib | | include/crm/cib.h | .. index:: | | | | | | include/crm/cib/* | single: C library; libcib | | | | | | single: libcib | | | | | | | | | | | | API for pacemaker-based IPC and | | | | | | the CIB | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libcrmcluster | pcmk | lib/cluster | | include/crm/cluster.h | .. index:: | | | | | | include/crm/cluster/* | single: C library; libcrmcluster | | | | | | single: libcrmcluster | | | | | | | | | | | | Abstract interface to underlying | | | | | | cluster layer | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libcrmcommon | pcmk | lib/common | | include/crm/common/* | .. index:: | | | | | | some of include/crm/* | single: C library; libcrmcommon | | | | | | single: libcrmcommon | | | | | | | | | | | | Everything else | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libcrmservice | svc | lib/services | | include/crm/services.h | .. index:: | | | | | | single: C library; libcrmservice | | | | | | single: libcrmservice | | | | | | | | | | | | Abstract interface to supported | | | | | | resource types (OCF, LSB, etc.) | +---------------+---------+---------------+---------------------------+-------------------------------------+ | liblrmd | lrmd | lib/lrmd | | include/crm/lrmd*.h | .. index:: | | | | | | single: C library; liblrmd | | | | | | single: liblrmd | | | | | | | | | | | | API for pacemaker-execd IPC | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libpacemaker | pcmk | lib/pacemaker | | include/pacemaker*.h | .. index:: | | | | | | include/pcmki/* | single: C library; libpacemaker | | | | | | single: libpacemaker | | | | | | | | | | | | High-level APIs equivalent to | | | | | | command-line tool capabilities | | | | | | (and high-level internal APIs) | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libpe_rules | pe | lib/pengine | | include/crm/pengine/* | .. index:: | | | | | | single: C library; libpe_rules | | | | | | single: libpe_rules | | | | | | | | | | | | Deprecated APIs related to | | | | | | evaluating rules | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libpe_status | pe | lib/pengine | | include/crm/pengine/* | .. index:: | | | | | | single: C library; libpe_status | | | | | | single: libpe_status | | | | | | | | | | | | Low-level scheduler functionality | +---------------+---------+---------------+---------------------------+-------------------------------------+ | libstonithd | stonith | lib/fencing | | include/crm/stonith-ng.h| .. index:: | | | | | | include/crm/fencing/* | single: C library; libstonithd | | | | | | single: libstonithd | | | | | | | | | | | | API for pacemaker-fenced IPC | +---------------+---------+---------------+---------------------------+-------------------------------------+ Public versus Internal APIs ___________________________ Pacemaker libraries have both internal and public APIs. Internal APIs are those used only within Pacemaker; public APIs are those offered (via header files and documentation) for external code to use. Generic functionality needed by Pacemaker itself, such as string processing or XML processing, should remain internal, while functions providing useful high-level access to Pacemaker capabilities should be public. When in doubt, keep APIs internal, because it's easier to expose a previously internal API than hide a previously public API. Internal APIs can be changed as needed. The public API/ABI should maintain a degree of stability so that external applications using it do not need to be rewritten or rebuilt frequently. Many OSes/distributions avoid breaking API/ABI compatibility within a major release, so if Pacemaker breaks compatibility, that significantly delays when OSes can package the new version. Therefore, changes to public APIs should be backward-compatible (as detailed throughout this chapter), unless we are doing a (rare) release where we specifically intend to break compatibility. External applications known to use Pacemaker's public C API include `sbd `_ and dlm_controld. .. index:: pair: C; naming API Symbol Naming _________________ Exposed API symbols (non-``static`` function names, ``struct`` and ``typedef`` names in header files, etc.) must begin with the prefix appropriate to the library (shown in the table at the beginning of this section). This reduces the chance of naming collisions when external software links against the library. The prefix is usually lowercase but may be all-caps for some defined constants and macros. Public API symbols should follow the library prefix with a single underbar (for example, ``pcmk_something``), and internal API symbols with a double underbar (for example, ``pcmk__other_thing``). File-local symbols (such as static functions) and non-library code do not require a prefix, though a unique prefix indicating an executable (controld, crm_mon, etc.) can be helpful when symbols are shared between multiple source files for the executable. API Header File Naming ______________________ * Internal API headers should be named ending in ``_internal.h``, in the same location as public headers, with the exception of libpacemaker, which for historical reasons keeps internal headers in ``include/pcmki/pcmki_*.h``). * If a library needs to share symbols just within the library, header files for these should be named ending in ``_private.h`` and located in the library source directory (not ``include``). Such functions should be declared as ``G_GNUC_INTERNAL``, to aid compiler efficiency (glib defines this symbol appropriately for the compiler). Header files that are not library API are kept in the same directory as the source code they're included from. The easiest way to tell what kind of API a symbol is, is to see where it's declared. If it's in a public header, it's public API; if it's in an internal header, it's internal API; if it's in a library-private header, it's library-private API; otherwise, it's not an API. .. index:: pair: C; API documentation single: Doxygen API Documentation _________________ Pacemaker uses `Doxygen `_ to automatically generate its `online API documentation `_, so all public API (header files, functions, structs, enums, etc.) should be documented with Doxygen comment blocks. Other code may be documented in the same way if desired, with an ``\internal`` tag in the Doxygen comment. Simple example of an internal function with a Doxygen comment block: .. code-block:: c /*! * \internal * \brief Return string length plus 1 * * Return the number of characters in a given string, plus one. * * \param[in] s A string (must not be NULL) * * \return The length of \p s plus 1. */ static int f(const char *s) { return strlen(s) + 1; } Function arguments are marked as ``[in]`` for input only, ``[out]`` for output only, or ``[in,out]`` for both input and output. ``[in,out]`` should be used for struct pointer arguments if the function can change any data accessed via the pointer. For example, if the struct contains a ``GHashTable *`` member, the argument should be marked as ``[in,out]`` if the function inserts data into the table, even if the struct members themselves are not changed. However, an argument is not ``[in,out]`` if something reachable via the argument is modified via a separate argument. For example, both ``pcmk_resource_t`` and ``pcmk_node_t`` contain pointers to their ``pcmk_scheduler_t`` and thus indirectly to each other, but if the function modifies the resource via the resource argument, the node argument does not have to be ``[in,out]``. Public API Deprecation ______________________ Public APIs may not be removed in most Pacemaker releases, but they may be deprecated. When a public API is deprecated, it is moved to a header whose name ends in ``compat.h``. The original header includes the compatibility header only if the ``PCMK_ALLOW_DEPRECATED`` symbol is undefined or defined to 1. This allows external code to continue using the deprecated APIs, but internal code is prevented from using them because the ``crm_internal.h`` header defines the symbol to 0. .. index:: pair: C; boilerplate pair: license; C pair: copyright; C C Boilerplate ############# Every C file should start with a short copyright and license notice: .. code-block:: c /* * Copyright the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under WITHOUT ANY WARRANTY. */ ** should follow the policy set forth in the `COPYING `_ file, generally one of "GNU General Public License version 2 or later (GPLv2+)" or "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)". Header files should additionally protect against multiple inclusion by defining a unique symbol of the form ``PCMK____H``, and declare C compatibility for inclusion by C++. For example: .. code-block:: c #ifndef PCMK__MY_HEADER__H #define PCMK__MY_HEADER__H // put #include directives here #ifdef __cplusplus extern "C" { #endif // put header code here #ifdef __cplusplus } #endif #endif // PCMK__MY_HEADER__H Public API header files should give a Doxygen file description at the top of the header code. For example: .. code-block:: c /*! * \file * \brief My brief description here * \ingroup core */ .. index:: pair: C; whitespace Line Formatting ############### * Indentation must be 4 spaces, no tabs. * Do not leave trailing whitespace. * Lines should be no longer than 80 characters unless limiting line length hurts readability. .. index:: pair: C; comment Comments ######## .. code-block:: c /* Single-line comments may look like this */ // ... or this /* Multi-line comments should start immediately after the comment opening. * Subsequent lines should start with an aligned asterisk. The comment * closing should be aligned and on a line by itself. */ .. index:: pair: C; operator Operators ######### .. code-block:: c // Operators have spaces on both sides x = a; /* (1) Do not rely on operator precedence; use parentheses when mixing * operators with different priority, for readability. * (2) No space is used after an opening parenthesis or before a closing * parenthesis. */ x = a + b - (c * d); .. index:: single: C; if single: C; else single: C; while single: C; for single: C; switch Control Statements (if, else, while, for, switch) ################################################# .. code-block:: c /* * (1) The control keyword is followed by a space, a left parenthesis * without a space, the condition, a right parenthesis, a space, and the * opening bracket on the same line. * (2) Always use braces around control statement blocks, even if they only * contain one line. This makes code review diffs smaller if a line gets * added in the future, and avoids the chance of bad indenting making a * line incorrectly appear to be part of the block. * (3) The closing bracket is on a line by itself. */ if (v < 0) { return 0; } /* "else" and "else if" are on the same line with the previous ending brace * and next opening brace, separated by a space. Blank lines may be used * between blocks to help readability. */ if (v > 0) { return 0; } else if (a == 0) { return 1; } else { return 2; } /* Do not use assignments in conditions. This ensures that the developer's * intent is always clear, makes code reviews easier, and reduces the chance * of using assignment where comparison is intended. */ // Do this ... a = f(); if (a) { return 0; } // ... NOT this if (a = f()) { return 0; } /* It helps readability to use the "!" operator only in boolean * comparisons, and explicitly compare numeric values against 0, * pointers against NULL, etc. This helps remind the reader of the * type being compared. */ int i = 0; char *s = NULL; bool cond = false; if (!cond) { return 0; } if (i == 0) { return 0; } if (s == NULL) { return 0; } /* In a "switch" statement, indent "case" one level, and indent the body of * each "case" another level. */ switch (expression) { case 0: command1; break; case 1: command2; break; default: command3; break; } .. index:: pair: C; macro Macros ###### Macros are a powerful but easily misused feature of the C preprocessor, and Pacemaker uses a lot of obscure macro features. If you need to brush up, the `GCC documentation for macros `_ is excellent. Some common issues: * Beware of side effects in macro arguments that may be evaluated more than once * Always parenthesize macro arguments used in the macro body to avoid precedence issues if the argument is an expression * Multi-statement macro bodies should be enclosed in do...while(0) to make them behave more like a single statement and avoid control flow issues Often, a static inline function defined in a header is preferable to a macro, to avoid the numerous issues that plague macros and gain the benefit of argument and return value type checking. .. index:: pair: C; memory Memory Management ################# * Always use ``calloc()`` rather than ``malloc()``. It has no additional cost on modern operating systems, and reduces the severity and security risks of uninitialized memory usage bugs. * Ensure that all dynamically allocated memory is freed when no longer needed, and not used after it is freed. This can be challenging in the more event-driven, callback-oriented sections of code. * Free dynamically allocated memory using the free function corresponding to how it was allocated. For example, use ``free()`` with ``calloc()``, and ``g_free()`` with most glib functions that allocate objects. .. index:: single: C; struct Structures ########## Changes to structures defined in public API headers (adding or removing members, or changing member types) are generally not possible without breaking API compatibility. However, there are exceptions: * Public API structures can be designed such that they can be allocated only via API functions, not declared directly or allocated with standard memory functions using ``sizeof``. * This can be enforced simply by documentating the limitation, in which case new ``struct`` members can be added to the end of the structure without breaking compatibility. * Alternatively, the structure definition can be kept in an internal header, with only a pointer type definition kept in a public header, in which case the structure definition can be changed however needed. .. index:: single: C; variable Variables ######### .. index:: single: C; pointer Pointers ________ .. code-block:: c /* (1) The asterisk goes by the variable name, not the type; * (2) Avoid leaving pointers uninitialized, to lessen the impact of * use-before-assignment bugs */ char *my_string = NULL; // Use space before asterisk and after closing parenthesis in a cast char *foo = (char *) bar; .. index:: single: C; global variable Globals _______ Global variables should be avoided in libraries when possible. State information should instead be passed as function arguments (often as a structure). This is not for thread safety -- Pacemaker's use of forking ensures it will never be threaded -- but it does minimize overhead, improve readability, and avoid obscure side effects. Variable Naming _______________ Time intervals are sometimes represented in Pacemaker code as user-defined text specifications (for example, "10s"), other times as an integer number of seconds or milliseconds, and still other times as a string representation of an integer number. Variables for these should be named with an indication of which is being used (for example, use ``interval_spec``, ``interval_ms``, or ``interval_ms_s`` instead of ``interval``). .. index:: pair: C; booleans pair: C; bool pair: C; gboolean Booleans ________ Booleans in C can be represented by an integer type, ``bool``, or ``gboolean``. Integers are sometimes useful for storing booleans when they must be converted to and from a string, such as an XML attribute value (for which ``crm_element_value_int()`` can be used). Integer booleans use 0 for false and nonzero (usually 1) for true. ``gboolean`` should be used with glib APIs that specify it. ``gboolean`` should always be used with glib's ``TRUE`` and ``FALSE`` constants. Otherwise, ``bool`` should be preferred. ``bool`` should be used with the ``true`` and ``false`` constants from the ``stdbool.h`` header. Do not use equality operators when testing booleans. For example: .. code-block:: c // Do this if (bool1) { fn(); } if (!bool2) { fn2(); } // Not this if (bool1 == true) { fn(); } if (bool2 == false) { fn2(); } // Otherwise there's no logical end ... if ((bool1 == false) == true) { fn(); } .. index:: pair: C; strings String Handling ############### Define Constants for Magic Strings __________________________________ A "magic" string is one used for control purposes rather than human reading, and which must be exactly the same every time it is used. Examples would be configuration option names, XML attribute names, or environment variable names. These should always be defined constants, rather than using the string literal everywhere. If someone mistypes a defined constant, the code won't compile, but if they mistype a literal, it could go unnoticed until a user runs into a problem. String-Related Library Functions ________________________________ Pacemaker's libcrmcommon has a large number of functions to assist in string handling. The most commonly used ones are: * ``pcmk__str_eq()`` tests string equality (similar to ``strcmp()``), but can handle NULL, and takes options for case-insensitive, whether NULL should be considered a match, etc. * ``crm_strdup_printf()`` takes ``printf()``-style arguments and creates a string from them (dynamically allocated, so it must be freed with ``free()``). It asserts on memory failure, so the return value is always non-NULL. String handling functions should almost always be internal API, since Pacemaker isn't intended to be used as a general-purpose library. Most are declared in ``include/crm/common/strings_internal.h``. ``util.h`` has some older ones that are public API (for now, but will eventually be made internal). char*, gchar*, and GString __________________________ When using dynamically allocated strings, be careful to always use the appropriate free function. * ``char*`` strings allocated with something like ``calloc()`` must be freed with ``free()``. Most Pacemaker library functions that allocate strings use this implementation. * glib functions often use ``gchar*`` instead, which must be freed with ``g_free()``. * Occasionally, it's convenient to use glib's flexible ``GString*`` type, which must be freed with ``g_string_free()``. .. index:: pair: C; regular expression Regular Expressions ___________________ - Use ``REG_NOSUB`` with ``regcomp()`` whenever possible, for efficiency. - Be sure to use ``regfree()`` appropriately. .. index:: single: C; enum Enumerations ############ * Enumerations should not have a ``typedef``, and do not require any naming convention beyond what applies to all exposed symbols. * New values should usually be added to the end of public API enumerations, because the compiler will define the values to 0, 1, etc., in the order given, and inserting a value in the middle would change the numerical values of all later values, breaking code compiled with the old values. However, if enum numerical values are explicitly specified rather than left to the compiler, new values can be added anywhere. * When defining constant integer values, enum should be preferred over ``#define`` or ``const`` when possible. This allows type checking without consuming memory. Flag groups ___________ Pacemaker often uses flag groups (also called bit fields or bitmasks) for a collection of boolean options (flags/bits). This is more efficient for storage and manipulation than individual booleans, but its main advantage is when used in public APIs, because using another bit in a bitmask is backward compatible, whereas adding a new function argument (or sometimes even a structure member) is not. .. code-block:: c #include /* (1) Define an enumeration to name the individual flags, for readability. * An enumeration is preferred to a series of "#define" constants * because it is typed, and logically groups the related names. * (2) Define the values using left-shifting, which is more readable and * less error-prone than hexadecimal literals (0x0001, 0x0002, 0x0004, * etc.). * (3) Using a comma after the last entry makes diffs smaller for reviewing * if a new value needs to be added or removed later. */ enum pcmk__some_bitmask_type { pcmk__some_value = (1 << 0), pcmk__other_value = (1 << 1), pcmk__another_value = (1 << 2), }; /* The flag group itself should be an unsigned type from stdint.h (not * the enum type, since it will be a mask of the enum values and not just * one of them). uint32_t is the most common, since we rarely need more than * 32 flags, but a smaller or larger type could be appropriate in some * cases. */ uint32_t flags = pcmk__some_value|pcmk__other_value; /* If the values will be used only with uint64_t, define them accordingly, * to make compilers happier. */ enum pcmk__something_else { pcmk__whatever = (UINT64_C(1) << 0), }; We have convenience functions for checking flags (see ``pcmk_any_flags_set()``, ``pcmk_all_flags_set()``, and ``pcmk_is_set()``) as well as setting and clearing them (see ``pcmk__set_flags_as()`` and ``pcmk__clear_flags_as()``, usually used via wrapper macros defined for specific flag groups). These convenience functions should be preferred to direct bitwise arithmetic, for readability and logging consistency. .. index:: pair: C; function Functions ######### Function Naming _______________ Function names should be unique across the entire project, to allow for individual tracing via ``PCMK_trace_functions``, and make it easier to search code and follow detail logs. .. _sort_func: Sorting ^^^^^^^ A function that sorts an entire list should have ``sort`` in its name. It sorts elements using a :ref:`comparison ` function, which may be either hard-coded or passed as an argument. .. _compare_func: Comparison ^^^^^^^^^^ A comparison function for :ref:`sorting ` should have ``cmp`` in its name and should *not* have ``sort`` in its name. .. _constructor_func: Constructors ^^^^^^^^^^^^ A constructor creates a new dynamically allocated object. It may perform some initialization procedure on the new object. * If the constructor always creates an independent object instance, its name should include ``new``. * If the constructor may add the new object to some existing object, its name should include ``create``. Functions that take the caller's name as an argument ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sometimes, we define a function that uses the ``__FILE__``, ``__func__``, and/or ``__LINE__`` of the caller for logging purposes, often with a wrapper macro that automatically passes them. * The function should take those values as its first arguments. * The function name should end in ``_as()``. * If a wrapper macro is used, its name should be the same without ``_as()``. * See ``pcmk__assert_alloc()`` and ``pcmk__assert_alloc_as()`` as examples. Function Definitions ____________________ .. code-block:: c /* * (1) The return type goes on its own line * (2) The opening brace goes by itself on a line * (3) Use "const" with pointer arguments whenever appropriate, to allow the * function to be used by more callers. */ int my_func1(const char *s) { return 0; } /* Functions with no arguments must explicitly list them as void, * for compatibility with strict compilers */ int my_func2(void) { return 0; } /* * (1) For functions with enough arguments that they must break to the next * line, align arguments with the first argument. * (2) When a function argument is a function itself, use the pointer form. * (3) Declare functions and file-global variables as ``static`` whenever * appropriate. This gains a slight efficiency in shared libraries, and * helps the reader know that it is not used outside the one file. */ static int my_func3(int bar, const char *a, const char *b, const char *c, void (*callback)()) { return 0; } Return Values _____________ Functions that need to indicate success or failure should follow one of the following guidelines. More details, including functions for using them in user messages and converting from one to another, can be found in ``include/crm/common/results.h``. * A **standard Pacemaker return code** is one of the ``pcmk_rc_*`` enum values or a system errno code, as an ``int``. * ``crm_exit_t`` (the ``CRM_EX_*`` enum values) is a system-independent code suitable for the exit status of a process, or for interchange between nodes. + These values need to be kept in sync with the ``ExitStatus`` enum in + ``python/pacemaker/exitstatus.py``. * Other special-purpose status codes exist, such as ``enum ocf_exitcode`` for the possible exit statuses of OCF resource agents (along with some Pacemaker-specific extensions). It is usually obvious when the context calls for such. * Some older Pacemaker APIs use the now-deprecated "legacy" return values of ``pcmk_ok`` or the positive or negative value of one of the ``pcmk_err_*`` constants or system errno codes. * Functions registered with external libraries (as callbacks for example) should use the appropriate signature defined by those libraries, rather than follow Pacemaker guidelines. Of course, functions may have return values that aren't success/failure indicators, such as a pointer, integer count, or bool. :ref:`Comparison ` functions should return * a negative integer if the first argument should sort first * 0 if its arguments are equal for sorting purposes * a positive integer is the second argument should sort first Public API Functions ____________________ Unless we are doing a (rare) release where we break public API compatibility, new public API functions can be added, but existing function signatures (return type, name, and argument types) should not be changed. To work around this, an existing function can become a wrapper for a new function. .. index:: pair: C; logging pair: C; output Logging and Output ################## Logging Vs. Output __________________ Log messages and output messages are logically similar but distinct. Oversimplifying a bit, daemons log, and tools output. Log messages are intended to help with troubleshooting and debugging. They may have a high level of technical detail, and are usually filtered by severity -- for example, the system log by default gets messages of notice level and higher. Output is intended to let the user know what a tool is doing, and is generally terser and less technical, and may even be parsed by scripts. Output might have "verbose" and "quiet" modes, but it is not filtered by severity. Common Guidelines for All Messages __________________________________ * When format strings are used for derived data types whose implementation may vary across platforms (``pid_t``, ``time_t``, etc.), the safest approach is to use ``%lld`` in the format string, and cast the value to ``long long``. * Do not rely on ``%s`` handling ``NULL`` values properly. While the standard library functions might, not all functions using printf-style formatting does, and it's safest to get in the habit of always ensuring format values are non-NULL. If a value can be NULL, the ``pcmk__s()`` function is a convenient way to say "this string if not NULL otherwise this default". * The convenience macros ``pcmk__plural_s()`` and ``pcmk__plural_alt()`` are handy when logging a word that may be singular or plural. Log Levels __________ When to use each log level: * **critical:** fatal error (usually something that would make a daemon exit) * **error:** failure of something that affects the cluster (such as a resource action, fencing action, etc.) or daemon operation * **warning:** minor, potential, or recoverable failures (such as something only affecting a daemon client, or invalid configuration that can be left to default) * **notice:** important successful events (such as a node joining or leaving, resource action results, or configuration changes) * **info:** events that would be helpful with troubleshooting (such as status section updates or elections) * **debug:** information that would be helpful for debugging code or complex problems * **trace:** like debug but for very noisy or low-level stuff By default, critical through notice are logged to the system log and detail log, info is logged to the detail log only, and debug and trace are not logged (if enabled, they go to the detail log only). Logging _______ Pacemaker uses libqb for logging, but wraps it with a higher level of functionality (see ``include/crm/common/logging*h``). A few macros ``crm_err()``, ``crm_warn()``, etc. do most of the heavy lifting. By default, Pacemaker sends logs at notice level and higher to the system log, and logs at info level and higher to the detail log (typically ``/var/log/pacemaker/pacemaker.log``). The intent is that most users will only ever need the system log, but for deeper troubleshooting and developer debugging, the detail log may be helpful, at the cost of being more technical and difficult to follow. The same message can have more detail in the detail log than in the system log, using libqb's "extended logging" feature: .. code-block:: c /* The following will log a simple message in the system log, like: warning: Action failed: Node not found with extra detail in the detail log, like: warning: Action failed: Node not found | rc=-1005 id=hgjjg-51006 */ crm_warn("Action failed: %s " QB_XS " rc=%d id=%s", pcmk_rc_str(rc), rc, id); Assertion Logging _________________ ``pcmk__assert(expr)`` If ``expr`` is false, this will call ``crm_err()`` with a "Triggered fatal assertion" message (with details), then abort execution. This should be used for logic errors that should be impossible (such as a NULL function argument where not accepted) and environmental errors that can't be handled gracefully (for example, memory allocation failures, though returning ``ENOMEM`` is often better). ``CRM_LOG_ASSERT(expr)`` If ``expr`` is false, this will generally log a message without aborting. If the log level is below trace, it just calls ``crm_err()`` with a "Triggered assert" message (with details). If the log level is trace, and the caller is a daemon, then it will fork a child process in which to dump core, as well as logging the message. If the log level is trace, and the caller is not a daemon, then it will behave like ``pcmk__assert()`` (i.e. log and abort). This should be used for logic or protocol errors that require no special handling. ``CRM_CHECK(expr, failed_action)`` If ``expr`` is false, behave like ``CRM_LOG_ASSERT(expr)`` (that is, log a message and dump core if requested) then perform ``failed_action`` (which must not contain ``continue``, ``break``, or ``errno``). This should be used for logic or protocol errors that can be handled, usually by returning an error status. Output ______ Pacemaker has a somewhat complicated system for tool output. The main benefit is that the user can select the output format with the ``--output-as`` option (usually "text" for human-friendly output or "xml" for reliably script-parsable output, though ``crm_mon`` additionally supports "console" and "html"). A custom message can be defined with a unique string identifier, plus implementation functions for each supported format. The caller invokes the message using the identifier. The user selects the output format via ``--output-as``, and the output code automatically calls the appropriate implementation function. Custom messages are useful when you want to output messages that are more complex than a one-line error or informational message, reproducible, and automatically handled by the output formatting system. Custom messages can contain other custom messages. Custom message functions are implemented as follows: Start with the macro ``PCMK__OUTPUT_ARGS``, whose arguments are the message name, followed by the arguments to the message. Then there is the function declaration, for which the arguments are the pointer to the current output object, then a variable argument list. To output a custom message, you first need to create, i.e. register, the custom message that you want to output. Either call ``register_message``, which registers a custom message at runtime, or make use of the collection of predefined custom messages in ``fmt_functions``, which is defined in ``lib/pacemaker/pcmk_output.c``. Once you have the message to be outputted, output it by calling ``message``. Note: The ``fmt_functions`` functions accommodate all of the output formats; the default implementation accommodates any format that isn't explicitly accommodated. The default output provides valid output for any output format, but you may still want to implement a specific output, i.e. xml, text, or html. The ``message`` function automatically knows which implementation to use, because the ``pcmk__output_s`` contains this information. The interface (most importantly ``pcmk__output_t``) is declared in ``include/crm/common/output*h``. See the API comments and existing tools for examples. Some of its important member functions are ``err``, which formats error messages and ``info``, which formats informational messages. Also, ``list_item``, which formats list items, ``begin_list``, which starts lists, and ``end_list``, which ends lists, are important because lists can be useful, yet differently handled by the different output types. .. index:: pair: C; XML XML ### External Libraries __________________ Pacemaker uses `libxml2 `_ and `libxslt `_ to process XML. These libraries implement only version 1.0 of the XML, XPath, and XSLT specifications. Naming ______ Names of functions, constants, and enum values related to XML should contain substrings indicating the type of object they're used with, according to the following convention: * ``xml``: XML subtree, or XML generically * ``xe``: XML element node, including the attributes belonging to an element * ``xa``: XML attribute node * ``xc``: XML comment node Private Data ____________ Libxml2 data structures such as ``xmlNode`` and ``xmlDoc`` contain a ``void *_private`` member for application-specific data. Pacemaker uses this field to store internal bookkeeping data, such as changes relative to another XML tree, or ACLs. XML documents, elements, attributes, and comments have private data. The private data field must be allocated immediately after the node is created and freed immediately before the node is freed. Wrapper Functions _________________ Pacemaker provides wrappers for a variety of libxml2 and libxslt functions. They should be used whenever possible. Some are merely for convenience. However, many perform additional, Pacemaker-specific tasks, such as change tracking, ACL checking, and allocation/deallocation of XML documents and private data. Pacemaker assumes that every XML node is part of a document and has private data allocated. If libxml2 APIs are used directly instead of the wrapper functions, Pacemaker may crash with a segmentation fault, or change tracking and ACL checking may be incorrectly disabled. XPaths ______ Separting XPath element names with ``/`` (specifying each level in the hierarchy explicitly) is more efficient than ``//`` (allowing intermediate levels to be omitted), so it should be used whenever practical. .. index:: single: Makefile.am Makefiles ######### Pacemaker uses `automake `_ for building, so the Makefile.am in each directory should be edited rather than Makefile.in or Makefile, which are automatically generated. * Public API headers are installed (by adding them to a ``HEADERS`` variable in ``Makefile.am``), but internal API headers are not (by adding them to ``noinst_HEADERS``). .. index:: pair: C; vim settings vim Settings ############ Developers who use ``vim`` to edit source code can add the following settings to their ``~/.vimrc`` file to follow Pacemaker C coding guidelines: .. code-block:: none " follow Pacemaker coding guidelines when editing C source code files filetype plugin indent on au FileType c setlocal expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80 autocmd BufNewFile,BufRead *.h set filetype=c let c_space_errors = 1 diff --git a/include/crm/common/results.h b/include/crm/common/results.h index a671cb8efd..60a88ddbc0 100644 --- a/include/crm/common/results.h +++ b/include/crm/common/results.h @@ -1,405 +1,408 @@ /* - * Copyright 2012-2024 the Pacemaker project contributors + * Copyright 2012-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_RESULTS__H #define PCMK__CRM_COMMON_RESULTS__H #include // gboolean #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Function and executable result codes * \ingroup core */ // Lifted from config.h /* The _Noreturn keyword of C11. */ #ifndef _Noreturn # if (defined __cplusplus \ && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \ || (defined _MSC_VER && 1900 <= _MSC_VER))) # define _Noreturn [[noreturn]] # elif ((!defined __cplusplus || defined __clang__) \ && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \ || 4 < __GNUC__ + (7 <= __GNUC_MINOR__))) /* _Noreturn works as-is. */ # elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C # define _Noreturn __attribute__ ((__noreturn__)) # elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0) # define _Noreturn __declspec (noreturn) # else # define _Noreturn # endif #endif /* * 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 that * system errors and custom errors could collide (which did in fact happen * already on one architecture). 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) // NOTE: sbd (as of at least 1.5.2) uses this #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 */ #define pcmk_err_generic 201 #define pcmk_err_no_quorum 202 #define pcmk_err_schema_validation 203 #define pcmk_err_transform_failed 204 #define pcmk_err_old_data 205 // NOTE: sbd (as of at least 1.5.2) uses this #define pcmk_err_diff_failed 206 // NOTE: sbd (as of at least 1.5.2) uses this #define pcmk_err_diff_resync 207 #define pcmk_err_cib_modified 208 #define pcmk_err_cib_backup 209 #define pcmk_err_cib_save 210 #define pcmk_err_schema_unchanged 211 #define pcmk_err_cib_corrupt 212 #define pcmk_err_multiple 213 #define pcmk_err_node_unknown 214 #define pcmk_err_already 215 /* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */ #ifdef __hppa__ #define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */ #define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */ #else #define pcmk_err_bad_nvpair 216 #define pcmk_err_unknown_format 217 #endif /*! * \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_dc = -1040, pcmk_rc_compression = -1039, pcmk_rc_ns_resolution = -1038, pcmk_rc_no_transaction = -1037, pcmk_rc_bad_xml_patch = -1036, pcmk_rc_bad_input = -1035, pcmk_rc_disabled = -1034, pcmk_rc_duplicate_id = -1033, pcmk_rc_unpack_error = -1032, pcmk_rc_invalid_transition = -1031, pcmk_rc_graph_error = -1030, pcmk_rc_dot_error = -1029, pcmk_rc_underflow = -1028, pcmk_rc_no_input = -1027, pcmk_rc_no_output = -1026, pcmk_rc_after_range = -1025, pcmk_rc_within_range = -1024, pcmk_rc_before_range = -1023, pcmk_rc_undetermined = -1022, pcmk_rc_op_unsatisfied = -1021, pcmk_rc_ipc_pid_only = -1020, pcmk_rc_ipc_unresponsive = -1019, pcmk_rc_ipc_unauthorized = -1018, 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 // NOTE: sbd (as of at least 1.5.2) uses this pcmk_rc_ok = 0 // Positive values reserved for system error numbers }; /*! * \enum ocf_exitcode * \brief Exit status codes for resource agents * * The OCF Resource Agent API standard enumerates the possible exit status codes * that agents should return. Besides being used with OCF agents, these values * are also used by the executor as a universal status for all agent standards; * actual results are mapped to these before returning them to clients. */ enum ocf_exitcode { PCMK_OCF_OK = 0, //!< Success // NOTE: booth (as of at least 1.1) uses this value PCMK_OCF_UNKNOWN_ERROR = 1, //!< Unspecified error PCMK_OCF_INVALID_PARAM = 2, //!< Parameter invalid (in local context) PCMK_OCF_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented PCMK_OCF_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges PCMK_OCF_NOT_INSTALLED = 5, //!< Dependencies not available locally PCMK_OCF_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently) // NOTE: booth (as of at least 1.1) uses this value PCMK_OCF_NOT_RUNNING = 7, //!< Service safely stopped PCMK_OCF_RUNNING_PROMOTED = 8, //!< Service active and promoted PCMK_OCF_FAILED_PROMOTED = 9, //!< Service failed and possibly in promoted role PCMK_OCF_DEGRADED = 190, //!< Service active but more likely to fail soon PCMK_OCF_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon /* These two are Pacemaker extensions, not in the OCF standard. The * controller records PCMK_OCF_UNKNOWN for pending actions. * PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support * PCMK_EXEC_NOT_CONNECTED. */ PCMK_OCF_CONNECTION_DIED = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED PCMK_OCF_UNKNOWN = 193, //!< Action is pending }; // NOTE: sbd (as of at least 1.5.2) uses this /*! * \enum crm_exit_e * \brief Exit status codes for tools and daemons * * We want well-specified (i.e. OS-invariant) exit status codes for our daemons * and applications so they can be relied on by callers. (Function return codes * and errno's do not make good exit statuses.) * * The only hard rule is that exit statuses must be between 0 and 255; all else * is convention. Universally, 0 is success, and 1 is generic error (excluding * OSes we don't support -- for example, OpenVMS considers 1 success!). * * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for * application use. OCF adds 8-9 and 190-191. * * sysexits.h was an attempt to give additional meanings, but never really * caught on. It uses 0 and 64-78. * * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command * found but not executable", 127 is "command not found", 128 + n is * "interrupted by signal n"). * * tldp.org recommends 64-113 for application use. * * We try to overlap with the above conventions when practical. + * + * NOTE: When new exit codes are added here, remember to also update + * python/pacemaker/exitstatus.py. */ typedef enum crm_exit_e { // Common convention CRM_EX_OK = 0, //!< Success CRM_EX_ERROR = 1, //!< Unspecified error // LSB + OCF CRM_EX_INVALID_PARAM = 2, //!< Parameter invalid (in local context) CRM_EX_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented CRM_EX_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges CRM_EX_NOT_INSTALLED = 5, //!< Dependencies not available locally CRM_EX_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently) CRM_EX_NOT_RUNNING = 7, //!< Service safely stopped CRM_EX_PROMOTED = 8, //!< Service active and promoted CRM_EX_FAILED_PROMOTED = 9, //!< Service failed and possibly promoted // sysexits.h CRM_EX_USAGE = 64, //!< Command line usage error CRM_EX_DATAERR = 65, //!< User-supplied data incorrect CRM_EX_NOINPUT = 66, //!< Input file not available CRM_EX_NOUSER = 67, //!< User does not exist CRM_EX_NOHOST = 68, //!< Host unknown CRM_EX_UNAVAILABLE = 69, //!< Needed service unavailable CRM_EX_SOFTWARE = 70, //!< Internal software bug CRM_EX_OSERR = 71, //!< External (OS/environmental) problem CRM_EX_OSFILE = 72, //!< System file not usable CRM_EX_CANTCREAT = 73, //!< File couldn't be created CRM_EX_IOERR = 74, //!< File I/O error CRM_EX_TEMPFAIL = 75, //!< Try again CRM_EX_PROTOCOL = 76, //!< Protocol violated CRM_EX_NOPERM = 77, //!< Non-file permission issue CRM_EX_CONFIG = 78, //!< Misconfiguration // Custom CRM_EX_FATAL = 100, //!< Do not respawn CRM_EX_PANIC = 101, //!< Panic the local host CRM_EX_DISCONNECT = 102, //!< Lost connection to something CRM_EX_OLD = 103, //!< Update older than existing config CRM_EX_DIGEST = 104, //!< Digest comparison failed CRM_EX_NOSUCH = 105, //!< Requested item does not exist CRM_EX_QUORUM = 106, //!< Local partition does not have quorum CRM_EX_UNSAFE = 107, //!< Requires --force or new conditions CRM_EX_EXISTS = 108, //!< Requested item already exists CRM_EX_MULTIPLE = 109, //!< Requested item has multiple matches CRM_EX_EXPIRED = 110, //!< Requested item has expired CRM_EX_NOT_YET_IN_EFFECT = 111, //!< Requested item is not in effect CRM_EX_INDETERMINATE = 112, //!< Could not determine status CRM_EX_UNSATISFIED = 113, //!< Requested item does not satisfy constraints CRM_EX_NO_DC = 114, //!< DC is not yet elected, e.g. right after cluster restart // Other CRM_EX_TIMEOUT = 124, //!< Convention from timeout(1) /* Anything above 128 overlaps with some shells' use of these values for * "interrupted by signal N", and so may be unreliable when detected by * shell scripts. */ // OCF Resource Agent API 1.1 CRM_EX_DEGRADED = 190, //!< Service active but more likely to fail soon CRM_EX_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon /* Custom * * This can be used to initialize exit status variables or to indicate that * a command is pending (which is what the controller uses it for). */ CRM_EX_NONE = 193, //!< No exit status available CRM_EX_MAX = 255, //!< Ensure crm_exit_t can hold this } crm_exit_t; /*! * \enum pcmk_exec_status * \brief Execution status * * These codes are used to specify the result of the attempt to execute an * agent, rather than the agent's result itself. */ enum pcmk_exec_status { PCMK_EXEC_UNKNOWN = -2, //!< Used only to initialize variables PCMK_EXEC_PENDING = -1, //!< Action is in progress PCMK_EXEC_DONE, //!< Action completed, result is known PCMK_EXEC_CANCELLED, //!< Action was cancelled PCMK_EXEC_TIMEOUT, //!< Action did not complete in time PCMK_EXEC_NOT_SUPPORTED, //!< Agent does not implement requested action PCMK_EXEC_ERROR, //!< Execution failed, may be retried PCMK_EXEC_ERROR_HARD, //!< Execution failed, do not retry on node PCMK_EXEC_ERROR_FATAL, //!< Execution failed, do not retry anywhere PCMK_EXEC_NOT_INSTALLED, //!< Agent or dependency not available locally PCMK_EXEC_NOT_CONNECTED, //!< No connection to executor PCMK_EXEC_INVALID, //!< Action cannot be attempted (e.g. shutdown) PCMK_EXEC_NO_FENCE_DEVICE, //!< No fence device is configured for target PCMK_EXEC_NO_SECRETS, //!< Necessary CIB secrets are unavailable // Add new values above here then update this one below PCMK_EXEC_MAX = PCMK_EXEC_NO_SECRETS, //!< Maximum value for this enum }; /*! * \enum pcmk_result_type * \brief Types of Pacemaker result codes * * A particular integer can have different meanings within different Pacemaker * result code families. It may be interpretable within zero, one, or multiple * families. * * These values are useful for specifying how an integer result code should be * interpreted in situations involving a generic integer value. For example, a * function that can process multiple types of result codes might accept an * arbitrary integer argument along with a \p pcmk_result_type argument that * specifies how to interpret the integer. */ enum pcmk_result_type { pcmk_result_legacy = 0, //!< Legacy API function return code pcmk_result_rc = 1, //!< Standard Pacemaker return code pcmk_result_exitcode = 2, //!< Exit status code pcmk_result_exec_status = 3, //!< Execution status }; int pcmk_result_get_strings(int code, enum pcmk_result_type type, const char **name, const char **desc); const char *pcmk_rc_name(int rc); // NOTE: sbd (as of at least 1.5.2) uses this const char *pcmk_rc_str(int rc); crm_exit_t pcmk_rc2exitc(int rc); enum ocf_exitcode pcmk_rc2ocf(int rc); int pcmk_rc2legacy(int rc); int pcmk_legacy2rc(int legacy_rc); // NOTE: sbd (as of at least 1.5.2) uses this const char *pcmk_strerror(int rc); const char *pcmk_errorname(int rc); const char *crm_exit_name(crm_exit_t exit_code); // NOTE: sbd (as of at least 1.5.2) uses this const char *crm_exit_str(crm_exit_t exit_code); _Noreturn crm_exit_t crm_exit(crm_exit_t rc); /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork); static inline const char * pcmk_exec_status_str(enum pcmk_exec_status status) { switch (status) { case PCMK_EXEC_PENDING: return "Pending"; case PCMK_EXEC_DONE: return "Done"; case PCMK_EXEC_CANCELLED: return "Cancelled"; case PCMK_EXEC_TIMEOUT: return "Timed out"; case PCMK_EXEC_NOT_SUPPORTED: return "Unsupported"; case PCMK_EXEC_ERROR: return "Error"; case PCMK_EXEC_ERROR_HARD: return "Hard error"; case PCMK_EXEC_ERROR_FATAL: return "Fatal error"; case PCMK_EXEC_NOT_INSTALLED: return "Not installed"; case PCMK_EXEC_NOT_CONNECTED: return "Internal communication failure"; case PCMK_EXEC_INVALID: return "Cannot execute now"; case PCMK_EXEC_NO_FENCE_DEVICE: return "No fence device"; case PCMK_EXEC_NO_SECRETS: return "CIB secrets unavailable"; default: return "Unrecognized status (bug?)"; } } #ifdef __cplusplus } #endif #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #endif diff --git a/python/pacemaker/exitstatus.py b/python/pacemaker/exitstatus.py index 03f7d2c8e2..3f951ce465 100644 --- a/python/pacemaker/exitstatus.py +++ b/python/pacemaker/exitstatus.py @@ -1,68 +1,69 @@ """A module providing constants relating to why a process or function exited.""" __all__ = ["ExitStatus"] -__copyright__ = "Copyright 2023-2024 the Pacemaker project contributors" +__copyright__ = "Copyright 2023-2025 the Pacemaker project contributors" __license__ = "GNU Lesser General Public License version 2.1 or later (LGPLv2.1+)" from enum import IntEnum, unique from pacemaker._library import _libcrmcommon # These values must be kept in sync with include/crm/common/results.h @unique class ExitStatus(IntEnum): """ Exit status codes for a function or process. These constants describe both success and failure conditions. """ OK = 0 ERROR = 1 INVALID_PARAM = 2 UNIMPLEMENT_FEATURE = 3 INSUFFICIENT_PRIV = 4 NOT_INSTALLED = 5 NOT_CONFIGURED = 6 NOT_RUNNING = 7 PROMOTED = 8 FAILED_PROMOTED = 9 USAGE = 64 DATAERR = 65 NOINPUT = 66 NOUSER = 67 NOHOST = 68 UNAVAILABLE = 69 SOFTWARE = 70 OSERR = 71 OSFILE = 72 CANTCREAT = 73 IOERR = 74 TEMPFAIL = 75 PROTOCOL = 76 NOPERM = 77 CONFIG = 78 FATAL = 100 PANIC = 101 DISCONNECT = 102 OLD = 103 DIGEST = 104 NOSUCH = 105 QUORUM = 106 UNSAFE = 107 EXISTS = 108 MULTIPLE = 109 EXPIRED = 110 NOT_YET_IN_EFFECT = 111 INDETERMINATE = 112 UNSATISFIED = 113 + NO_DC = 114 TIMEOUT = 124 DEGRADED = 190 DEGRADED_PROMOTED = 191 NONE = 193 MAX = 255 def __str__(self): """Given an ExitStatus, return the matching error string.""" return _libcrmcommon.crm_exit_str(self.value).decode()