diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am index d7aae53bfd..04d56dc3c8 100644 --- a/lib/common/Makefile.am +++ b/lib/common/Makefile.am @@ -1,108 +1,108 @@ # # 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 General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/common.mk AM_CPPFLAGS += -I$(top_builddir)/lib/gnu -I$(top_srcdir)/lib/gnu ## libraries lib_LTLIBRARIES = libcrmcommon.la check_LTLIBRARIES = libcrmcommon_test.la # Disable -Wcast-qual if used, because we do some hacky casting, # and because libxml2 has some signatures that should be const but aren't # for backward compatibility reasons. # s390 needs -fPIC # s390-suse-linux/bin/ld: .libs/ipc.o: relocation R_390_PC32DBL against `__stack_chk_fail@@GLIBC_2.4' can not be used when making a shared object; recompile with -fPIC CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC # Without "." here, check-recursive will run through the subdirectories first # and then run "make check" here. This will fail, because there's things in # the subdirectories that need check_LTLIBRARIES built first. Adding "." here # changes the order so the subdirectories are processed afterwards. SUBDIRS = . tests noinst_HEADERS = crmcommon_private.h mock_private.h libcrmcommon_la_LDFLAGS = -version-info 43:0:9 libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libcrmcommon_la_LIBADD = @LIBADD_DL@ $(top_builddir)/lib/gnu/libgnu.la # If configured with --with-profiling or --with-coverage, BUILD_PROFILING will # be set and -fno-builtin will be added to the CFLAGS. However, libcrmcommon # uses the fabs() function which is normally supplied by gcc as one of its # builtins. Therefore we need to explicitly link against libm here or the # tests won't link. if BUILD_PROFILING libcrmcommon_la_LIBADD += -lm endif # Use += rather than backlashed continuation lines for parsing by bumplibs libcrmcommon_la_SOURCES = libcrmcommon_la_SOURCES += acl.c libcrmcommon_la_SOURCES += agents.c libcrmcommon_la_SOURCES += alerts.c libcrmcommon_la_SOURCES += attrd_client.c libcrmcommon_la_SOURCES += cib.c if BUILD_CIBSECRETS libcrmcommon_la_SOURCES += cib_secrets.c endif libcrmcommon_la_SOURCES += cmdline.c libcrmcommon_la_SOURCES += digest.c libcrmcommon_la_SOURCES += health.c libcrmcommon_la_SOURCES += io.c libcrmcommon_la_SOURCES += ipc_client.c libcrmcommon_la_SOURCES += ipc_common.c libcrmcommon_la_SOURCES += ipc_controld.c libcrmcommon_la_SOURCES += ipc_pacemakerd.c libcrmcommon_la_SOURCES += ipc_schedulerd.c libcrmcommon_la_SOURCES += ipc_server.c libcrmcommon_la_SOURCES += iso8601.c libcrmcommon_la_SOURCES += lists.c libcrmcommon_la_SOURCES += logging.c libcrmcommon_la_SOURCES += mainloop.c libcrmcommon_la_SOURCES += messages.c libcrmcommon_la_SOURCES += nvpair.c libcrmcommon_la_SOURCES += operations.c libcrmcommon_la_SOURCES += options.c libcrmcommon_la_SOURCES += output.c libcrmcommon_la_SOURCES += output_html.c libcrmcommon_la_SOURCES += output_log.c libcrmcommon_la_SOURCES += output_none.c libcrmcommon_la_SOURCES += output_text.c libcrmcommon_la_SOURCES += output_xml.c libcrmcommon_la_SOURCES += patchset.c libcrmcommon_la_SOURCES += pid.c libcrmcommon_la_SOURCES += procfs.c libcrmcommon_la_SOURCES += remote.c libcrmcommon_la_SOURCES += results.c libcrmcommon_la_SOURCES += schemas.c libcrmcommon_la_SOURCES += scores.c libcrmcommon_la_SOURCES += strings.c libcrmcommon_la_SOURCES += utils.c libcrmcommon_la_SOURCES += watchdog.c libcrmcommon_la_SOURCES += xml.c libcrmcommon_la_SOURCES += xpath.c -WRAPPED = calloc getenv getpwnam_r uname +WRAPPED = calloc getenv getpwnam_r uname setgrent getgrent endgrent WRAPPED_FLAGS = $(foreach fn,$(WRAPPED),-Wl,--wrap=$(fn)) libcrmcommon_test_la_SOURCES = $(libcrmcommon_la_SOURCES) libcrmcommon_test_la_SOURCES += mock.c libcrmcommon_test_la_LDFLAGS = $(LDFLAGS_HARDENED_LIB) $(WRAPPED_FLAGS) libcrmcommon_test_la_CFLAGS = $(libcrmcommon_la_CFLAGS) libcrmcommon_test_la_LIBADD = $(libcrmcommon_la_LIBADD) nodist_libcrmcommon_test_la_SOURCES = $(nodist_libcrmcommon_la_SOURCES) clean-generic: rm -f *.log *.debug *.xml *~ diff --git a/lib/common/mock.c b/lib/common/mock.c index 55812ddbc7..fa9431e6dc 100644 --- a/lib/common/mock.c +++ b/lib/common/mock.c @@ -1,76 +1,93 @@ /* * 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 "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. * - * Each unit test will only ever want to use a mocked version of one or two - * library functions. However, we need to mark all the mocked functions as - * wrapped (with -Wl,--wrap= in the LDFLAGS) in libcrmcommon_test.a so that - * all those unit tests can share the same special test library. The unit - * test then defines its own wrapped function. Because a unit test won't - * define every single wrapped function, there will be undefined references - * at link time. + * Each unit test will only ever want to use a mocked version of a few + * library functions (i.e. not all of them). However, we need to mark all + * the mocked functions as wrapped (with -Wl,--wrap= in the LDFLAGS) in + * libcrmcommon_test.a so that all those unit tests can share the same + * special test library. The unit test then defines its own wrapped + * function. Because a unit test won't define every single wrapped + * function, there will be undefined references at link time. * * This file takes care of those undefined references. It defines a * wrapped version of every function that simply calls the real libc * version. These wrapped versions are defined with a weak attribute, * which means the unit tests can define another wrapped version for * unit testing that will override the version defined here. * * HOW TO ADD A MOCKED FUNCTION: * * - Define a __wrap_X function here below with the same prototype as the * actual function and that just calls __real_X. * - Add a __real_X and __wrap_X function prototype to mock_private.h. * - Add the function name to the WRAPPED variable in Makefile.am. * * HOW TO USE A MOCKED FUNCTION: * * - In the Makefile.am for your new test, add: * * your_fn_test_LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la -lcmocka * your_fn_test_LDFLAGS = -Wl,--wrap=X * * You can use multiple wrapped functions by adding multiple -Wl * arguments. * - #include "mock_private.h" in your test file. * - Add a __wrap_X function with the same prototype as the real function. * - Write your test cases, using will_return(), mock_type(), and * mock_ptr_type() from cmocka. See existing test cases for details. */ void *__attribute__((weak)) __wrap_calloc(size_t nmemb, size_t size) { return __real_calloc(nmemb, size); } char *__attribute__((weak)) __wrap_getenv(const char *name) { return __real_getenv(name); } int __attribute__((weak)) __wrap_getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { return __real_getpwnam_r(name, pwd, buf, buflen, result); } int __attribute__((weak)) __wrap_uname(struct utsname *buf) { return __real_uname(buf); } + +void __attribute__((weak)) +__wrap_setgrent(void) { + __real_setgrent(); +} + +struct group * __attribute__((weak)) +__wrap_getgrent(void) { + return __real_getgrent(); +} + +void __attribute__((weak)) +__wrap_endgrent(void) { + __real_endgrent(); +} + diff --git a/lib/common/mock_private.h b/lib/common/mock_private.h index 3df7c9839c..0c1134cc3a 100644 --- a/lib/common/mock_private.h +++ b/lib/common/mock_private.h @@ -1,34 +1,45 @@ /* * 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. */ #ifndef MOCK_PRIVATE__H # define MOCK_PRIVATE__H #include #include #include #include +#include /* This header is for the sole use of libcrmcommon_test. */ void *__real_calloc(size_t nmemb, size_t size); void *__wrap_calloc(size_t nmemb, size_t size); char *__real_getenv(const char *name); char *__wrap_getenv(const char *name); int __real_getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result); int __wrap_getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result); int __real_uname(struct utsname *buf); int __wrap_uname(struct utsname *buf); +void __real_setgrent(void); +void __wrap_setgrent(void); + +struct group *__real_getgrent(void); +struct group *__wrap_getgrent(void); + +void __real_endgrent(void); +void __wrap_endgrent(void); + + #endif // MOCK_PRIVATE__H diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index 679c9cb8e6..a73fc354c5 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -1,21 +1,28 @@ # -# Copyright 2021 the Pacemaker project contributors +# 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 General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/lib/common LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka +pcmk__is_user_in_group_test_LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la -lcmocka +pcmk__is_user_in_group_test_LDFLAGS = \ + -Wl,--wrap=setgrent \ + -Wl,--wrap=getgrent \ + -Wl,--wrap=endgrent + include $(top_srcdir)/mk/tap.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = \ + pcmk__is_user_in_group_test \ pcmk_acl_required_test \ xml_acl_denied_test \ xml_acl_enabled_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/acl/pcmk__is_user_in_group_test.c b/lib/common/tests/acl/pcmk__is_user_in_group_test.c new file mode 100644 index 0000000000..67b8c2c7c1 --- /dev/null +++ b/lib/common/tests/acl/pcmk__is_user_in_group_test.c @@ -0,0 +1,92 @@ +/* + * 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 "../../crmcommon_private.h" + +#include "mock_private.h" + +#include +#include +#include +#include +#include + +// THe index of the group that is going to be returned next from "get group entry" (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* (its 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) { + group_idx = 0; +} + +// 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(group_idx >= NUM_GROUPS) return NULL; + return &groups[group_idx++]; +} + +void +__wrap_endgrent(void) { +} + +static void +is_pcmk__is_user_in_group(void **state) +{ + // null user + assert_false(pcmk__is_user_in_group(NULL, "grp0")); + // null group + assert_false(pcmk__is_user_in_group("user0", NULL)); + // nonexistent group + assert_false(pcmk__is_user_in_group("user0", "nonexistent_group")); + // user is in group + assert_true(pcmk__is_user_in_group("user0", "grp0")); + // user is not in group + assert_false(pcmk__is_user_in_group("user2", "grp0")); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(is_pcmk__is_user_in_group) + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +}