1 /*
2 * Copyright 2021-2023 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <errno.h>
13 #include <pwd.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <stddef.h>
17 #include <stdint.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <setjmp.h>
22 #include <sys/types.h>
23 #include <sys/utsname.h>
24 #include <unistd.h>
25 #include <grp.h>
26
27 #include <cmocka.h>
28 #include "mock_private.h"
29
30 /* This file is only used when running "make check". It is built into
31 * libcrmcommon_test.a, not into libcrmcommon.so. It is used to support
32 * constructing mock versions of library functions for unit testing.
33 *
34 * HOW TO ADD A MOCKED FUNCTION:
35 *
36 * - In this file, declare a bool pcmk__mock_X variable, and define a __wrap_X
37 * function with the same prototype as the actual function that performs the
38 * desired behavior if pcmk__mock_X is true and calls __real_X otherwise.
39 * You can use cmocka's mock_type() and mock_ptr_type() to pass extra
40 * information to the mocked function (see existing examples for details).
41 *
42 * - In mock_private.h, add declarations for extern bool pcmk__mock_X and the
43 * __real_X and __wrap_X function prototypes.
44 *
45 * - In mk/tap.mk, add the function name to the WRAPPED variable.
46 *
47 * HOW TO USE A MOCKED FUNCTION:
48 *
49 * - #include "mock_private.h" in your test file.
50 *
51 * - Write your test cases using pcmk__mock_X and cmocka's will_return() as
52 * needed per the comments for the mocked function below. See existing test
53 * cases for examples.
54 */
55
56 // LCOV_EXCL_START
57 /* calloc()
58 *
59 * If pcmk__mock_calloc is set to true, later calls to calloc() will return
60 * NULL and must be preceded by:
61 *
62 * expect_*(__wrap_calloc, nmemb[, ...]);
63 * expect_*(__wrap_calloc, size[, ...]);
64 *
65 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
66 */
67
68 bool pcmk__mock_calloc = false;
69
70 void *
71 __wrap_calloc(size_t nmemb, size_t size)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
72 {
73 if (!pcmk__mock_calloc) {
74 return __real_calloc(nmemb, size);
75 }
76 check_expected(nmemb);
77 check_expected(size);
78 return NULL;
79 }
80
81
82 /* getenv()
83 *
84 * If pcmk__mock_getenv is set to true, later calls to getenv() must be preceded
85 * by:
86 *
87 * expect_*(__wrap_getenv, name[, ...]);
88 * will_return(__wrap_getenv, return_value);
89 *
90 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
91 */
92
93 bool pcmk__mock_getenv = false;
94
95 char *
96 __wrap_getenv(const char *name)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
97 {
98 if (!pcmk__mock_getenv) {
99 return __real_getenv(name);
100 }
101 check_expected_ptr(name);
102 return mock_ptr_type(char *);
103 }
104
105
106 /* setenv()
107 *
108 * If pcmk__mock_setenv is set to true, later calls to setenv() must be preceded
109 * by:
110 *
111 * expect_*(__wrap_setenv, name[, ...]);
112 * expect_*(__wrap_setenv, value[, ...]);
113 * expect_*(__wrap_setenv, overwrite[, ...]);
114 * will_return(__wrap_setenv, errno_to_set);
115 *
116 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
117 *
118 * The mocked function will return 0 if errno_to_set is 0, and -1 otherwise.
119 */
120 bool pcmk__mock_setenv = false;
121
122 int
123 __wrap_setenv(const char *name, const char *value, int overwrite)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
124 {
125 if (!pcmk__mock_setenv) {
126 return __real_setenv(name, value, overwrite);
127 }
128 check_expected_ptr(name);
129 check_expected_ptr(value);
130 check_expected(overwrite);
131 errno = mock_type(int);
132 return (errno == 0)? 0 : -1;
133 }
134
135
136 /* unsetenv()
137 *
138 * If pcmk__mock_unsetenv is set to true, later calls to unsetenv() must be
139 * preceded by:
140 *
141 * expect_*(__wrap_unsetenv, name[, ...]);
142 * will_return(__wrap_setenv, errno_to_set);
143 *
144 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
145 *
146 * The mocked function will return 0 if errno_to_set is 0, and -1 otherwise.
147 */
148 bool pcmk__mock_unsetenv = false;
149
150 int
151 __wrap_unsetenv(const char *name)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
152 {
153 if (!pcmk__mock_unsetenv) {
154 return __real_unsetenv(name);
155 }
156 check_expected_ptr(name);
157 errno = mock_type(int);
158 return (errno == 0)? 0 : -1;
159 }
160
161
162 /* getpid()
163 *
164 * If pcmk__mock_getpid is set to true, later calls to getpid() must be preceded
165 * by:
166 *
167 * will_return(__wrap_getpid, return_value);
168 */
169
170 bool pcmk__mock_getpid = false;
171
172 pid_t
173 __wrap_getpid(void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
174 {
175 return pcmk__mock_getpid? mock_type(pid_t) : __real_getpid();
176 }
177
178
179 /* setgrent(), getgrent() and endgrent()
180 *
181 * If pcmk__mock_grent is set to true, getgrent() will behave as if the only
182 * groups on the system are:
183 *
184 * - grp0 (user0, user1)
185 * - grp1 (user1)
186 * - grp2 (user2, user1)
187 */
188
189 bool pcmk__mock_grent = false;
190
191 // Index of group that will be returned next from getgrent()
192 static int group_idx = 0;
193
194 // Data used for testing
195 static const char* grp0_members[] = {
196 "user0", "user1", NULL
197 };
198
199 static const char* grp1_members[] = {
200 "user1", NULL
201 };
202
203 static const char* grp2_members[] = {
204 "user2", "user1", NULL
205 };
206
207 /* An array of "groups" (a struct from grp.h)
208 *
209 * The members of the groups are initalized here to some testing data, casting
210 * away the consts to make the compiler happy and simplify initialization. We
211 * never actually change these variables during the test!
212 *
213 * string literal = const char* (cannot be changed b/c ? )
214 * vs. char* (it's getting casted to this)
215 */
216 static const int NUM_GROUPS = 3;
217 static struct group groups[] = {
218 {(char*)"grp0", (char*)"", 0, (char**)grp0_members},
219 {(char*)"grp1", (char*)"", 1, (char**)grp1_members},
220 {(char*)"grp2", (char*)"", 2, (char**)grp2_members},
221 };
222
223 // This function resets the group_idx to 0.
224 void
225 __wrap_setgrent(void) {
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
226 if (pcmk__mock_grent) {
227 group_idx = 0;
228 } else {
229 __real_setgrent();
230 }
231 }
232
233 /* This function returns the next group entry in the list of groups, or
234 * NULL if there aren't any left.
235 * group_idx is a global variable which keeps track of where you are in the list
236 */
237 struct group *
238 __wrap_getgrent(void) {
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
239 if (pcmk__mock_grent) {
240 if (group_idx >= NUM_GROUPS) {
241 return NULL;
242 }
243 return &groups[group_idx++];
244 } else {
245 return __real_getgrent();
246 }
247 }
248
249 void
250 __wrap_endgrent(void) {
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
251 if (!pcmk__mock_grent) {
252 __real_endgrent();
253 }
254 }
255
256
257 /* fopen()
258 *
259 * If pcmk__mock_fopen is set to true, later calls to fopen() must be
260 * preceded by:
261 *
262 * expect_*(__wrap_fopen, pathname[, ...]);
263 * expect_*(__wrap_fopen, mode[, ...]);
264 * will_return(__wrap_fopen, errno_to_set);
265 *
266 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
267 *
268 * This has two mocked functions, since fopen() is sometimes actually fopen64().
269 */
270
271 bool pcmk__mock_fopen = false;
272
273 FILE *
274 __wrap_fopen(const char *pathname, const char *mode)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
275 {
276 if (pcmk__mock_fopen) {
277 check_expected_ptr(pathname);
278 check_expected_ptr(mode);
279 errno = mock_type(int);
280
281 if (errno != 0) {
282 return NULL;
283 } else {
284 return __real_fopen(pathname, mode);
285 }
286
287 } else {
288 return __real_fopen(pathname, mode);
289 }
290 }
291
292 #ifdef HAVE_FOPEN64
293 FILE *
294 __wrap_fopen64(const char *pathname, const char *mode)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
295 {
296 if (pcmk__mock_fopen) {
297 check_expected_ptr(pathname);
298 check_expected_ptr(mode);
299 errno = mock_type(int);
300
301 if (errno != 0) {
302 return NULL;
303 } else {
304 return __real_fopen64(pathname, mode);
305 }
306
307 } else {
308 return __real_fopen64(pathname, mode);
309 }
310 }
311 #endif
312
313 /* getpwnam_r()
314 *
315 * If pcmk__mock_getpwnam_r is set to true, later calls to getpwnam_r() must be
316 * preceded by:
317 *
318 * expect_*(__wrap_getpwnam_r, name[, ...]);
319 * expect_*(__wrap_getpwnam_r, pwd[, ...]);
320 * expect_*(__wrap_getpwnam_r, buf[, ...]);
321 * expect_*(__wrap_getpwnam_r, buflen[, ...]);
322 * expect_*(__wrap_getpwnam_r, result[, ...]);
323 * will_return(__wrap_getpwnam_r, return_value);
324 * will_return(__wrap_getpwnam_r, ptr_to_result_struct);
325 *
326 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
327 */
328
329 bool pcmk__mock_getpwnam_r = false;
330
331 int
332 __wrap_getpwnam_r(const char *name, struct passwd *pwd, char *buf,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
333 size_t buflen, struct passwd **result)
334 {
335 if (pcmk__mock_getpwnam_r) {
336 int retval = mock_type(int);
337
338 check_expected_ptr(name);
339 check_expected_ptr(pwd);
340 check_expected_ptr(buf);
341 check_expected(buflen);
342 check_expected_ptr(result);
343 *result = mock_ptr_type(struct passwd *);
344 return retval;
345
346 } else {
347 return __real_getpwnam_r(name, pwd, buf, buflen, result);
348 }
349 }
350
351 /*
352 * If pcmk__mock_readlink is set to true, later calls to readlink() must be
353 * preceded by:
354 *
355 * expect_*(__wrap_readlink, path[, ...]);
356 * expect_*(__wrap_readlink, buf[, ...]);
357 * expect_*(__wrap_readlink, bufsize[, ...]);
358 * will_return(__wrap_readlink, errno_to_set);
359 * will_return(__wrap_readlink, link_contents);
360 *
361 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
362 *
363 * The mocked function will return 0 if errno_to_set is 0, and -1 otherwise.
364 */
365
366 bool pcmk__mock_readlink = false;
367
368 ssize_t
369 __wrap_readlink(const char *restrict path, char *restrict buf,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
370 size_t bufsize)
371 {
372 if (pcmk__mock_readlink) {
373 const char *contents = NULL;
374
375 check_expected_ptr(path);
376 check_expected_ptr(buf);
377 check_expected(bufsize);
378 errno = mock_type(int);
379 contents = mock_ptr_type(const char *);
380
381 if (errno == 0) {
382 strncpy(buf, contents, bufsize - 1);
383 return strlen(contents);
384 }
385 return -1;
386
387 } else {
388 return __real_readlink(path, buf, bufsize);
389 }
390 }
391
392
393 /* strdup()
394 *
395 * If pcmk__mock_strdup is set to true, later calls to strdup() will return
396 * NULL and must be preceded by:
397 *
398 * expect_*(__wrap_strdup, s[, ...]);
399 *
400 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
401 */
402
403 bool pcmk__mock_strdup = false;
404
405 char *
406 __wrap_strdup(const char *s)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
407 {
408 if (!pcmk__mock_strdup) {
409 return __real_strdup(s);
410 }
411 check_expected_ptr(s);
412 return NULL;
413 }
414
415
416 /* uname()
417 *
418 * If pcmk__mock_uname is set to true, later calls to uname() must be preceded
419 * by:
420 *
421 * expect_*(__wrap_uname, buf[, ...]);
422 * will_return(__wrap_uname, return_value);
423 * will_return(__wrap_uname, node_name_for_buf_parameter_to_uname);
424 *
425 * expect_* functions: https://api.cmocka.org/group__cmocka__param.html
426 */
427
428 bool pcmk__mock_uname = false;
429
430 int
431 __wrap_uname(struct utsname *buf)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
432 {
433 if (pcmk__mock_uname) {
434 int retval = 0;
435 char *result = NULL;
436
437 check_expected_ptr(buf);
438 retval = mock_type(int);
439 result = mock_ptr_type(char *);
440
441 if (result != NULL) {
442 strcpy(buf->nodename, result);
443 }
444 return retval;
445
446 } else {
447 return __real_uname(buf);
448 }
449 }
450
451 // LCOV_EXCL_STOP