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 pcmk__xml_free(cib->transaction);
393 cib->transaction = NULL;
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 if (cib_op_callback_table != NULL) {
439 g_hash_table_destroy(cib_op_callback_table);
440 cib_op_callback_table = NULL;
441 }
442 }
443
444 char *
445 get_shadow_file(const char *suffix)
446 {
447 char *cib_home = NULL;
448 char *fullname = NULL;
449 char *name = pcmk__assert_asprintf("shadow.%s", suffix);
450 const char *dir = getenv("CIB_shadow_dir");
451
452 if (dir == NULL) {
453 /* @TODO This basically duplicates pcmk__uid2username(), but we need the
454 * password database entry, not just the user name from it. We should
455 * reduce the duplication.
456 */
457 struct passwd *pwent = NULL;
458 const char *user = NULL;
459
460 errno = 0;
461 pwent = getpwuid(geteuid());
462
463 if (pwent) {
464 user = pwent->pw_name;
465
466 } else {
467 // Save errno before getenv()
468 int rc = errno;
469
470 user = getenv("USER");
471 pcmk__warn("Could not get password database entry for effective "
472 "user ID %lld: %s. Assuming user is %s.",
473 (long long) geteuid(),
474 ((rc != 0)? strerror(rc) : "No matching entry found"),
475 pcmk__s(user, "unprivileged user"));
476 }
477
478 if (pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
479 dir = CRM_CONFIG_DIR;
480
481 } else {
482 const char *home = NULL;
483
484 if ((home = getenv("HOME")) == NULL) {
485 if (pwent) {
486 home = pwent->pw_dir;
487 }
488 }
489
490 dir = pcmk__get_tmpdir();
491 if (home && home[0] == '/') {
492 int rc = 0;
493
494 cib_home = pcmk__assert_asprintf("%s/.cib", home);
495
496 rc = mkdir(cib_home, 0700);
497 if (rc < 0 && errno != EEXIST) {
498 pcmk__err("Couldn't create user-specific shadow directory "
499 "%s: %s",
500 cib_home, strerror(errno));
501
502 } else {
503 dir = cib_home;
504 }
505 }
506 }
507 }
508
509 fullname = pcmk__assert_asprintf("%s/%s", dir, name);
510 free(cib_home);
511 free(name);
512
513 return fullname;
514 }
515
516 cib_t *
517 cib_shadow_new(const char *shadow)
518 {
519 cib_t *new_cib = NULL;
520 char *shadow_file = NULL;
521
522 CRM_CHECK(shadow != NULL, return NULL);
523
524 shadow_file = get_shadow_file(shadow);
525 new_cib = cib_file_new(shadow_file);
526 free(shadow_file);
527
528 return new_cib;
529 }
530
531 /*!
532 * \brief Create a new CIB connection object
533 *
534 * Create a new live, remote, file, or shadow file CIB connection object based
535 * on the values of CIB-related environment variables (CIB_shadow, CIB_file,
536 * CIB_port, CIB_server, CIB_user, and CIB_passwd). The object will not be
537 * connected.
538 *
539 * \return Newly allocated CIB connection object
540 * \note The CIB API does not fully support opening multiple CIB connection
541 * objects simultaneously, so the returned object should be treated as a
542 * singleton.
543 */
544 /* @TODO Ensure all APIs support multiple simultaneous CIB connection objects
545 * (at least cib_free_callbacks() currently does not).
546 */
547 cib_t *
548 cib_new(void)
549 {
550 const char *value = getenv("CIB_shadow");
551 const char *server = NULL;
552 const char *user = NULL;
553 const char *pass = NULL;
554 gboolean encrypted = TRUE;
555 int port;
556
557 if (!pcmk__str_empty(value)) {
558 return cib_shadow_new(value);
559 }
560
561 value = getenv("CIB_file");
562 if (!pcmk__str_empty(value)) {
563 return cib_file_new(value);
564 }
565
566 value = getenv("CIB_port");
567 if (pcmk__str_empty(value)) {
568 return cib_native_new();
569 }
570
571 /* We don't ensure port is valid (>= 0) because cib_new() currently can't
572 * return NULL in practice, and introducing a NULL return here could cause
573 * core dumps that would previously just cause signon() failures.
574 */
575 pcmk__scan_port(value, &port);
576
577 if (!pcmk__is_true(getenv("CIB_encrypted"))) {
578 encrypted = FALSE;
579 }
580
581 server = getenv("CIB_server");
582 user = getenv("CIB_user");
583 pass = getenv("CIB_passwd");
584
585 if (pcmk__str_empty(user)) {
586 user = CRM_DAEMON_USER;
587 }
588
589 if (pcmk__str_empty(server)) {
590 server = "localhost";
591 }
592
593 pcmk__debug("Initializing %s remote CIB access to %s:%d as user %s",
594 (encrypted? "encrypted" : "plain-text"), server, port, user);
595 return cib_remote_new(server, user, pass, port, encrypted);
596 }
597
598 /*!
599 * \internal
600 * \brief Create a generic CIB connection instance
601 *
602 * \return Newly allocated and initialized cib_t instance
603 *
604 * \note This is called by each variant's cib_*_new() function before setting
605 * variant-specific values.
606 */
607 cib_t *
608 cib_new_variant(void)
609 {
610 cib_t *new_cib = NULL;
611
612 new_cib = calloc(1, sizeof(cib_t));
613
|
(1) Event path: |
Condition "new_cib == NULL", taking false branch. |
614 if (new_cib == NULL) {
615 return NULL;
616 }
617
618 remove_cib_op_callback(0, TRUE); /* remove all */
619
620 new_cib->call_id = 1;
621 new_cib->variant = cib_undefined;
622
623 new_cib->type = cib_no_connection;
624 new_cib->state = cib_disconnected;
625 new_cib->variant_opaque = NULL;
626 new_cib->notify_list = NULL;
627
628 /* 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] |
629 new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
630
|
(4) Event path: |
Condition "new_cib->cmds == NULL", taking false branch. |
631 if (new_cib->cmds == NULL) {
632 free(new_cib);
633 return NULL;
634 }
635
636 new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
637 new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
638 new_cib->cmds->register_callback = cib_client_register_callback;
639 new_cib->cmds->register_callback_full = cib_client_register_callback_full;
640
641 new_cib->cmds->noop = cib_client_noop; // Deprecated method
642 new_cib->cmds->ping = cib_client_ping;
643 new_cib->cmds->query = cib_client_query;
644 new_cib->cmds->sync = cib_client_sync;
645
646 new_cib->cmds->query_from = cib_client_query_from;
647 new_cib->cmds->sync_from = cib_client_sync_from;
648
649 new_cib->cmds->set_primary = set_primary;
650 new_cib->cmds->set_secondary = set_secondary;
651
652 new_cib->cmds->upgrade = cib_client_upgrade;
653 new_cib->cmds->bump_epoch = cib_client_bump_epoch;
654
655 new_cib->cmds->create = cib_client_create;
656 new_cib->cmds->modify = cib_client_modify;
657 new_cib->cmds->replace = cib_client_replace;
658 new_cib->cmds->remove = cib_client_delete;
659 new_cib->cmds->erase = cib_client_erase;
660
661 new_cib->cmds->init_transaction = cib_client_init_transaction;
662 new_cib->cmds->end_transaction = cib_client_end_transaction;
663
664 new_cib->cmds->set_user = cib_client_set_user;
665
666 new_cib->cmds->fetch_schemas = cib_client_fetch_schemas;
667
|
(5) Event return_alloc: |
Returning "new_cib", where "new_cib->cmds" is allocated memory. |
| Also see events: |
[alloc_fn][assign] |
668 return new_cib;
669 }
670
671 void
672 cib_free_notify(cib_t *cib)
673 {
674
675 if (cib) {
676 GList *list = cib->notify_list;
677
678 while (list != NULL) {
679 cib_notify_client_t *client = g_list_nth_data(list, 0);
680
681 list = g_list_remove(list, client);
682 free(client);
683 }
684 cib->notify_list = NULL;
685 }
686 }
687
688 /*!
689 * \brief Free all callbacks for a CIB connection
690 *
691 * \param[in,out] cib CIB connection to clean up
692 */
693 void
694 cib_free_callbacks(cib_t *cib)
695 {
696 cib_free_notify(cib);
697
698 destroy_op_callback_table();
699 }
700
701 /*!
702 * \brief Free all memory used by CIB connection
703 *
704 * \param[in,out] cib CIB connection to delete
705 */
706 void
707 cib_delete(cib_t *cib)
708 {
709 cib_free_callbacks(cib);
710 if (cib) {
711 cib->cmds->free(cib);
712 }
713 }
714
715 void
716 remove_cib_op_callback(int call_id, gboolean all_callbacks)
717 {
718 if (all_callbacks) {
719 destroy_op_callback_table();
720 cib_op_callback_table = pcmk__intkey_table(cib_destroy_op_callback);
721 } else {
722 pcmk__intkey_table_remove(cib_op_callback_table, call_id);
723 }
724 }
725
726 int
727 num_cib_op_callbacks(void)
728 {
729 if (cib_op_callback_table == NULL) {
730 return 0;
731 }
732 return g_hash_table_size(cib_op_callback_table);
733 }
734
735 static void
736 cib_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
737 {
738 int call = GPOINTER_TO_INT(key);
739 cib_callback_client_t *blob = value;
740
741 pcmk__debug("Call %d (%s): pending", call, pcmk__s(blob->id, "without ID"));
742 }
743
744 void
745 cib_dump_pending_callbacks(void)
746 {
747 if (cib_op_callback_table == NULL) {
748 return;
749 }
750 return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
751 }
752
753 cib_callback_client_t*
754 cib__lookup_id (int call_id)
755 {
756 return pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
757 }
758
759 // Deprecated functions kept only for backward API compatibility
760 // LCOV_EXCL_START
761
762 #include <crm/cib_compat.h>
763
764 cib_t *
765 cib_new_no_shadow(void)
766 {
767 const char *shadow = getenv("CIB_shadow");
768 cib_t *cib = NULL;
769
770 unsetenv("CIB_shadow");
771 cib = cib_new();
772
773 if (shadow != NULL) {
774 setenv("CIB_shadow", shadow, 1);
775 }
776 return cib;
777 }
778
779 // LCOV_EXCL_STOP
780 // End deprecated API
781