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 int
31 ciblib_GCompareFunc(const void *a, const void *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(void *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 // @COMPAT cib_api_operations_t:sync is deprecated since 3.1.0
285 static int
286 cib_client_sync(cib_t * cib, const char *section, int call_options)
287 {
288 return cib->cmds->sync_from(cib, NULL, section, call_options);
289 }
290
291 static int
292 cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options)
293 {
294 return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC, host, section, NULL,
295 NULL, call_options, cib->user);
296 }
297
298 static int
299 cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_options)
300 {
301 return cib_internal_op(cib, PCMK__CIB_REQUEST_CREATE, NULL, section, data,
302 NULL, call_options, cib->user);
303 }
304
305 static int
306 cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_options)
307 {
308 return cib_internal_op(cib, PCMK__CIB_REQUEST_MODIFY, NULL, section, data,
309 NULL, call_options, cib->user);
310 }
311
312 static int
313 cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_options)
314 {
315 return cib_internal_op(cib, PCMK__CIB_REQUEST_REPLACE, NULL, section, data,
316 NULL, call_options, cib->user);
317 }
318
319 static int
320 cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_options)
321 {
322 return cib_internal_op(cib, PCMK__CIB_REQUEST_DELETE, NULL, section, data,
323 NULL, call_options, cib->user);
324 }
325
326 static int
327 cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options)
328 {
329 return cib_internal_op(cib, PCMK__CIB_REQUEST_ERASE, NULL, NULL, NULL,
330 output_data, call_options, cib->user);
331 }
332
333 static int
334 cib_client_init_transaction(cib_t *cib)
335 {
336 int rc = pcmk_rc_ok;
337
338 if (cib == NULL) {
339 return -EINVAL;
340 }
341
342 if (cib->transaction != NULL) {
343 // A client can have at most one transaction at a time
344 rc = pcmk_rc_already;
345 }
346
347 if (rc == pcmk_rc_ok) {
348 cib->transaction = pcmk__xe_create(NULL, PCMK__XE_CIB_TRANSACTION);
349 }
350
351 if (rc != pcmk_rc_ok) {
352 const char *client_id = NULL;
353
354 cib->cmds->client_id(cib, NULL, &client_id);
355 pcmk__err("Failed to initialize CIB transaction for client %s: %s",
356 client_id, pcmk_rc_str(rc));
357 }
358 return pcmk_rc2legacy(rc);
359 }
360
361 static int
362 cib_client_end_transaction(cib_t *cib, bool commit, int call_options)
363 {
364 const char *client_id = NULL;
365 int rc = pcmk_ok;
366
367 if (cib == NULL) {
368 return -EINVAL;
369 }
370
371 cib->cmds->client_id(cib, NULL, &client_id);
372 client_id = pcmk__s(client_id, "(unidentified)");
373
374 if (commit) {
375 if (cib->transaction == NULL) {
376 rc = pcmk_rc_no_transaction;
377
378 pcmk__err("Failed to commit transaction for CIB client %s: %s",
379 client_id, pcmk_rc_str(rc));
380 return pcmk_rc2legacy(rc);
381 }
382 rc = cib_internal_op(cib, PCMK__CIB_REQUEST_COMMIT_TRANSACT, NULL, NULL,
383 cib->transaction, NULL, call_options, cib->user);
384
385 } else {
386 // Discard always succeeds
387 if (cib->transaction != NULL) {
388 pcmk__trace("Discarded transaction for CIB client %s", client_id);
389 } else {
390 pcmk__trace("No transaction found for CIB client %s", client_id);
391 }
392 }
393
394 g_clear_pointer(&cib->transaction, pcmk__xml_free);
395 return rc;
396 }
397
398 static int
399 cib_client_fetch_schemas(cib_t *cib, xmlNode **output_data, const char *after_ver,
400 int call_options)
401 {
402 xmlNode *data = pcmk__xe_create(NULL, PCMK__XA_SCHEMA);
403 int rc = pcmk_ok;
404
405 pcmk__xe_set(data, PCMK_XA_VERSION, after_ver);
406
407 rc = cib_internal_op(cib, PCMK__CIB_REQUEST_SCHEMAS, NULL, NULL, data,
408 output_data, call_options, NULL);
409 pcmk__xml_free(data);
410 return rc;
411 }
412
413 static void
414 cib_client_set_user(cib_t *cib, const char *user)
415 {
416 pcmk__str_update(&(cib->user), user);
417 }
418
419 static void
420 cib_destroy_op_callback(void *data)
421 {
422 cib_callback_client_t *blob = data;
423
424 if (blob->timer && blob->timer->ref > 0) {
425 g_source_remove(blob->timer->ref);
426 }
427 free(blob->timer);
428
429 if (blob->user_data && blob->free_func) {
430 blob->free_func(blob->user_data);
431 }
432
433 free(blob);
434 }
435
436 static void
437 destroy_op_callback_table(void)
438 {
439 g_clear_pointer(&cib_op_callback_table, g_hash_table_destroy);
440 }
441
442 char *
443 get_shadow_file(const char *suffix)
444 {
445 char *cib_home = NULL;
446 char *fullname = NULL;
447 char *name = pcmk__assert_asprintf("shadow.%s", suffix);
448 const char *dir = getenv("CIB_shadow_dir");
449
450 if (dir == NULL) {
451 /* @TODO This basically duplicates pcmk__uid2username(), but we need the
452 * password database entry, not just the user name from it. We should
453 * reduce the duplication.
454 */
455 struct passwd *pwent = NULL;
456 const char *user = NULL;
457
458 errno = 0;
459 pwent = getpwuid(geteuid());
460
461 if (pwent) {
462 user = pwent->pw_name;
463
464 } else {
465 // Save errno before getenv()
466 int rc = errno;
467
468 user = getenv("USER");
469 pcmk__warn("Could not get password database entry for effective "
470 "user ID %lld: %s. Assuming user is %s.",
471 (long long) geteuid(),
472 ((rc != 0)? strerror(rc) : "No matching entry found"),
473 pcmk__s(user, "unprivileged user"));
474 }
475
476 if (pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
477 dir = CRM_CONFIG_DIR;
478
479 } else {
480 const char *home = NULL;
481
482 if ((home = getenv("HOME")) == NULL) {
483 if (pwent) {
484 home = pwent->pw_dir;
485 }
486 }
487
488 dir = pcmk__get_tmpdir();
489 if (home && home[0] == '/') {
490 int rc = 0;
491
492 cib_home = pcmk__assert_asprintf("%s/.cib", home);
493
494 rc = mkdir(cib_home, 0700);
495 if (rc < 0 && errno != EEXIST) {
496 pcmk__err("Couldn't create user-specific shadow directory "
497 "%s: %s",
498 cib_home, strerror(errno));
499
500 } else {
501 dir = cib_home;
502 }
503 }
504 }
505 }
506
507 fullname = pcmk__assert_asprintf("%s/%s", dir, name);
508 free(cib_home);
509 free(name);
510
511 return fullname;
512 }
513
514 cib_t *
515 cib_shadow_new(const char *shadow)
516 {
517 cib_t *new_cib = NULL;
518 char *shadow_file = NULL;
519
520 CRM_CHECK(shadow != NULL, return NULL);
521
522 shadow_file = get_shadow_file(shadow);
523 new_cib = cib_file_new(shadow_file);
524 free(shadow_file);
525
526 return new_cib;
527 }
528
529 /*!
530 * \brief Create a new CIB connection object
531 *
532 * Create a new live, remote, file, or shadow file CIB connection object based
533 * on the values of CIB-related environment variables (CIB_shadow, CIB_file,
534 * CIB_port, CIB_server, CIB_user, and CIB_passwd). The object will not be
535 * connected.
536 *
537 * \return Newly allocated CIB connection object
538 * \note The CIB API does not fully support opening multiple CIB connection
539 * objects simultaneously, so the returned object should be treated as a
540 * singleton.
541 */
542 /* @TODO Ensure all APIs support multiple simultaneous CIB connection objects
543 * (at least cib_free_callbacks() currently does not).
544 */
545 cib_t *
546 cib_new(void)
547 {
548 const char *value = getenv("CIB_shadow");
549 const char *server = NULL;
550 const char *user = NULL;
551 const char *pass = NULL;
552 gboolean encrypted = TRUE;
553 int port;
554
555 if (!pcmk__str_empty(value)) {
556 return cib_shadow_new(value);
557 }
558
559 value = getenv("CIB_file");
560 if (!pcmk__str_empty(value)) {
561 return cib_file_new(value);
562 }
563
564 value = getenv("CIB_port");
565 if (pcmk__str_empty(value)) {
566 return cib_native_new();
567 }
568
569 /* We don't ensure port is valid (>= 0) because cib_new() currently can't
570 * return NULL in practice, and introducing a NULL return here could cause
571 * core dumps that would previously just cause signon() failures.
572 */
573 pcmk__scan_port(value, &port);
574
575 if (!pcmk__is_true(getenv("CIB_encrypted"))) {
576 encrypted = FALSE;
577 }
578
579 server = getenv("CIB_server");
580 user = getenv("CIB_user");
581 pass = getenv("CIB_passwd");
582
583 if (pcmk__str_empty(user)) {
584 user = CRM_DAEMON_USER;
585 }
586
587 if (pcmk__str_empty(server)) {
588 server = "localhost";
589 }
590
591 pcmk__debug("Initializing %s remote CIB access to %s:%d as user %s",
592 (encrypted? "encrypted" : "plain-text"), server, port, user);
593 return cib_remote_new(server, user, pass, port, encrypted);
594 }
595
596 /*!
597 * \internal
598 * \brief Create a generic CIB connection instance
599 *
600 * \return Newly allocated and initialized cib_t instance
601 *
602 * \note This is called by each variant's cib_*_new() function before setting
603 * variant-specific values.
604 */
605 cib_t *
606 cib_new_variant(void)
607 {
608 cib_t *new_cib = NULL;
609
610 new_cib = calloc(1, sizeof(cib_t));
611
|
(1) Event path: |
Condition "new_cib == NULL", taking false branch. |
612 if (new_cib == NULL) {
613 return NULL;
614 }
615
616 remove_cib_op_callback(0, TRUE); /* remove all */
617
618 new_cib->call_id = 1;
619 new_cib->variant = cib_undefined;
620
621 new_cib->type = cib_no_connection;
622 new_cib->state = cib_disconnected;
623 new_cib->variant_opaque = NULL;
624 new_cib->notify_list = NULL;
625
626 /* 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] |
627 new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
628
|
(4) Event path: |
Condition "new_cib->cmds == NULL", taking false branch. |
629 if (new_cib->cmds == NULL) {
630 free(new_cib);
631 return NULL;
632 }
633
634 new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
635 new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
636 new_cib->cmds->register_callback = cib_client_register_callback;
637 new_cib->cmds->register_callback_full = cib_client_register_callback_full;
638
639 new_cib->cmds->noop = cib_client_noop; // Deprecated method
640 new_cib->cmds->ping = cib_client_ping;
641 new_cib->cmds->query = cib_client_query;
642 new_cib->cmds->sync = cib_client_sync;
643
644 new_cib->cmds->query_from = cib_client_query_from;
645 new_cib->cmds->sync_from = cib_client_sync_from;
646
647 new_cib->cmds->set_primary = set_primary;
648 new_cib->cmds->set_secondary = set_secondary;
649
650 new_cib->cmds->upgrade = cib_client_upgrade;
651 new_cib->cmds->bump_epoch = cib_client_bump_epoch;
652
653 new_cib->cmds->create = cib_client_create;
654 new_cib->cmds->modify = cib_client_modify;
655 new_cib->cmds->replace = cib_client_replace;
656 new_cib->cmds->remove = cib_client_delete;
657 new_cib->cmds->erase = cib_client_erase;
658
659 new_cib->cmds->init_transaction = cib_client_init_transaction;
660 new_cib->cmds->end_transaction = cib_client_end_transaction;
661
662 new_cib->cmds->set_user = cib_client_set_user;
663
664 new_cib->cmds->fetch_schemas = cib_client_fetch_schemas;
665
|
(5) Event return_alloc: |
Returning "new_cib", where "new_cib->cmds" is allocated memory. |
| Also see events: |
[alloc_fn][assign] |
666 return new_cib;
667 }
668
669 void
670 cib_free_notify(cib_t *cib)
671 {
672
673 if (cib) {
674 GList *list = cib->notify_list;
675
676 while (list != NULL) {
677 cib_notify_client_t *client = g_list_nth_data(list, 0);
678
679 list = g_list_remove(list, client);
680 free(client);
681 }
682 cib->notify_list = NULL;
683 }
684 }
685
686 /*!
687 * \brief Free all callbacks for a CIB connection
688 *
689 * \param[in,out] cib CIB connection to clean up
690 */
691 void
692 cib_free_callbacks(cib_t *cib)
693 {
694 cib_free_notify(cib);
695
696 destroy_op_callback_table();
697 }
698
699 /*!
700 * \brief Free all memory used by CIB connection
701 *
702 * \param[in,out] cib CIB connection to delete
703 */
704 void
705 cib_delete(cib_t *cib)
706 {
707 cib_free_callbacks(cib);
708 if (cib) {
709 cib->cmds->free(cib);
710 }
711 }
712
713 void
714 remove_cib_op_callback(int call_id, gboolean all_callbacks)
715 {
716 if (all_callbacks) {
717 destroy_op_callback_table();
718 cib_op_callback_table = pcmk__intkey_table(cib_destroy_op_callback);
719 } else {
720 pcmk__intkey_table_remove(cib_op_callback_table, call_id);
721 }
722 }
723
724 int
725 num_cib_op_callbacks(void)
726 {
727 if (cib_op_callback_table == NULL) {
728 return 0;
729 }
730 return g_hash_table_size(cib_op_callback_table);
731 }
732
733 static void
734 cib_dump_pending_op(void *key, void *value, void *user_data)
735 {
736 int call = GPOINTER_TO_INT(key);
737 cib_callback_client_t *blob = value;
738
739 pcmk__debug("Call %d (%s): pending", call, pcmk__s(blob->id, "without ID"));
740 }
741
742 void
743 cib_dump_pending_callbacks(void)
744 {
745 if (cib_op_callback_table == NULL) {
746 return;
747 }
748 return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
749 }
750
751 cib_callback_client_t*
752 cib__lookup_id (int call_id)
753 {
754 return pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
755 }
756
757 // Deprecated functions kept only for backward API compatibility
758 // LCOV_EXCL_START
759
760 #include <crm/cib_compat.h>
761
762 cib_t *
763 cib_new_no_shadow(void)
764 {
765 const char *shadow = getenv("CIB_shadow");
766 cib_t *cib = NULL;
767
768 unsetenv("CIB_shadow");
769 cib = cib_new();
770
771 if (shadow != NULL) {
772 setenv("CIB_shadow", shadow, 1);
773 }
774 return cib;
775 }
776
777 // LCOV_EXCL_STOP
778 // End deprecated API
779