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