1 /*
2 * Copyright 2004-2026 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 #include <unistd.h>
12 #include <stdbool.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <stdarg.h>
16 #include <string.h>
17 #include <pwd.h>
18
19 #include <sys/stat.h>
20 #include <sys/types.h>
21
22 #include <glib.h>
23
24 #include <crm/crm.h>
25 #include <crm/cib/internal.h>
26 #include <crm/common/xml.h>
27
28 static GHashTable *cib_op_callback_table = NULL;
29
30 static gint
31 ciblib_GCompareFunc(gconstpointer a, gconstpointer b)
32 {
33 int rc = 0;
34 const cib_notify_client_t *a_client = a;
35 const cib_notify_client_t *b_client = b;
36
37 CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
38 rc = strcmp(a_client->event, b_client->event);
39 if (rc == 0) {
40 if (a_client->callback == b_client->callback) {
41 return 0;
42 } else if (((long)a_client->callback) < ((long)b_client->callback)) {
43 pcmk__trace("callbacks for %s are not equal: %p < %p",
44 a_client->event, a_client->callback, b_client->callback);
45 return -1;
46 }
47 pcmk__trace("callbacks for %s are not equal: %p > %p", a_client->event,
48 a_client->callback, b_client->callback);
49 return 1;
50 }
51 return rc;
52 }
53
54 static int
55 cib_client_add_notify_callback(cib_t * cib, const char *event,
56 void (*callback) (const char *event,
57 xmlNode * msg))
58 {
59 GList *list_item = NULL;
60 cib_notify_client_t *new_client = NULL;
61
62 if ((cib->variant != cib_native) && (cib->variant != cib_remote)) {
63 return -EPROTONOSUPPORT;
64 }
65
66 pcmk__trace("Adding callback for %s events (%u)", event,
67 g_list_length(cib->notify_list));
68
69 new_client = pcmk__assert_alloc(1, sizeof(cib_notify_client_t));
70 new_client->event = event;
71 new_client->callback = callback;
72
73 list_item = g_list_find_custom(cib->notify_list, new_client,
74 ciblib_GCompareFunc);
75
76 if (list_item != NULL) {
77 pcmk__warn("Callback already present");
78 free(new_client);
79 return -EINVAL;
80
81 } else {
82 cib->notify_list = g_list_append(cib->notify_list, new_client);
83
84 cib->cmds->register_notification(cib, event, 1);
85
86 pcmk__trace("Callback added (%d)", g_list_length(cib->notify_list));
87 }
88 return pcmk_ok;
89 }
90
91 static int
92 get_notify_list_event_count(cib_t *cib, const char *event)
93 {
94 int count = 0;
95
96 for (GList *iter = g_list_first(cib->notify_list); iter != NULL;
97 iter = iter->next) {
98 cib_notify_client_t *client = (cib_notify_client_t *) iter->data;
99
100 if (strcmp(client->event, event) == 0) {
101 count++;
102 }
103 }
104 pcmk__trace("event(%s) count : %d", event, count);
105 return count;
106 }
107
108 static int
109 cib_client_del_notify_callback(cib_t *cib, const char *event,
110 void (*callback) (const char *event,
111 xmlNode *msg))
112 {
113 GList *list_item = NULL;
114 cib_notify_client_t *new_client = NULL;
115
116 if (cib->variant != cib_native && cib->variant != cib_remote) {
117 return -EPROTONOSUPPORT;
118 }
119
120 if (get_notify_list_event_count(cib, event) == 0) {
121 pcmk__debug("The callback of the event does not exist(%s)", event);
122 return pcmk_ok;
123 }
124
125 pcmk__debug("Removing callback for %s events", event);
126
127 new_client = pcmk__assert_alloc(1, sizeof(cib_notify_client_t));
128 new_client->event = event;
129 new_client->callback = callback;
130
131 list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc);
132
133 if (list_item != NULL) {
134 cib_notify_client_t *list_client = list_item->data;
135
136 cib->notify_list = g_list_remove(cib->notify_list, list_client);
137 free(list_client);
138
139 pcmk__trace("Removed callback");
140
141 } else {
142 pcmk__trace("Callback not present");
143 }
144
145 if (get_notify_list_event_count(cib, event) == 0) {
146 /* When there is not the registration of the event, the processing turns off a notice. */
147 cib->cmds->register_notification(cib, event, 0);
148 }
149
150 free(new_client);
151 return pcmk_ok;
152 }
153
154 static gboolean
155 cib_async_timeout_handler(gpointer data)
156 {
157 struct timer_rec_s *timer = data;
158
159 pcmk__debug("Async call %d timed out after %ds", timer->call_id,
160 timer->timeout);
161 cib_native_callback(timer->cib, NULL, timer->call_id, -ETIME);
162
163 // We remove the handler in remove_cib_op_callback()
164 return G_SOURCE_CONTINUE;
165 }
166
167 static gboolean
168 cib_client_register_callback_full(cib_t *cib, int call_id, int timeout,
169 gboolean only_success, void *user_data,
170 const char *callback_name,
171 void (*callback)(xmlNode *, int, int,
172 xmlNode *, void *),
173 void (*free_func)(void *))
174 {
175 cib_callback_client_t *blob = NULL;
176
177 if (call_id < 0) {
178 if (only_success == FALSE) {
179 callback(NULL, call_id, call_id, NULL, user_data);
180 } else {
181 pcmk__warn("CIB call failed: %s", pcmk_strerror(call_id));
182 }
183 if (user_data && free_func) {
184 free_func(user_data);
185 }
186 return FALSE;
187 }
188
189 blob = pcmk__assert_alloc(1, sizeof(cib_callback_client_t));
190 blob->id = callback_name;
191 blob->only_success = only_success;
192 blob->user_data = user_data;
193 blob->callback = callback;
194 blob->free_func = free_func;
195
196 if (timeout > 0) {
197 struct timer_rec_s *async_timer =
198 pcmk__assert_alloc(1, sizeof(struct timer_rec_s));
199
200 blob->timer = async_timer;
201
202 async_timer->cib = cib;
203 async_timer->call_id = call_id;
204 async_timer->timeout = timeout * 1000;
205 async_timer->ref = pcmk__create_timer(async_timer->timeout,
206 cib_async_timeout_handler,
207 async_timer);
208 }
209
210 pcmk__trace("Adding callback %s for call %d", callback_name, call_id);
211 pcmk__intkey_table_insert(cib_op_callback_table, call_id, blob);
212
213 return TRUE;
214 }
215
216 static gboolean
217 cib_client_register_callback(cib_t *cib, int call_id, int timeout,
218 gboolean only_success, void *user_data,
219 const char *callback_name,
220 void (*callback) (xmlNode *, int, int, xmlNode *,
221 void *))
222 {
223 return cib_client_register_callback_full(cib, call_id, timeout,
224 only_success, user_data,
225 callback_name, callback, NULL);
226 }
227
228 static int
229 cib_client_noop(cib_t * cib, int call_options)
230 {
231 return cib_internal_op(cib, PCMK__CIB_REQUEST_NOOP, NULL, NULL, NULL, NULL,
232 call_options, cib->user);
233 }
234
235 static int
236 cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options)
237 {
238 return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data,
239 call_options, cib->user);
240 }
241
242 static int
243 cib_client_query(cib_t * cib, const char *section, xmlNode ** output_data, int call_options)
244 {
245 return cib->cmds->query_from(cib, NULL, section, output_data, call_options);
246 }
247
248 static int
249 cib_client_query_from(cib_t * cib, const char *host, const char *section,
250 xmlNode ** output_data, int call_options)
251 {
252 return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, host, section, NULL,
253 output_data, call_options, cib->user);
254 }
255
256 static int
257 set_secondary(cib_t *cib, int call_options)
258 {
259 return cib_internal_op(cib, PCMK__CIB_REQUEST_SECONDARY, NULL, NULL, NULL,
260 NULL, call_options, cib->user);
261 }
262
263 static int
264 set_primary(cib_t *cib, int call_options)
265 {
266 return cib_internal_op(cib, PCMK__CIB_REQUEST_PRIMARY, NULL, NULL, NULL,
267 NULL, call_options, cib->user);
268 }
269
270 static int
271 cib_client_bump_epoch(cib_t * cib, int call_options)
272 {
273 return cib_internal_op(cib, PCMK__CIB_REQUEST_BUMP, NULL, NULL, NULL, NULL,
274 call_options, cib->user);
275 }
276
277 static int
278 cib_client_upgrade(cib_t * cib, int call_options)
279 {
280 return cib_internal_op(cib, PCMK__CIB_REQUEST_UPGRADE, NULL, NULL, NULL,
281 NULL, call_options, cib->user);
282 }
283
284 static int
285 cib_client_sync(cib_t * cib, const char *section, int call_options)
286 {
287 return cib->cmds->sync_from(cib, NULL, section, call_options);
288 }
289
290 static int
291 cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options)
292 {
293 return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC, host, section, NULL,
294 NULL, call_options, cib->user);
295 }
296
297 static int
298 cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_options)
299 {
300 return cib_internal_op(cib, PCMK__CIB_REQUEST_CREATE, NULL, section, data,
301 NULL, call_options, cib->user);
302 }
303
304 static int
305 cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_options)
306 {
307 return cib_internal_op(cib, PCMK__CIB_REQUEST_MODIFY, NULL, section, data,
308 NULL, call_options, cib->user);
309 }
310
311 static int
312 cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_options)
313 {
314 return cib_internal_op(cib, PCMK__CIB_REQUEST_REPLACE, NULL, section, data,
315 NULL, call_options, cib->user);
316 }
317
318 static int
319 cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_options)
320 {
321 return cib_internal_op(cib, PCMK__CIB_REQUEST_DELETE, NULL, section, data,
322 NULL, call_options, cib->user);
323 }
324
325 static int
326 cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options)
327 {
328 return cib_internal_op(cib, PCMK__CIB_REQUEST_ERASE, NULL, NULL, NULL,
329 output_data, call_options, cib->user);
330 }
331
332 static int
333 cib_client_init_transaction(cib_t *cib)
334 {
335 int rc = pcmk_rc_ok;
336
337 if (cib == NULL) {
338 return -EINVAL;
339 }
340
341 if (cib->transaction != NULL) {
342 // A client can have at most one transaction at a time
343 rc = pcmk_rc_already;
344 }
345
346 if (rc == pcmk_rc_ok) {
347 cib->transaction = pcmk__xe_create(NULL, PCMK__XE_CIB_TRANSACTION);
348 }
349
350 if (rc != pcmk_rc_ok) {
351 const char *client_id = NULL;
352
353 cib->cmds->client_id(cib, NULL, &client_id);
354 pcmk__err("Failed to initialize CIB transaction for client %s: %s",
355 client_id, pcmk_rc_str(rc));
356 }
357 return pcmk_rc2legacy(rc);
358 }
359
360 static int
361 cib_client_end_transaction(cib_t *cib, bool commit, int call_options)
362 {
363 const char *client_id = NULL;
364 int rc = pcmk_ok;
365
366 if (cib == NULL) {
367 return -EINVAL;
368 }
369
370 cib->cmds->client_id(cib, NULL, &client_id);
371 client_id = pcmk__s(client_id, "(unidentified)");
372
373 if (commit) {
374 if (cib->transaction == NULL) {
375 rc = pcmk_rc_no_transaction;
376
377 pcmk__err("Failed to commit transaction for CIB client %s: %s",
378 client_id, pcmk_rc_str(rc));
379 return pcmk_rc2legacy(rc);
380 }
381 rc = cib_internal_op(cib, PCMK__CIB_REQUEST_COMMIT_TRANSACT, NULL, NULL,
382 cib->transaction, NULL, call_options, cib->user);
383
384 } else {
385 // Discard always succeeds
386 if (cib->transaction != NULL) {
387 pcmk__trace("Discarded transaction for CIB client %s", client_id);
388 } else {
389 pcmk__trace("No transaction found for CIB client %s", client_id);
390 }
391 }
392
393 g_clear_pointer(&cib->transaction, pcmk__xml_free);
394 return rc;
395 }
396
397 static int
398 cib_client_fetch_schemas(cib_t *cib, xmlNode **output_data, const char *after_ver,
399 int call_options)
400 {
401 xmlNode *data = pcmk__xe_create(NULL, PCMK__XA_SCHEMA);
402 int rc = pcmk_ok;
403
404 pcmk__xe_set(data, PCMK_XA_VERSION, after_ver);
405
406 rc = cib_internal_op(cib, PCMK__CIB_REQUEST_SCHEMAS, NULL, NULL, data,
407 output_data, call_options, NULL);
408 pcmk__xml_free(data);
409 return rc;
410 }
411
412 static void
413 cib_client_set_user(cib_t *cib, const char *user)
414 {
415 pcmk__str_update(&(cib->user), user);
416 }
417
418 static void
419 cib_destroy_op_callback(gpointer data)
420 {
421 cib_callback_client_t *blob = data;
422
423 if (blob->timer && blob->timer->ref > 0) {
424 g_source_remove(blob->timer->ref);
425 }
426 free(blob->timer);
427
428 if (blob->user_data && blob->free_func) {
429 blob->free_func(blob->user_data);
430 }
431
432 free(blob);
433 }
434
435 static void
436 destroy_op_callback_table(void)
437 {
438 g_clear_pointer(&cib_op_callback_table, g_hash_table_destroy);
439 }
440
441 char *
442 get_shadow_file(const char *suffix)
443 {
444 char *cib_home = NULL;
445 char *fullname = NULL;
446 char *name = pcmk__assert_asprintf("shadow.%s", suffix);
447 const char *dir = getenv("CIB_shadow_dir");
448
449 if (dir == NULL) {
450 /* @TODO This basically duplicates pcmk__uid2username(), but we need the
451 * password database entry, not just the user name from it. We should
452 * reduce the duplication.
453 */
454 struct passwd *pwent = NULL;
455 const char *user = NULL;
456
457 errno = 0;
458 pwent = getpwuid(geteuid());
459
460 if (pwent) {
461 user = pwent->pw_name;
462
463 } else {
464 // Save errno before getenv()
465 int rc = errno;
466
467 user = getenv("USER");
468 pcmk__warn("Could not get password database entry for effective "
469 "user ID %lld: %s. Assuming user is %s.",
470 (long long) geteuid(),
471 ((rc != 0)? strerror(rc) : "No matching entry found"),
472 pcmk__s(user, "unprivileged user"));
473 }
474
475 if (pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
476 dir = CRM_CONFIG_DIR;
477
478 } else {
479 const char *home = NULL;
480
481 if ((home = getenv("HOME")) == NULL) {
482 if (pwent) {
483 home = pwent->pw_dir;
484 }
485 }
486
487 dir = pcmk__get_tmpdir();
488 if (home && home[0] == '/') {
489 int rc = 0;
490
491 cib_home = pcmk__assert_asprintf("%s/.cib", home);
492
493 rc = mkdir(cib_home, 0700);
494 if (rc < 0 && errno != EEXIST) {
495 pcmk__err("Couldn't create user-specific shadow directory "
496 "%s: %s",
497 cib_home, strerror(errno));
498
499 } else {
500 dir = cib_home;
501 }
502 }
503 }
504 }
505
506 fullname = pcmk__assert_asprintf("%s/%s", dir, name);
507 free(cib_home);
508 free(name);
509
510 return fullname;
511 }
512
513 cib_t *
514 cib_shadow_new(const char *shadow)
515 {
516 cib_t *new_cib = NULL;
517 char *shadow_file = NULL;
518
519 CRM_CHECK(shadow != NULL, return NULL);
520
521 shadow_file = get_shadow_file(shadow);
522 new_cib = cib_file_new(shadow_file);
523 free(shadow_file);
524
525 return new_cib;
526 }
527
528 /*!
529 * \brief Create a new CIB connection object
530 *
531 * Create a new live, remote, file, or shadow file CIB connection object based
532 * on the values of CIB-related environment variables (CIB_shadow, CIB_file,
533 * CIB_port, CIB_server, CIB_user, and CIB_passwd). The object will not be
534 * connected.
535 *
536 * \return Newly allocated CIB connection object
537 * \note The CIB API does not fully support opening multiple CIB connection
538 * objects simultaneously, so the returned object should be treated as a
539 * singleton.
540 */
541 /* @TODO Ensure all APIs support multiple simultaneous CIB connection objects
542 * (at least cib_free_callbacks() currently does not).
543 */
544 cib_t *
545 cib_new(void)
546 {
547 const char *value = getenv("CIB_shadow");
548 const char *server = NULL;
549 const char *user = NULL;
550 const char *pass = NULL;
551 gboolean encrypted = TRUE;
552 int port;
553
554 if (!pcmk__str_empty(value)) {
555 return cib_shadow_new(value);
556 }
557
558 value = getenv("CIB_file");
559 if (!pcmk__str_empty(value)) {
560 return cib_file_new(value);
561 }
562
563 value = getenv("CIB_port");
564 if (pcmk__str_empty(value)) {
565 return cib_native_new();
566 }
567
568 /* We don't ensure port is valid (>= 0) because cib_new() currently can't
569 * return NULL in practice, and introducing a NULL return here could cause
570 * core dumps that would previously just cause signon() failures.
571 */
572 pcmk__scan_port(value, &port);
573
574 if (!pcmk__is_true(getenv("CIB_encrypted"))) {
575 encrypted = FALSE;
576 }
577
578 server = getenv("CIB_server");
579 user = getenv("CIB_user");
580 pass = getenv("CIB_passwd");
581
582 if (pcmk__str_empty(user)) {
583 user = CRM_DAEMON_USER;
584 }
585
586 if (pcmk__str_empty(server)) {
587 server = "localhost";
588 }
589
590 pcmk__debug("Initializing %s remote CIB access to %s:%d as user %s",
591 (encrypted? "encrypted" : "plain-text"), server, port, user);
592 return cib_remote_new(server, user, pass, port, encrypted);
593 }
594
595 /*!
596 * \internal
597 * \brief Create a generic CIB connection instance
598 *
599 * \return Newly allocated and initialized cib_t instance
600 *
601 * \note This is called by each variant's cib_*_new() function before setting
602 * variant-specific values.
603 */
604 cib_t *
605 cib_new_variant(void)
606 {
607 cib_t *new_cib = NULL;
608
609 new_cib = calloc(1, sizeof(cib_t));
610
|
(1) Event path: |
Condition "new_cib == NULL", taking false branch. |
611 if (new_cib == NULL) {
612 return NULL;
613 }
614
615 remove_cib_op_callback(0, TRUE); /* remove all */
616
617 new_cib->call_id = 1;
618 new_cib->variant = cib_undefined;
619
620 new_cib->type = cib_no_connection;
621 new_cib->state = cib_disconnected;
622 new_cib->variant_opaque = NULL;
623 new_cib->notify_list = NULL;
624
625 /* the rest will get filled in by the variant constructor */
|
(2) Event alloc_fn: |
Storage is returned from allocation function "calloc". |
|
(3) Event assign: |
Assigning: "new_cib->cmds" = "calloc(1UL, 232UL)". |
| Also see events: |
[return_alloc] |
626 new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
627
|
(4) Event path: |
Condition "new_cib->cmds == NULL", taking false branch. |
628 if (new_cib->cmds == NULL) {
629 free(new_cib);
630 return NULL;
631 }
632
633 new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
634 new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
635 new_cib->cmds->register_callback = cib_client_register_callback;
636 new_cib->cmds->register_callback_full = cib_client_register_callback_full;
637
638 new_cib->cmds->noop = cib_client_noop; // Deprecated method
639 new_cib->cmds->ping = cib_client_ping;
640 new_cib->cmds->query = cib_client_query;
641 new_cib->cmds->sync = cib_client_sync;
642
643 new_cib->cmds->query_from = cib_client_query_from;
644 new_cib->cmds->sync_from = cib_client_sync_from;
645
646 new_cib->cmds->set_primary = set_primary;
647 new_cib->cmds->set_secondary = set_secondary;
648
649 new_cib->cmds->upgrade = cib_client_upgrade;
650 new_cib->cmds->bump_epoch = cib_client_bump_epoch;
651
652 new_cib->cmds->create = cib_client_create;
653 new_cib->cmds->modify = cib_client_modify;
654 new_cib->cmds->replace = cib_client_replace;
655 new_cib->cmds->remove = cib_client_delete;
656 new_cib->cmds->erase = cib_client_erase;
657
658 new_cib->cmds->init_transaction = cib_client_init_transaction;
659 new_cib->cmds->end_transaction = cib_client_end_transaction;
660
661 new_cib->cmds->set_user = cib_client_set_user;
662
663 new_cib->cmds->fetch_schemas = cib_client_fetch_schemas;
664
|
(5) Event return_alloc: |
Returning "new_cib", where "new_cib->cmds" is allocated memory. |
| Also see events: |
[alloc_fn][assign] |
665 return new_cib;
666 }
667
668 void
669 cib_free_notify(cib_t *cib)
670 {
671
672 if (cib) {
673 GList *list = cib->notify_list;
674
675 while (list != NULL) {
676 cib_notify_client_t *client = g_list_nth_data(list, 0);
677
678 list = g_list_remove(list, client);
679 free(client);
680 }
681 cib->notify_list = NULL;
682 }
683 }
684
685 /*!
686 * \brief Free all callbacks for a CIB connection
687 *
688 * \param[in,out] cib CIB connection to clean up
689 */
690 void
691 cib_free_callbacks(cib_t *cib)
692 {
693 cib_free_notify(cib);
694
695 destroy_op_callback_table();
696 }
697
698 /*!
699 * \brief Free all memory used by CIB connection
700 *
701 * \param[in,out] cib CIB connection to delete
702 */
703 void
704 cib_delete(cib_t *cib)
705 {
706 cib_free_callbacks(cib);
707 if (cib) {
708 cib->cmds->free(cib);
709 }
710 }
711
712 void
713 remove_cib_op_callback(int call_id, gboolean all_callbacks)
714 {
715 if (all_callbacks) {
716 destroy_op_callback_table();
717 cib_op_callback_table = pcmk__intkey_table(cib_destroy_op_callback);
718 } else {
719 pcmk__intkey_table_remove(cib_op_callback_table, call_id);
720 }
721 }
722
723 int
724 num_cib_op_callbacks(void)
725 {
726 if (cib_op_callback_table == NULL) {
727 return 0;
728 }
729 return g_hash_table_size(cib_op_callback_table);
730 }
731
732 static void
733 cib_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
734 {
735 int call = GPOINTER_TO_INT(key);
736 cib_callback_client_t *blob = value;
737
738 pcmk__debug("Call %d (%s): pending", call, pcmk__s(blob->id, "without ID"));
739 }
740
741 void
742 cib_dump_pending_callbacks(void)
743 {
744 if (cib_op_callback_table == NULL) {
745 return;
746 }
747 return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
748 }
749
750 cib_callback_client_t*
751 cib__lookup_id (int call_id)
752 {
753 return pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
754 }
755
756 // Deprecated functions kept only for backward API compatibility
757 // LCOV_EXCL_START
758
759 #include <crm/cib_compat.h>
760
761 cib_t *
762 cib_new_no_shadow(void)
763 {
764 const char *shadow = getenv("CIB_shadow");
765 cib_t *cib = NULL;
766
767 unsetenv("CIB_shadow");
768 cib = cib_new();
769
770 if (shadow != NULL) {
771 setenv("CIB_shadow", shadow, 1);
772 }
773 return cib;
774 }
775
776 // LCOV_EXCL_STOP
777 // End deprecated API
778