diff --git a/include/crm/common/unittest_internal.h b/include/crm/common/unittest_internal.h index 096b2072d3..4070539530 100644 --- a/include/crm/common/unittest_internal.h +++ b/include/crm/common/unittest_internal.h @@ -1,56 +1,68 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #ifndef CRM_COMMON_UNITTEST_INTERNAL__H #define CRM_COMMON_UNITTEST_INTERNAL__H /* internal unit testing related utilities */ -/* A cmocka-like assert macro for use in unit testing. This one verifies that - * an expression aborts through CRM_ASSERT, erroring out if that is not the case. +/*! + * \internal + * \brief Assert that a statement aborts through CRM_ASSERT(). * - * This macro works by running the expression in a forked child process with core - * dumps disabled (CRM_ASSERT calls abort(), which will write out a core dump). - * The parent waits for the child to exit and checks why. If the child received - * a SIGABRT, the test passes. For all other cases, the test fails. + * \param[in] stmt Statement to execute; can be an expression. + * + * A cmocka-like assert macro for use in unit testing. This one verifies that a + * statement aborts through CRM_ASSERT(), erroring out if that is not the case. + * + * This macro works by running the statement in a forked child process with core + * dumps disabled (CRM_ASSERT() calls \c abort(), which will write out a core + * dump). The parent waits for the child to exit and checks why. If the child + * received a \c SIGABRT, the test passes. For all other cases, the test fails. + * + * \note If cmocka's expect_*() or will_return() macros are called along with + * pcmk__assert_asserts(), they must be called within a block that is + * passed as the \c stmt argument. That way, the values are added only to + * the child's queue. Otherwise, values added to the parent's queue will + * never be popped, and the test will fail. */ -#define pcmk__assert_asserts(expr) \ +#define pcmk__assert_asserts(stmt) \ do { \ pid_t p = fork(); \ if (p == 0) { \ struct rlimit cores = { 0, 0 }; \ setrlimit(RLIMIT_CORE, &cores); \ - expr; \ + stmt; \ _exit(0); \ } else if (p > 0) { \ int wstatus = 0; \ if (waitpid(p, &wstatus, 0) == -1) { \ fail_msg("waitpid failed"); \ } \ if (!(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)) { \ - fail_msg("expr terminated without asserting"); \ + fail_msg("statement terminated in child without asserting"); \ } \ } else { \ fail_msg("unable to fork for assert test"); \ } \ } while (0); #endif /* CRM_COMMON_UNITTEST_INTERNAL__H */ diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index b11ff2b4c9..3989751607 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -1,294 +1,300 @@ /* * Copyright 2018-2022 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 CRMCOMMON_PRIVATE__H # define CRMCOMMON_PRIVATE__H /* This header is for the sole use of libcrmcommon, so that functions can be * declared with G_GNUC_INTERNAL for efficiency. */ #include // uint8_t, uint32_t #include // bool #include // size_t #include // GList #include // xmlNode, xmlAttr #include // struct qb_ipc_response_header // Decent chunk size for processing large amounts of data #define PCMK__BUFFER_SIZE 4096 #if defined(PCMK__UNIT_TESTING) #undef G_GNUC_INTERNAL #define G_GNUC_INTERNAL #endif /* When deleting portions of an XML tree, we keep a record so we can know later * (e.g. when checking differences) that something was deleted. */ typedef struct pcmk__deleted_xml_s { char *path; int position; } pcmk__deleted_xml_t; typedef struct xml_private_s { long check; uint32_t flags; char *user; GList *acls; GList *deleted_objs; // List of pcmk__deleted_xml_t } xml_private_t; #define pcmk__set_xml_flags(xml_priv, flags_to_set) do { \ (xml_priv)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_NEVER, "XML", "XML node", (xml_priv)->flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_xml_flags(xml_priv, flags_to_clear) do { \ (xml_priv)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_NEVER, "XML", "XML node", (xml_priv)->flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) G_GNUC_INTERNAL void pcmk__xml2text(xmlNode *data, int options, char **buffer, int *offset, int *max, int depth); G_GNUC_INTERNAL void pcmk__buffer_add_char(char **buffer, int *offset, int *max, char c); G_GNUC_INTERNAL bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy); G_GNUC_INTERNAL int pcmk__element_xpath(const char *prefix, xmlNode *xml, char *buffer, int offset, size_t buffer_size); G_GNUC_INTERNAL void pcmk__mark_xml_created(xmlNode *xml); G_GNUC_INTERNAL int pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set); G_GNUC_INTERNAL xmlNode *pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact); G_GNUC_INTERNAL void pcmk__xe_log(int log_level, const char *file, const char *function, int line, const char *prefix, xmlNode *data, int depth, int options); G_GNUC_INTERNAL void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, bool as_diff); G_GNUC_INTERNAL xmlNode *pcmk__xc_match(xmlNode *root, xmlNode *search_comment, bool exact); G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update); G_GNUC_INTERNAL void pcmk__free_acls(GList *acls); G_GNUC_INTERNAL void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user); G_GNUC_INTERNAL bool pcmk__is_user_in_group(const char *user, const char *group); G_GNUC_INTERNAL void pcmk__apply_acl(xmlNode *xml); G_GNUC_INTERNAL void pcmk__apply_creation_acl(xmlNode *xml, bool check_top); G_GNUC_INTERNAL void pcmk__mark_xml_attr_dirty(xmlAttr *a); G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name); static inline const char * pcmk__xml_attr_value(const xmlAttr *attr) { return ((attr == NULL) || (attr->children == NULL))? NULL : (const char *) attr->children->content; } /* * IPC */ #define PCMK__IPC_VERSION 1 #define PCMK__CONTROLD_API_MAJOR "1" #define PCMK__CONTROLD_API_MINOR "0" // IPC behavior that varies by daemon typedef struct pcmk__ipc_methods_s { /*! * \internal * \brief Allocate any private data needed by daemon IPC * * \param[in] api IPC API connection * * \return Standard Pacemaker return code */ int (*new_data)(pcmk_ipc_api_t *api); /*! * \internal * \brief Free any private data used by daemon IPC * * \param[in] api_data Data allocated by new_data() method */ void (*free_data)(void *api_data); /*! * \internal * \brief Perform daemon-specific handling after successful connection * * Some daemons require clients to register before sending any other * commands. The controller requires a CRM_OP_HELLO (with no reply), and * the CIB manager, executor, and fencer require a CRM_OP_REGISTER (with a * reply). Ideally this would be consistent across all daemons, but for now * this allows each to do its own authorization. * * \param[in] api IPC API connection * * \return Standard Pacemaker return code */ int (*post_connect)(pcmk_ipc_api_t *api); /*! * \internal * \brief Check whether an IPC request results in a reply * * \param[in] api IPC API connection * \param[in] request IPC request XML * * \return true if request would result in an IPC reply, false otherwise */ bool (*reply_expected)(pcmk_ipc_api_t *api, xmlNode *request); /*! * \internal * \brief Perform daemon-specific handling of an IPC message * * \param[in] api IPC API connection * \param[in] msg Message read from IPC connection * * \return true if more IPC reply messages should be expected */ bool (*dispatch)(pcmk_ipc_api_t *api, xmlNode *msg); /*! * \internal * \brief Perform daemon-specific handling of an IPC disconnect * * \param[in] api IPC API connection */ void (*post_disconnect)(pcmk_ipc_api_t *api); } pcmk__ipc_methods_t; // Implementation of pcmk_ipc_api_t struct pcmk_ipc_api_s { enum pcmk_ipc_server server; // Daemon this IPC API instance is for enum pcmk_ipc_dispatch dispatch_type; // How replies should be dispatched size_t ipc_size_max; // maximum IPC buffer size crm_ipc_t *ipc; // IPC connection mainloop_io_t *mainloop_io; // If using mainloop, I/O source for IPC bool free_on_disconnect; // Whether disconnect should free object pcmk_ipc_callback_t cb; // Caller-registered callback (if any) void *user_data; // Caller-registered data (if any) void *api_data; // For daemon-specific use pcmk__ipc_methods_t *cmds; // Behavior that varies by daemon }; typedef struct pcmk__ipc_header_s { struct qb_ipc_response_header qb; uint32_t size_uncompressed; uint32_t size_compressed; uint32_t flags; uint8_t version; } pcmk__ipc_header_t; G_GNUC_INTERNAL int pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request); G_GNUC_INTERNAL void pcmk__call_ipc_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data); G_GNUC_INTERNAL unsigned int pcmk__ipc_buffer_size(unsigned int max); G_GNUC_INTERNAL bool pcmk__valid_ipc_header(const pcmk__ipc_header_t *header); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__attrd_api_methods(void); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__controld_api_methods(void); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__pacemakerd_api_methods(void); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__schedulerd_api_methods(void); /* * Logging */ /*! * \brief Check the authenticity of the IPC socket peer process * * If everything goes well, peer's authenticity is verified by the means * of comparing against provided referential UID and GID (either satisfies), * and the result of this check can be deduced from the return value. * As an exception, detected UID of 0 ("root") satisfies arbitrary * provided referential daemon's credentials. * * \param[in] qb_ipc libqb client connection if available * \param[in] sock IPC related, connected Unix socket to check peer of * \param[in] refuid referential UID to check against * \param[in] refgid referential GID to check against * \param[out] gotpid to optionally store obtained PID of the peer * (not available on FreeBSD, special value of 1 * used instead, and the caller is required to * special case this value respectively) * \param[out] gotuid to optionally store obtained UID of the peer * \param[out] gotgid to optionally store obtained GID of the peer * * \return Standard Pacemaker return code * ie: 0 if it the connection is authentic * pcmk_rc_ipc_unauthorized if the connection is not authentic, * standard errors. * * \note While this function is tolerant on what constitutes authorized * IPC daemon process (its effective user matches UID=0 or \p refuid, * or at least its group matches \p refgid), either or both (in case * of UID=0) mismatches on the expected credentials of such peer * process \e shall be investigated at the caller when value of 1 * gets returned there, since higher-than-expected privileges in * respect to the expected/intended credentials possibly violate * the least privilege principle and may pose an additional risk * (i.e. such accidental inconsistency shall be eventually fixed). */ int pcmk__crm_ipc_is_authentic_process(qb_ipcc_connection_t *qb_ipc, int sock, uid_t refuid, gid_t refgid, pid_t *gotpid, uid_t *gotuid, gid_t *gotgid); +/* + * Utils + */ +#define PCMK__PW_BUFFER_LEN 500 + + #endif // CRMCOMMON_PRIVATE__H diff --git a/lib/common/mock.c b/lib/common/mock.c index 4b355d1cbc..f628b03942 100644 --- a/lib/common/mock.c +++ b/lib/common/mock.c @@ -1,314 +1,371 @@ /* * Copyright 2021-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mock_private.h" /* This file is only used when running "make check". It is built into * libcrmcommon_test.a, not into libcrmcommon.so. It is used to support * constructing mock versions of library functions for unit testing. * * HOW TO ADD A MOCKED FUNCTION: * * - In this file, declare a bool pcmk__mock_X variable, and define a __wrap_X * function with the same prototype as the actual function that performs the * desired behavior if pcmk__mock_X is true and calls __real_X otherwise. * You can use cmocka's mock_type() and mock_ptr_type() to pass extra * information to the mocked function (see existing examples for details). * * - In mock_private.h, add declarations for extern bool pcmk__mock_X and the * __real_X and __wrap_X function prototypes. * * - In mk/tap.mk, add the function name to the WRAPPED variable. * * HOW TO USE A MOCKED FUNCTION: * * - #include "mock_private.h" in your test file. * * - Write your test cases using pcmk__mock_X and cmocka's will_return() as * needed per the comments for the mocked function below. See existing test * cases for examples. */ // LCOV_EXCL_START - /* calloc() * - * If pcmk__mock_calloc is set to true, later calls to malloc() will return - * NULL. + * If pcmk__mock_calloc is set to true, later calls to calloc() will return + * NULL and must be preceded by: + * + * expect_*(__wrap_calloc, nmemb[, ...]); + * expect_*(__wrap_calloc, size[, ...]); + * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html */ bool pcmk__mock_calloc = false; void * __wrap_calloc(size_t nmemb, size_t size) { - return pcmk__mock_calloc? NULL : __real_calloc(nmemb, size); + if (!pcmk__mock_calloc) { + return __real_calloc(nmemb, size); + } + check_expected(nmemb); + check_expected(size); + return NULL; } /* getenv() * * If pcmk__mock_getenv is set to true, later calls to getenv() must be preceded * by: * + * expect_*(__wrap_getenv, name[, ...]); * will_return(__wrap_getenv, return_value); + * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html */ bool pcmk__mock_getenv = false; char * __wrap_getenv(const char *name) { - return pcmk__mock_getenv? mock_ptr_type(char *) : __real_getenv(name); + if (!pcmk__mock_getenv) { + return __real_getenv(name); + } + check_expected_ptr(name); + return mock_ptr_type(char *); } /* getpid() * * If pcmk__mock_getpid is set to true, later calls to getpid() must be preceded * by: * * will_return(__wrap_getpid, return_value); */ bool pcmk__mock_getpid = false; pid_t __wrap_getpid(void) { return pcmk__mock_getpid? mock_type(pid_t) : __real_getpid(); } /* setgrent(), getgrent() and endgrent() * * If pcmk__mock_grent is set to true, getgrent() will behave as if the only * groups on the system are: * * - grp0 (user0, user1) * - grp1 (user1) * - grp2 (user2, user1) */ bool pcmk__mock_grent = false; // Index of group that will be returned next from getgrent() static int group_idx = 0; // Data used for testing static const char* grp0_members[] = { "user0", "user1", NULL }; static const char* grp1_members[] = { "user1", NULL }; static const char* grp2_members[] = { "user2", "user1", NULL }; /* An array of "groups" (a struct from grp.h) * * The members of the groups are initalized here to some testing data, casting * away the consts to make the compiler happy and simplify initialization. We * never actually change these variables during the test! * * string literal = const char* (cannot be changed b/c ? ) * vs. char* (it's getting casted to this) */ static const int NUM_GROUPS = 3; static struct group groups[] = { {(char*)"grp0", (char*)"", 0, (char**)grp0_members}, {(char*)"grp1", (char*)"", 1, (char**)grp1_members}, {(char*)"grp2", (char*)"", 2, (char**)grp2_members}, }; // This function resets the group_idx to 0. void __wrap_setgrent(void) { if (pcmk__mock_grent) { group_idx = 0; } else { __real_setgrent(); } } /* This function returns the next group entry in the list of groups, or * NULL if there aren't any left. * group_idx is a global variable which keeps track of where you are in the list */ struct group * __wrap_getgrent(void) { if (pcmk__mock_grent) { if (group_idx >= NUM_GROUPS) { return NULL; } return &groups[group_idx++]; } else { return __real_getgrent(); } } void __wrap_endgrent(void) { if (!pcmk__mock_grent) { __real_endgrent(); } } /* fopen() * * If pcmk__mock_fopen is set to true, later calls to fopen() must be * preceded by: * + * expect_*(__wrap_fopen, pathname[, ...]); + * expect_*(__wrap_fopen, mode[, ...]); * will_return(__wrap_fopen, errno_to_set); + * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html */ bool pcmk__mock_fopen = false; FILE * __wrap_fopen(const char *pathname, const char *mode) { if (pcmk__mock_fopen) { + check_expected_ptr(pathname); + check_expected_ptr(mode); errno = mock_type(int); if (errno != 0) { return NULL; } else { return __real_fopen(pathname, mode); } } else { return __real_fopen(pathname, mode); } } /* getpwnam_r() * * If pcmk__mock_getpwnam_r is set to true, later calls to getpwnam_r() must be * preceded by: * + * expect_*(__wrap_getpwnam_r, name[, ...]); + * expect_*(__wrap_getpwnam_r, pwd[, ...]); + * expect_*(__wrap_getpwnam_r, buf[, ...]); + * expect_*(__wrap_getpwnam_r, buflen[, ...]); + * expect_*(__wrap_getpwnam_r, result[, ...]); * will_return(__wrap_getpwnam_r, return_value); * will_return(__wrap_getpwnam_r, ptr_to_result_struct); + * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html */ bool pcmk__mock_getpwnam_r = false; int __wrap_getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { if (pcmk__mock_getpwnam_r) { int retval = mock_type(int); + check_expected_ptr(name); + check_expected_ptr(pwd); + check_expected_ptr(buf); + check_expected(buflen); + check_expected_ptr(result); *result = mock_ptr_type(struct passwd *); return retval; } else { return __real_getpwnam_r(name, pwd, buf, buflen, result); } } /* * If pcmk__mock_readlink is set to true, later calls to readlink() must be * preceded by: * + * expect_*(__wrap_readlink, path[, ...]); + * expect_*(__wrap_readlink, buf[, ...]); + * expect_*(__wrap_readlink, bufsize[, ...]); * will_return(__wrap_readlink, errno_to_set); * will_return(__wrap_readlink, link_contents); * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html + * * The mocked function will return 0 if errno_to_set is 0, and -1 otherwise. */ bool pcmk__mock_readlink = false; ssize_t __wrap_readlink(const char *restrict path, char *restrict buf, size_t bufsize) { if (pcmk__mock_readlink) { const char *contents = NULL; + check_expected_ptr(path); + check_expected_ptr(buf); + check_expected(bufsize); errno = mock_type(int); contents = mock_ptr_type(const char *); if (errno == 0) { strncpy(buf, contents, bufsize - 1); return strlen(contents); } return -1; } else { return __real_readlink(path, buf, bufsize); } } /* strdup() * * If pcmk__mock_strdup is set to true, later calls to strdup() will return - * NULL. + * NULL and must be preceded by: + * + * expect_*(__wrap_strdup, s[, ...]); + * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html */ bool pcmk__mock_strdup = false; char * __wrap_strdup(const char *s) { - return pcmk__mock_strdup? NULL : __real_strdup(s); + if (!pcmk__mock_strdup) { + return __real_strdup(s); + } + check_expected_ptr(s); + return NULL; } /* uname() * * If pcmk__mock_uname is set to true, later calls to uname() must be preceded * by: * + * expect_*(__wrap_uname, buf[, ...]); * will_return(__wrap_uname, return_value); * will_return(__wrap_uname, node_name_for_buf_parameter_to_uname); + * + * expect_* functions: https://api.cmocka.org/group__cmocka__param.html */ bool pcmk__mock_uname = false; int __wrap_uname(struct utsname *buf) { if (pcmk__mock_uname) { - int retval = mock_type(int); - char *result = mock_ptr_type(char *); + int retval = 0; + char *result = NULL; + + check_expected_ptr(buf); + retval = mock_type(int); + result = mock_ptr_type(char *); if (result != NULL) { strcpy(buf->nodename, result); } return retval; } else { return __real_uname(buf); } } // LCOV_EXCL_STOP diff --git a/lib/common/tests/io/pcmk__full_path_test.c b/lib/common/tests/io/pcmk__full_path_test.c index 7c8bddade6..2ae9cc3b3f 100644 --- a/lib/common/tests/io/pcmk__full_path_test.c +++ b/lib/common/tests/io/pcmk__full_path_test.c @@ -1,55 +1,60 @@ /* * Copyright 2020-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" static void function_asserts(void **state) { pcmk__assert_asserts(pcmk__full_path(NULL, "/dir")); pcmk__assert_asserts(pcmk__full_path("file", NULL)); - pcmk__mock_strdup = true; // strdup() will return NULL - pcmk__assert_asserts(pcmk__full_path("/full/path", "/dir")); - pcmk__mock_strdup = false; // Use real strdup() + pcmk__assert_asserts( + { + pcmk__mock_strdup = true; // strdup() will return NULL + expect_string(__wrap_strdup, s, "/full/path"); + pcmk__full_path("/full/path", "/dir"); + pcmk__mock_strdup = false; // Use real strdup() + } + ); } static void full_path(void **state) { char *path = NULL; path = pcmk__full_path("file", "/dir"); assert_int_equal(strcmp(path, "/dir/file"), 0); free(path); path = pcmk__full_path("/full/path", "/dir"); assert_int_equal(strcmp(path, "/full/path"), 0); free(path); path = pcmk__full_path("../relative/path", "/dir"); assert_int_equal(strcmp(path, "/dir/../relative/path"), 0); free(path); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(function_asserts), cmocka_unit_test(full_path), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/io/pcmk__get_tmpdir_test.c b/lib/common/tests/io/pcmk__get_tmpdir_test.c index eac8fc0f82..71941b0ce5 100644 --- a/lib/common/tests/io/pcmk__get_tmpdir_test.c +++ b/lib/common/tests/io/pcmk__get_tmpdir_test.c @@ -1,70 +1,76 @@ /* * Copyright 2021-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" static void getenv_returns_invalid(void **state) { const char *result; pcmk__mock_getenv = true; + expect_string(__wrap_getenv, name, "TMPDIR"); will_return(__wrap_getenv, NULL); // getenv("TMPDIR") return value result = pcmk__get_tmpdir(); assert_string_equal(result, "/tmp"); + expect_string(__wrap_getenv, name, "TMPDIR"); will_return(__wrap_getenv, ""); // getenv("TMPDIR") return value result = pcmk__get_tmpdir(); assert_string_equal(result, "/tmp"); + expect_string(__wrap_getenv, name, "TMPDIR"); will_return(__wrap_getenv, "subpath"); // getenv("TMPDIR") return value result = pcmk__get_tmpdir(); assert_string_equal(result, "/tmp"); pcmk__mock_getenv = false; } static void getenv_returns_valid(void **state) { const char *result; pcmk__mock_getenv = true; + expect_string(__wrap_getenv, name, "TMPDIR"); will_return(__wrap_getenv, "/var/tmp"); // getenv("TMPDIR") return value result = pcmk__get_tmpdir(); assert_string_equal(result, "/var/tmp"); + expect_string(__wrap_getenv, name, "TMPDIR"); will_return(__wrap_getenv, "/"); // getenv("TMPDIR") return value result = pcmk__get_tmpdir(); assert_string_equal(result, "/"); + expect_string(__wrap_getenv, name, "TMPDIR"); will_return(__wrap_getenv, "/tmp/abcd.1234"); // getenv("TMPDIR") return value result = pcmk__get_tmpdir(); assert_string_equal(result, "/tmp/abcd.1234"); pcmk__mock_getenv = false; } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(getenv_returns_invalid), cmocka_unit_test(getenv_returns_valid), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/output/pcmk__output_new_test.c b/lib/common/tests/output/pcmk__output_new_test.c index 14410830d6..4d27436ff6 100644 --- a/lib/common/tests/output/pcmk__output_new_test.c +++ b/lib/common/tests/output/pcmk__output_new_test.c @@ -1,152 +1,156 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include "mock_private.h" static bool init_succeeds = true; static bool fake_text_init(pcmk__output_t *out) { return init_succeeds; } static void fake_text_free_priv(pcmk__output_t *out) { /* This function intentionally left blank */ } /* "text" is the default for pcmk__output_new. */ static pcmk__output_t * mk_fake_text_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "text"; retval->init = fake_text_init; retval->free_priv = fake_text_free_priv; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; return retval; } static int setup(void **state) { pcmk__register_format(NULL, "text", mk_fake_text_output, NULL); return 0; } static int teardown(void **state) { pcmk__unregister_formats(); return 0; } static void empty_formatters(void **state) { pcmk__output_t *out = NULL; pcmk__assert_asserts(pcmk__output_new(&out, "fake", NULL, NULL)); } static void invalid_params(void **state) { /* This must be called with the setup/teardown functions so formatters is not NULL. */ pcmk__assert_asserts(pcmk__output_new(NULL, "fake", NULL, NULL)); } static void no_such_format(void **state) { pcmk__output_t *out = NULL; assert_int_equal(pcmk__output_new(&out, "fake", NULL, NULL), pcmk_rc_unknown_format); } static void create_fails(void **state) { pcmk__output_t *out = NULL; pcmk__mock_calloc = true; // calloc() will return NULL + expect_value(__wrap_calloc, nmemb, 1); + expect_value(__wrap_calloc, size, sizeof(pcmk__output_t)); assert_int_equal(pcmk__output_new(&out, "text", NULL, NULL), ENOMEM); pcmk__mock_calloc = false; // Use real calloc() } static void fopen_fails(void **state) { pcmk__output_t *out = NULL; pcmk__mock_fopen = true; + expect_string(__wrap_fopen, pathname, "destfile"); + expect_string(__wrap_fopen, mode, "w"); will_return(__wrap_fopen, EPERM); assert_int_equal(pcmk__output_new(&out, "text", "destfile", NULL), EPERM); pcmk__mock_fopen = false; } static void init_fails(void **state) { pcmk__output_t *out = NULL; init_succeeds = false; assert_int_equal(pcmk__output_new(&out, "text", NULL, NULL), ENOMEM); init_succeeds = true; } static void everything_succeeds(void **state) { pcmk__output_t *out = NULL; assert_int_equal(pcmk__output_new(&out, "text", NULL, NULL), pcmk_rc_ok); assert_string_equal(out->fmt_name, "text"); assert_ptr_equal(out->dest, stdout); assert_false(out->quiet); assert_non_null(out->messages); assert_string_equal(getenv("OCF_OUTPUT_FORMAT"), "text"); pcmk__output_free(out); } static void no_fmt_name_given(void **state) { pcmk__output_t *out = NULL; assert_int_equal(pcmk__output_new(&out, NULL, NULL, NULL), pcmk_rc_ok); assert_string_equal(out->fmt_name, "text"); pcmk__output_free(out); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(empty_formatters), cmocka_unit_test_setup_teardown(invalid_params, setup, teardown), cmocka_unit_test_setup_teardown(no_such_format, setup, teardown), cmocka_unit_test_setup_teardown(create_fails, setup, teardown), cmocka_unit_test_setup_teardown(init_fails, setup, teardown), cmocka_unit_test_setup_teardown(fopen_fails, setup, teardown), cmocka_unit_test_setup_teardown(everything_succeeds, setup, teardown), cmocka_unit_test_setup_teardown(no_fmt_name_given, setup, teardown), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c b/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c index 2f11c2d615..b9fa50fa7d 100644 --- a/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c +++ b/lib/common/tests/procfs/pcmk__procfs_has_pids_false_test.c @@ -1,47 +1,55 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" #include #include #include #if SUPPORT_PROCFS static void no_pids(void **state) { + char path[PATH_MAX]; + + snprintf(path, PATH_MAX, "/proc/%u/exe", getpid()); + // Set readlink() errno and link contents (for /proc/PID/exe) pcmk__mock_readlink = true; + + expect_string(__wrap_readlink, path, path); + expect_any(__wrap_readlink, buf); + expect_value(__wrap_readlink, bufsize, PATH_MAX - 1); will_return(__wrap_readlink, ENOENT); will_return(__wrap_readlink, NULL); assert_false(pcmk__procfs_has_pids()); pcmk__mock_readlink = false; } #endif // SUPPORT_PROCFS int main(int argc, char **argv) { const struct CMUnitTest tests[] = { #if SUPPORT_PROCFS cmocka_unit_test(no_pids), #endif }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c b/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c index f9730a6baf..b15435e053 100644 --- a/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c +++ b/lib/common/tests/procfs/pcmk__procfs_has_pids_true_test.c @@ -1,47 +1,55 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" #include #include #include #if SUPPORT_PROCFS static void has_pids(void **state) { + char path[PATH_MAX]; + + snprintf(path, PATH_MAX, "/proc/%u/exe", getpid()); + // Set readlink() errno and link contents (for /proc/PID/exe) pcmk__mock_readlink = true; + + expect_string(__wrap_readlink, path, path); + expect_any(__wrap_readlink, buf); + expect_value(__wrap_readlink, bufsize, PATH_MAX - 1); will_return(__wrap_readlink, 0); will_return(__wrap_readlink, "/ok"); assert_true(pcmk__procfs_has_pids()); pcmk__mock_readlink = false; } #endif // SUPPORT_PROCFS int main(int argc, char **argv) { const struct CMUnitTest tests[] = { #if SUPPORT_PROCFS cmocka_unit_test(has_pids), #endif }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c b/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c index aaba7e29a7..11f01032fc 100644 --- a/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c +++ b/lib/common/tests/procfs/pcmk__procfs_pid2path_test.c @@ -1,84 +1,96 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" #include #include #include #if SUPPORT_PROCFS static void no_exe_file(void **state) { char path[PATH_MAX]; // Set readlink() errno and link contents pcmk__mock_readlink = true; + + expect_string(__wrap_readlink, path, "/proc/1000/exe"); + expect_value(__wrap_readlink, buf, path); + expect_value(__wrap_readlink, bufsize, sizeof(path) - 1); will_return(__wrap_readlink, ENOENT); will_return(__wrap_readlink, NULL); assert_int_equal(pcmk__procfs_pid2path(1000, path, sizeof(path)), ENOENT); pcmk__mock_readlink = false; } static void contents_too_long(void **state) { char path[10]; // Set readlink() errno and link contents pcmk__mock_readlink = true; + + expect_string(__wrap_readlink, path, "/proc/1000/exe"); + expect_value(__wrap_readlink, buf, path); + expect_value(__wrap_readlink, bufsize, sizeof(path) - 1); will_return(__wrap_readlink, 0); will_return(__wrap_readlink, "/more/than/10/characters"); assert_int_equal(pcmk__procfs_pid2path(1000, path, sizeof(path)), ENAMETOOLONG); pcmk__mock_readlink = false; } static void contents_ok(void **state) { char path[PATH_MAX]; // Set readlink() errno and link contents pcmk__mock_readlink = true; + + expect_string(__wrap_readlink, path, "/proc/1000/exe"); + expect_value(__wrap_readlink, buf, path); + expect_value(__wrap_readlink, bufsize, sizeof(path) - 1); will_return(__wrap_readlink, 0); will_return(__wrap_readlink, "/ok"); assert_int_equal(pcmk__procfs_pid2path((pid_t) 1000, path, sizeof(path)), pcmk_rc_ok); assert_string_equal(path, "/ok"); pcmk__mock_readlink = false; } #endif // SUPPORT_PROCFS int main(int argc, char **argv) { const struct CMUnitTest tests[] = { #if SUPPORT_PROCFS cmocka_unit_test(no_exe_file), cmocka_unit_test(contents_too_long), cmocka_unit_test(contents_ok), #endif }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/strings/pcmk__compress_test.c b/lib/common/tests/strings/pcmk__compress_test.c index 5e131b768d..adf7e7a175 100644 --- a/lib/common/tests/strings/pcmk__compress_test.c +++ b/lib/common/tests/strings/pcmk__compress_test.c @@ -1,60 +1,66 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" #define SIMPLE_DATA "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" const char *SIMPLE_COMPRESSED = "BZh41AY&SYO\x1ai"; static void simple_compress(void **state) { char *result = calloc(1024, sizeof(char)); unsigned int len; assert_int_equal(pcmk__compress(SIMPLE_DATA, 40, 0, &result, &len), pcmk_rc_ok); assert_memory_equal(result, SIMPLE_COMPRESSED, 13); } static void max_too_small(void **state) { char *result = calloc(1024, sizeof(char)); unsigned int len; assert_int_equal(pcmk__compress(SIMPLE_DATA, 40, 10, &result, &len), pcmk_rc_error); } static void calloc_fails(void **state) { char *result = calloc(1024, sizeof(char)); unsigned int len; - pcmk__mock_calloc = true; // calloc() will return NULL - pcmk__assert_asserts(pcmk__compress(SIMPLE_DATA, 40, 0, &result, &len)); - pcmk__mock_calloc = false; // Use the real calloc() + pcmk__assert_asserts( + { + pcmk__mock_calloc = true; // calloc() will return NULL + expect_value(__wrap_calloc, nmemb, (size_t) ((40 * 1.01) + 601)); + expect_value(__wrap_calloc, size, sizeof(char)); + pcmk__compress(SIMPLE_DATA, 40, 0, &result, &len); + pcmk__mock_calloc = false; // Use the real calloc() + } + ); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(simple_compress), cmocka_unit_test(max_too_small), cmocka_unit_test(calloc_fails), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/strings/pcmk__str_update_test.c b/lib/common/tests/strings/pcmk__str_update_test.c index 7d1b0cf780..e9c9220958 100644 --- a/lib/common/tests/strings/pcmk__str_update_test.c +++ b/lib/common/tests/strings/pcmk__str_update_test.c @@ -1,81 +1,86 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" static void update_null(void **state) { char *str = NULL; // These just make sure they don't crash pcmk__str_update(NULL, NULL); pcmk__str_update(NULL, "value"); // Update an already NULL string to NULL pcmk__str_update(&str, NULL); assert_null(str); // Update an already allocated string to NULL str = strdup("hello"); pcmk__str_update(&str, NULL); assert_null(str); } static void update_same(void **state) { char *str = NULL; char *saved = NULL; str = strdup("hello"); saved = str; pcmk__str_update(&str, "hello"); assert_ptr_equal(saved, str); // No free and reallocation free(str); } static void update_different(void **state) { char *str = NULL; str = strdup("hello"); pcmk__str_update(&str, "world"); assert_string_equal(str, "world"); free(str); } static void strdup_fails(void **state) { char *str = NULL; str = strdup("hello"); - pcmk__mock_strdup = true; // strdup() will return NULL - pcmk__assert_asserts(pcmk__str_update(&str, "world")); - pcmk__mock_strdup = false; // Use the real strdup() + pcmk__assert_asserts( + { + pcmk__mock_strdup = true; // strdup() will return NULL + expect_string(__wrap_strdup, s, "world"); + pcmk__str_update(&str, "world"); + pcmk__mock_strdup = false; // Use the real strdup() + } + ); free(str); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(update_null), cmocka_unit_test(update_same), cmocka_unit_test(update_different), cmocka_unit_test(strdup_fails), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/utils/crm_user_lookup_test.c b/lib/common/tests/utils/crm_user_lookup_test.c index f40e318e25..89368cd9f0 100644 --- a/lib/common/tests/utils/crm_user_lookup_test.c +++ b/lib/common/tests/utils/crm_user_lookup_test.c @@ -1,108 +1,134 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include +#include "crmcommon_private.h" #include "mock_private.h" #include #include static void calloc_fails(void **state) { uid_t uid; gid_t gid; pcmk__mock_calloc = true; // calloc() will return NULL + expect_value(__wrap_calloc, nmemb, 1); + expect_value(__wrap_calloc, size, PCMK__PW_BUFFER_LEN); assert_int_equal(crm_user_lookup("hauser", &uid, &gid), -ENOMEM); pcmk__mock_calloc = false; // Use real calloc() } static void getpwnam_r_fails(void **state) { uid_t uid; gid_t gid; // Set getpwnam_r() return value and result parameter pcmk__mock_getpwnam_r = true; + + expect_string(__wrap_getpwnam_r, name, "hauser"); + expect_any(__wrap_getpwnam_r, pwd); + expect_any(__wrap_getpwnam_r, buf); + expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN); + expect_any(__wrap_getpwnam_r, result); will_return(__wrap_getpwnam_r, EIO); will_return(__wrap_getpwnam_r, NULL); assert_int_equal(crm_user_lookup("hauser", &uid, &gid), -EIO); pcmk__mock_getpwnam_r = false; } static void no_matching_pwent(void **state) { uid_t uid; gid_t gid; // Set getpwnam_r() return value and result parameter pcmk__mock_getpwnam_r = true; + + expect_string(__wrap_getpwnam_r, name, "hauser"); + expect_any(__wrap_getpwnam_r, pwd); + expect_any(__wrap_getpwnam_r, buf); + expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN); + expect_any(__wrap_getpwnam_r, result); will_return(__wrap_getpwnam_r, 0); will_return(__wrap_getpwnam_r, NULL); assert_int_equal(crm_user_lookup("hauser", &uid, &gid), -EINVAL); pcmk__mock_getpwnam_r = false; } static void entry_found(void **state) { uid_t uid; gid_t gid; /* We don't care about any of the other fields of the password entry, so just * leave them blank. */ struct passwd returned_ent = { .pw_uid = 1000, .pw_gid = 1000 }; /* Test getpwnam_r returning a valid passwd entry, but we don't pass uid or gid. */ // Set getpwnam_r() return value and result parameter pcmk__mock_getpwnam_r = true; + + expect_string(__wrap_getpwnam_r, name, "hauser"); + expect_any(__wrap_getpwnam_r, pwd); + expect_any(__wrap_getpwnam_r, buf); + expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN); + expect_any(__wrap_getpwnam_r, result); will_return(__wrap_getpwnam_r, 0); will_return(__wrap_getpwnam_r, &returned_ent); assert_int_equal(crm_user_lookup("hauser", NULL, NULL), 0); /* Test getpwnam_r returning a valid passwd entry, and we do pass uid and gid. */ // Set getpwnam_r() return value and result parameter + expect_string(__wrap_getpwnam_r, name, "hauser"); + expect_any(__wrap_getpwnam_r, pwd); + expect_any(__wrap_getpwnam_r, buf); + expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN); + expect_any(__wrap_getpwnam_r, result); will_return(__wrap_getpwnam_r, 0); will_return(__wrap_getpwnam_r, &returned_ent); assert_int_equal(crm_user_lookup("hauser", &uid, &gid), 0); assert_int_equal(uid, 1000); assert_int_equal(gid, 1000); pcmk__mock_getpwnam_r = false; } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(calloc_fails), cmocka_unit_test(getpwnam_r_fails), cmocka_unit_test(no_matching_pwent), cmocka_unit_test(entry_found), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/utils/pcmk_daemon_user_test.c b/lib/common/tests/utils/pcmk_daemon_user_test.c index d904a7ffb4..0442ad8242 100644 --- a/lib/common/tests/utils/pcmk_daemon_user_test.c +++ b/lib/common/tests/utils/pcmk_daemon_user_test.c @@ -1,77 +1,90 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include +#include "crmcommon_private.h" #include "mock_private.h" #include #include static void no_matching_pwent(void **state) { uid_t uid; gid_t gid; // Set getpwnam_r() return value and result parameter pcmk__mock_getpwnam_r = true; + + expect_string(__wrap_getpwnam_r, name, "hacluster"); + expect_any(__wrap_getpwnam_r, pwd); + expect_any(__wrap_getpwnam_r, buf); + expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN); + expect_any(__wrap_getpwnam_r, result); will_return(__wrap_getpwnam_r, ENOENT); will_return(__wrap_getpwnam_r, NULL); assert_int_equal(pcmk_daemon_user(&uid, &gid), -ENOENT); pcmk__mock_getpwnam_r = false; } static void entry_found(void **state) { uid_t uid; gid_t gid; /* We don't care about any of the other fields of the password entry, so just * leave them blank. */ struct passwd returned_ent = { .pw_uid = 1000, .pw_gid = 1000 }; /* Test getpwnam_r returning a valid passwd entry, but we don't pass uid or gid. */ // Set getpwnam_r() return value and result parameter pcmk__mock_getpwnam_r = true; + + expect_string(__wrap_getpwnam_r, name, "hacluster"); + expect_any(__wrap_getpwnam_r, pwd); + expect_any(__wrap_getpwnam_r, buf); + expect_value(__wrap_getpwnam_r, buflen, PCMK__PW_BUFFER_LEN); + expect_any(__wrap_getpwnam_r, result); will_return(__wrap_getpwnam_r, 0); will_return(__wrap_getpwnam_r, &returned_ent); assert_int_equal(pcmk_daemon_user(NULL, NULL), 0); /* Test getpwnam_r returning a valid passwd entry, and we do pass uid and gid. */ /* We don't need to call will_return() again because pcmk_daemon_user() * will have cached the uid/gid from the previous call and won't make * another call to getpwnam_r(). */ assert_int_equal(pcmk_daemon_user(&uid, &gid), 0); assert_int_equal(uid, 1000); assert_int_equal(gid, 1000); pcmk__mock_getpwnam_r = false; } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(no_matching_pwent), cmocka_unit_test(entry_found), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/tests/utils/pcmk_hostname_test.c b/lib/common/tests/utils/pcmk_hostname_test.c index 7faadee436..d20aabc6a9 100644 --- a/lib/common/tests/utils/pcmk_hostname_test.c +++ b/lib/common/tests/utils/pcmk_hostname_test.c @@ -1,60 +1,64 @@ /* * Copyright 2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include "mock_private.h" #include static void uname_succeeded_test(void **state) { char *retval; // Set uname() return value and buf parameter node name pcmk__mock_uname = true; + + expect_any(__wrap_uname, buf); will_return(__wrap_uname, 0); will_return(__wrap_uname, "somename"); retval = pcmk_hostname(); assert_non_null(retval); assert_string_equal("somename", retval); free(retval); pcmk__mock_uname = false; } static void uname_failed_test(void **state) { // Set uname() return value and buf parameter node name pcmk__mock_uname = true; + + expect_any(__wrap_uname, buf); will_return(__wrap_uname, -1); will_return(__wrap_uname, NULL); assert_null(pcmk_hostname()); pcmk__mock_uname = false; } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(uname_succeeded_test), cmocka_unit_test(uname_failed_test), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/lib/common/utils.c b/lib/common/utils.c index a0446a4c81..6db9e00aab 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,597 +1,593 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crmcommon_private.h" -#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; bool pcmk__is_user_in_group(const char *user, const char *group) { struct group *grent; char **gr_mem; if (user == NULL || group == NULL) { return false; } setgrent(); while ((grent = getgrent()) != NULL) { if (grent->gr_mem == NULL) { continue; } if(strcmp(group, grent->gr_name) != 0) { continue; } gr_mem = grent->gr_mem; while (*gr_mem != NULL) { if (!strcmp(user, *gr_mem++)) { endgrent(); return true; } } } endgrent(); return false; } int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid) { int rc = pcmk_ok; char *buffer = NULL; struct passwd pwd; struct passwd *pwentry = NULL; - buffer = calloc(1, PW_BUFFER_LEN); + buffer = calloc(1, PCMK__PW_BUFFER_LEN); if (buffer == NULL) { return -ENOMEM; } - rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry); + rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry); if (pwentry) { if (uid) { *uid = pwentry->pw_uid; } if (gid) { *gid = pwentry->pw_gid; } crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid); } else { rc = rc? -rc : -EINVAL; crm_info("User %s lookup: %s", name, pcmk_strerror(rc)); } free(buffer); return rc; } /*! * \brief Get user and group IDs of pacemaker daemon user * * \param[out] uid If non-NULL, where to store daemon user ID * \param[out] gid If non-NULL, where to store daemon group ID * * \return pcmk_ok on success, -errno otherwise */ int pcmk_daemon_user(uid_t *uid, gid_t *gid) { static uid_t daemon_uid; static gid_t daemon_gid; static bool found = false; int rc = pcmk_ok; if (!found) { rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); if (rc == pcmk_ok) { found = true; } } if (found) { if (uid) { *uid = daemon_uid; } if (gid) { *gid = daemon_gid; } } return rc; } /*! * \internal * \brief Return the integer equivalent of a portion of a string * * \param[in] text Pointer to beginning of string portion * \param[out] end_text This will point to next character after integer */ static int version_helper(const char *text, const char **end_text) { int atoi_result = -1; CRM_ASSERT(end_text != NULL); errno = 0; if (text != NULL && text[0] != 0) { /* seemingly sacrificing const-correctness -- because while strtol doesn't modify the input, it doesn't want to artificially taint the "end_text" pointer-to-pointer-to-first-char-in-string with constness in case the input wasn't actually constant -- by semantic definition not a single character will get modified so it shall be perfectly safe to make compiler happy with dropping "const" qualifier here */ atoi_result = (int) strtol(text, (char **) end_text, 10); if (errno == EINVAL) { crm_err("Conversion of '%s' %c failed", text, text[0]); atoi_result = -1; } } return atoi_result; } /* * version1 < version2 : -1 * version1 = version2 : 0 * version1 > version2 : 1 */ int compare_version(const char *version1, const char *version2) { int rc = 0; int lpc = 0; const char *ver1_iter, *ver2_iter; if (version1 == NULL && version2 == NULL) { return 0; } else if (version1 == NULL) { return -1; } else if (version2 == NULL) { return 1; } ver1_iter = version1; ver2_iter = version2; while (1) { int digit1 = 0; int digit2 = 0; lpc++; if (ver1_iter == ver2_iter) { break; } if (ver1_iter != NULL) { digit1 = version_helper(ver1_iter, &ver1_iter); } if (ver2_iter != NULL) { digit2 = version_helper(ver2_iter, &ver2_iter); } if (digit1 < digit2) { rc = -1; break; } else if (digit1 > digit2) { rc = 1; break; } if (ver1_iter != NULL && *ver1_iter == '.') { ver1_iter++; } if (ver1_iter != NULL && *ver1_iter == '\0') { ver1_iter = NULL; } if (ver2_iter != NULL && *ver2_iter == '.') { ver2_iter++; } if (ver2_iter != NULL && *ver2_iter == 0) { ver2_iter = NULL; } } if (rc == 0) { crm_trace("%s == %s (%d)", version1, version2, lpc); } else if (rc < 0) { crm_trace("%s < %s (%d)", version1, version2, lpc); } else if (rc > 0) { crm_trace("%s > %s (%d)", version1, version2, lpc); } return rc; } /*! * \brief Parse milliseconds from a Pacemaker interval specification * * \param[in] input Pacemaker time interval specification (a bare number of * seconds, a number with a unit optionally with whitespace * before and/or after the number, or an ISO 8601 duration) * * \return Milliseconds equivalent of given specification on success (limited * to the range of an unsigned integer), 0 if input is NULL, * or 0 (and set errno to EINVAL) on error */ guint crm_parse_interval_spec(const char *input) { long long msec = -1; errno = 0; if (input == NULL) { return 0; } else if (input[0] == 'P') { crm_time_t *period_s = crm_time_parse_duration(input); if (period_s) { msec = 1000 * crm_time_get_seconds(period_s); crm_time_free(period_s); } } else { msec = crm_get_msec(input); } if (msec < 0) { crm_warn("Using 0 instead of '%s'", input); errno = EINVAL; return 0; } return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec; } /*! * \internal * \brief Log a failed assertion * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion */ static void log_assertion_as(const char *file, const char *function, int line, const char *assert_condition) { if (!pcmk__is_daemon) { crm_enable_stderr(TRUE); // Make sure command-line user sees message } crm_err("%s: Triggered fatal assertion at %s:%d : %s", function, file, line, assert_condition); } /* coverity[+kill] */ /*! * \internal * \brief Log a failed assertion and abort * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion * * \note This does not return */ static _Noreturn void abort_as(const char *file, const char *function, int line, const char *assert_condition) { log_assertion_as(file, function, line, assert_condition); abort(); } /* coverity[+kill] */ /*! * \internal * \brief Handle a failed assertion * * When called by a daemon, fork a child that aborts (to dump core), otherwise * abort the current process. * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion */ static void fail_assert_as(const char *file, const char *function, int line, const char *assert_condition) { int status = 0; pid_t pid = 0; if (!pcmk__is_daemon) { abort_as(file, function, line, assert_condition); // does not return } pid = fork(); switch (pid) { case -1: // Fork failed crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d " ": %s", function, file, line, assert_condition); break; case 0: // Child process: just abort to dump core abort(); break; default: // Parent process: wait for child crm_err("%s: Forked child [%d] to record non-fatal assertion at " "%s:%d : %s", function, pid, file, line, assert_condition); crm_write_blackbox(SIGTRAP, NULL); do { if (waitpid(pid, &status, 0) == pid) { return; // Child finished dumping core } } while (errno == EINTR); if (errno == ECHILD) { // crm_mon ignores SIGCHLD crm_trace("Cannot wait on forked child [%d] " "(SIGCHLD is probably ignored)", pid); } else { crm_err("Cannot wait on forked child [%d]: %s", pid, pcmk_rc_str(errno)); } break; } } /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork) { if (!do_fork) { abort_as(file, function, line, assert_condition); } else if (do_core) { fail_assert_as(file, function, line, assert_condition); } else { log_assertion_as(file, function, line, assert_condition); } } /*! * \internal * \brief Convert the current process to a daemon process * * Fork a child process, exit the parent, create a PID file with the current * process ID, and close the standard input/output/error file descriptors. * Exit instead if a daemon is already running and using the PID file. * * \param[in] name Daemon executable name * \param[in] pidfile File name to use as PID file */ void pcmk__daemonize(const char *name, const char *pidfile) { int rc; pid_t pid; /* Check before we even try... */ rc = pcmk__pidfile_matches(pidfile, 1, name, &pid); if ((rc != pcmk_rc_ok) && (rc != ENOENT)) { crm_err("%s: already running [pid %lld in %s]", name, (long long) pid, pidfile); printf("%s: already running [pid %lld in %s]\n", name, (long long) pid, pidfile); crm_exit(CRM_EX_ERROR); } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); crm_perror(LOG_ERR, "fork"); crm_exit(CRM_EX_OSERR); } else if (pid > 0) { crm_exit(CRM_EX_OK); } rc = pcmk__lock_pidfile(pidfile, name); if (rc != pcmk_rc_ok) { crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d", pidfile, name, pcmk_rc_str(rc), rc); printf("Could not lock '%s' for %s: %s (%d)\n", pidfile, name, pcmk_rc_str(rc), rc); crm_exit(CRM_EX_ERROR); } umask(S_IWGRP | S_IWOTH | S_IROTH); close(STDIN_FILENO); pcmk__open_devnull(O_RDONLY); // stdin (fd 0) close(STDOUT_FILENO); pcmk__open_devnull(O_WRONLY); // stdout (fd 1) close(STDERR_FILENO); pcmk__open_devnull(O_WRONLY); // stderr (fd 2) } char * crm_meta_name(const char *field) { int lpc = 0; int max = 0; char *crm_name = NULL; CRM_CHECK(field != NULL, return NULL); crm_name = crm_strdup_printf(CRM_META "_%s", field); /* Massage the names so they can be used as shell variables */ max = strlen(crm_name); for (; lpc < max; lpc++) { switch (crm_name[lpc]) { case '-': crm_name[lpc] = '_'; break; } } return crm_name; } const char * crm_meta_value(GHashTable * hash, const char *field) { char *key = NULL; const char *value = NULL; key = crm_meta_name(field); if (key) { value = g_hash_table_lookup(hash, key); free(key); } return value; } #ifdef HAVE_UUID_UUID_H # include #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; } #ifdef HAVE_GNUTLS_GNUTLS_H void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); } #endif /*! * \brief Get the local hostname * * \return Newly allocated string with name, or NULL (and set errno) on error */ char * pcmk_hostname(void) { struct utsname hostinfo; return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename); } bool pcmk_str_is_infinity(const char *s) { return pcmk__str_any_of(s, CRM_INFINITY_S, CRM_PLUS_INFINITY_S, NULL); } bool pcmk_str_is_minus_infinity(const char *s) { return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none); } /*! * \internal * \brief Sleep for given milliseconds * * \param[in] ms Time to sleep * * \note The full time might not be slept if a signal is received. */ void pcmk__sleep_ms(unsigned int ms) { // @TODO Impose a sane maximum sleep to avoid hanging a process for long //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP); // Use sleep() for any whole seconds if (ms >= 1000) { sleep(ms / 1000); ms -= ms / 1000; } if (ms == 0) { return; } #if defined(HAVE_NANOSLEEP) // nanosleep() is POSIX-2008, so prefer that { struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) }; nanosleep(&req, NULL); } #elif defined(HAVE_USLEEP) // usleep() is widely available, though considered obsolete usleep((useconds_t) ms); #else // Otherwise use a trick with select() timeout { struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms }; select(0, NULL, NULL, NULL, &tv); } #endif } diff --git a/lib/pengine/tests/status/pe_new_working_set_test.c b/lib/pengine/tests/status/pe_new_working_set_test.c index 888d5d9ed0..bf08bdf3d5 100644 --- a/lib/pengine/tests/status/pe_new_working_set_test.c +++ b/lib/pengine/tests/status/pe_new_working_set_test.c @@ -1,50 +1,52 @@ /* * Copyright 2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include "mock_private.h" static void calloc_fails(void **state) { pcmk__mock_calloc = true; // calloc() will return NULL + expect_value(__wrap_calloc, nmemb, 1); + expect_value(__wrap_calloc, size, sizeof(pe_working_set_t)); assert_null(pe_new_working_set()); pcmk__mock_calloc = false; // Use real calloc() } static void calloc_succeeds(void **state) { pe_working_set_t *data_set = pe_new_working_set(); /* Nothing else to test about this function, as all it does is call * set_working_set_defaults which is also a public function and should * get its own unit test. */ assert_non_null(data_set); /* Avoid calling pe_free_working_set here so we don't artificially * inflate the coverage numbers. */ free(data_set); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(calloc_fails), cmocka_unit_test(calloc_succeeds), }; cmocka_set_message_output(CM_OUTPUT_TAP); return cmocka_run_group_tests(tests, NULL, NULL); }