root/lib/cib/cib_remote.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. cib_remote_perform_op
  2. cib_remote_callback_dispatch
  3. cib_remote_command_dispatch
  4. cib_tls_close
  5. cib_remote_connection_destroy
  6. cib_tls_signon
  7. cib_remote_signon
  8. cib_remote_signoff
  9. cib_remote_free
  10. cib_remote_inputfd
  11. cib_remote_register_notification
  12. cib_remote_set_connection_dnotify
  13. cib_remote_client_id
  14. cib_remote_new
  15. cib__set_output

   1 /*
   2  * Copyright 2008-2024 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 <unistd.h>
  13 #include <stdlib.h>
  14 #include <stdio.h>
  15 #include <stdarg.h>
  16 #include <string.h>
  17 #include <netdb.h>
  18 #include <termios.h>
  19 #include <sys/socket.h>
  20 
  21 #include <glib.h>
  22 
  23 #include <crm/crm.h>
  24 #include <crm/cib/internal.h>
  25 #include <crm/common/ipc_internal.h>
  26 #include <crm/common/mainloop.h>
  27 #include <crm/common/xml.h>
  28 #include <crm/common/remote_internal.h>
  29 #include <crm/common/output_internal.h>
  30 
  31 #ifdef HAVE_GNUTLS_GNUTLS_H
  32 
  33 #  include <gnutls/gnutls.h>
  34 
  35 // GnuTLS handshake timeout in seconds
  36 #define TLS_HANDSHAKE_TIMEOUT 5
  37 
  38 static gnutls_anon_client_credentials_t anon_cred_c;
  39 static gboolean remote_gnutls_credentials_init = FALSE;
  40 
  41 #endif // HAVE_GNUTLS_GNUTLS_H
  42 
  43 #include <arpa/inet.h>
  44 
  45 typedef struct cib_remote_opaque_s {
  46     int port;
  47     char *server;
  48     char *user;
  49     char *passwd;
  50     gboolean encrypted;
  51     pcmk__remote_t command;
  52     pcmk__remote_t callback;
  53     pcmk__output_t *out;
  54 } cib_remote_opaque_t;
  55 
  56 static int
  57 cib_remote_perform_op(cib_t *cib, const char *op, const char *host,
     /* [previous][next][first][last][top][bottom][index][help] */
  58                       const char *section, xmlNode *data,
  59                       xmlNode **output_data, int call_options,
  60                       const char *user_name)
  61 {
  62     int rc;
  63     int remaining_time = 0;
  64     time_t start_time;
  65 
  66     xmlNode *op_msg = NULL;
  67     xmlNode *op_reply = NULL;
  68 
  69     cib_remote_opaque_t *private = cib->variant_opaque;
  70 
  71     if (cib->state == cib_disconnected) {
  72         return -ENOTCONN;
  73     }
  74 
  75     if (output_data != NULL) {
  76         *output_data = NULL;
  77     }
  78 
  79     if (op == NULL) {
  80         crm_err("No operation specified");
  81         return -EINVAL;
  82     }
  83 
  84     rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
  85                         NULL, &op_msg);
  86     if (rc != pcmk_ok) {
  87         return rc;
  88     }
  89 
  90     if (pcmk_is_set(call_options, cib_transaction)) {
  91         rc = cib__extend_transaction(cib, op_msg);
  92         free_xml(op_msg);
  93         return rc;
  94     }
  95 
  96     crm_trace("Sending %s message to the CIB manager", op);
  97     if (!(call_options & cib_sync_call)) {
  98         pcmk__remote_send_xml(&private->callback, op_msg);
  99     } else {
 100         pcmk__remote_send_xml(&private->command, op_msg);
 101     }
 102     free_xml(op_msg);
 103 
 104     if ((call_options & cib_discard_reply)) {
 105         crm_trace("Discarding reply");
 106         return pcmk_ok;
 107 
 108     } else if (!(call_options & cib_sync_call)) {
 109         return cib->call_id;
 110     }
 111 
 112     crm_trace("Waiting for a synchronous reply");
 113 
 114     start_time = time(NULL);
 115     remaining_time = cib->call_timeout ? cib->call_timeout : 60;
 116 
 117     rc = pcmk_rc_ok;
 118     while (remaining_time > 0 && (rc != ENOTCONN)) {
 119         int reply_id = -1;
 120         int msg_id = cib->call_id;
 121 
 122         rc = pcmk__read_remote_message(&private->command,
 123                                        remaining_time * 1000);
 124         op_reply = pcmk__remote_message_xml(&private->command);
 125 
 126         if (!op_reply) {
 127             break;
 128         }
 129 
 130         crm_element_value_int(op_reply, PCMK__XA_CIB_CALLID, &reply_id);
 131 
 132         if (reply_id == msg_id) {
 133             break;
 134 
 135         } else if (reply_id < msg_id) {
 136             crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
 137             crm_log_xml_trace(op_reply, "Old reply");
 138 
 139         } else if ((reply_id - 10000) > msg_id) {
 140             /* wrap-around case */
 141             crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
 142             crm_log_xml_trace(op_reply, "Old reply");
 143         } else {
 144             crm_err("Received a __future__ reply:" " %d (wanted %d)", reply_id, msg_id);
 145         }
 146 
 147         free_xml(op_reply);
 148         op_reply = NULL;
 149 
 150         /* wasn't the right reply, try and read some more */
 151         remaining_time = time(NULL) - start_time;
 152     }
 153 
 154     /* if(IPC_ISRCONN(native->command_channel) == FALSE) { */
 155     /*      crm_err("The CIB manager disconnected: %d",  */
 156     /*              native->command_channel->ch_status); */
 157     /*      cib->state = cib_disconnected; */
 158     /* } */
 159 
 160     if (rc == ENOTCONN) {
 161         crm_err("Disconnected while waiting for reply.");
 162         return -ENOTCONN;
 163     } else if (op_reply == NULL) {
 164         crm_err("No reply message - empty");
 165         return -ENOMSG;
 166     }
 167 
 168     crm_trace("Synchronous reply received");
 169 
 170     /* Start processing the reply... */
 171     if (crm_element_value_int(op_reply, PCMK__XA_CIB_RC, &rc) != 0) {
 172         rc = -EPROTO;
 173     }
 174 
 175     if (rc == -pcmk_err_diff_resync) {
 176         /* This is an internal value that clients do not and should not care about */
 177         rc = pcmk_ok;
 178     }
 179 
 180     if (rc == pcmk_ok || rc == -EPERM) {
 181         crm_log_xml_debug(op_reply, "passed");
 182 
 183     } else {
 184 /*  } else if(rc == -ETIME) { */
 185         crm_err("Call failed: %s", pcmk_strerror(rc));
 186         crm_log_xml_warn(op_reply, "failed");
 187     }
 188 
 189     if (output_data == NULL) {
 190         /* do nothing more */
 191 
 192     } else if (!(call_options & cib_discard_reply)) {
 193         xmlNode *wrapper = pcmk__xe_first_child(op_reply, PCMK__XE_CIB_CALLDATA,
 194                                                 NULL, NULL);
 195         xmlNode *tmp = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 196 
 197         if (tmp == NULL) {
 198             crm_trace("No output in reply to \"%s\" command %d", op, cib->call_id - 1);
 199         } else {
 200             *output_data = pcmk__xml_copy(NULL, tmp);
 201         }
 202     }
 203 
 204     free_xml(op_reply);
 205 
 206     return rc;
 207 }
 208 
 209 static int
 210 cib_remote_callback_dispatch(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 211 {
 212     int rc;
 213     cib_t *cib = user_data;
 214     cib_remote_opaque_t *private = cib->variant_opaque;
 215 
 216     xmlNode *msg = NULL;
 217 
 218     crm_info("Message on callback channel");
 219 
 220     rc = pcmk__read_remote_message(&private->callback, -1);
 221 
 222     msg = pcmk__remote_message_xml(&private->callback);
 223     while (msg) {
 224         const char *type = crm_element_value(msg, PCMK__XA_T);
 225 
 226         crm_trace("Activating %s callbacks...", type);
 227 
 228         if (pcmk__str_eq(type, PCMK__VALUE_CIB, pcmk__str_none)) {
 229             cib_native_callback(cib, msg, 0, 0);
 230 
 231         } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) {
 232             g_list_foreach(cib->notify_list, cib_native_notify, msg);
 233 
 234         } else {
 235             crm_err("Unknown message type: %s", type);
 236         }
 237 
 238         free_xml(msg);
 239         msg = pcmk__remote_message_xml(&private->callback);
 240     }
 241 
 242     if (rc == ENOTCONN) {
 243         return -1;
 244     }
 245 
 246     return 0;
 247 }
 248 
 249 static int
 250 cib_remote_command_dispatch(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 251 {
 252     int rc;
 253     cib_t *cib = user_data;
 254     cib_remote_opaque_t *private = cib->variant_opaque;
 255 
 256     rc = pcmk__read_remote_message(&private->command, -1);
 257 
 258     free(private->command.buffer);
 259     private->command.buffer = NULL;
 260     crm_err("received late reply for remote cib connection, discarding");
 261 
 262     if (rc == ENOTCONN) {
 263         return -1;
 264     }
 265     return 0;
 266 }
 267 
 268 static int
 269 cib_tls_close(cib_t *cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 270 {
 271     cib_remote_opaque_t *private = cib->variant_opaque;
 272 
 273 #ifdef HAVE_GNUTLS_GNUTLS_H
 274     if (private->encrypted) {
 275         if (private->command.tls_session) {
 276             gnutls_bye(*(private->command.tls_session), GNUTLS_SHUT_RDWR);
 277             gnutls_deinit(*(private->command.tls_session));
 278             gnutls_free(private->command.tls_session);
 279         }
 280 
 281         if (private->callback.tls_session) {
 282             gnutls_bye(*(private->callback.tls_session), GNUTLS_SHUT_RDWR);
 283             gnutls_deinit(*(private->callback.tls_session));
 284             gnutls_free(private->callback.tls_session);
 285         }
 286         private->command.tls_session = NULL;
 287         private->callback.tls_session = NULL;
 288         if (remote_gnutls_credentials_init) {
 289             gnutls_anon_free_client_credentials(anon_cred_c);
 290             gnutls_global_deinit();
 291             remote_gnutls_credentials_init = FALSE;
 292         }
 293     }
 294 #endif
 295 
 296     if (private->command.tcp_socket) {
 297         shutdown(private->command.tcp_socket, SHUT_RDWR);       /* no more receptions */
 298         close(private->command.tcp_socket);
 299     }
 300     if (private->callback.tcp_socket) {
 301         shutdown(private->callback.tcp_socket, SHUT_RDWR);      /* no more receptions */
 302         close(private->callback.tcp_socket);
 303     }
 304     private->command.tcp_socket = 0;
 305     private->callback.tcp_socket = 0;
 306 
 307     free(private->command.buffer);
 308     free(private->callback.buffer);
 309     private->command.buffer = NULL;
 310     private->callback.buffer = NULL;
 311 
 312     return 0;
 313 }
 314 
 315 static void
 316 cib_remote_connection_destroy(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 317 {
 318     crm_err("Connection destroyed");
 319 #ifdef HAVE_GNUTLS_GNUTLS_H
 320     cib_tls_close(user_data);
 321 #endif
 322 }
 323 
 324 static int
 325 cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel)
     /* [previous][next][first][last][top][bottom][index][help] */
 326 {
 327     cib_remote_opaque_t *private = cib->variant_opaque;
 328     int rc;
 329 
 330     xmlNode *answer = NULL;
 331     xmlNode *login = NULL;
 332 
 333     static struct mainloop_fd_callbacks cib_fd_callbacks = { 0, };
 334 
 335     cib_fd_callbacks.dispatch =
 336         event_channel ? cib_remote_callback_dispatch : cib_remote_command_dispatch;
 337     cib_fd_callbacks.destroy = cib_remote_connection_destroy;
 338 
 339     connection->tcp_socket = -1;
 340 #ifdef HAVE_GNUTLS_GNUTLS_H
 341     connection->tls_session = NULL;
 342 #endif
 343     rc = pcmk__connect_remote(private->server, private->port, 0, NULL,
 344                               &(connection->tcp_socket), NULL, NULL);
 345     if (rc != pcmk_rc_ok) {
 346         crm_info("Remote connection to %s:%d failed: %s " CRM_XS " rc=%d",
 347                  private->server, private->port, pcmk_rc_str(rc), rc);
 348         return -ENOTCONN;
 349     }
 350 
 351     if (private->encrypted) {
 352         int tls_rc = GNUTLS_E_SUCCESS;
 353 
 354         /* initialize GnuTls lib */
 355 #ifdef HAVE_GNUTLS_GNUTLS_H
 356         if (remote_gnutls_credentials_init == FALSE) {
 357             crm_gnutls_global_init();
 358             gnutls_anon_allocate_client_credentials(&anon_cred_c);
 359             remote_gnutls_credentials_init = TRUE;
 360         }
 361 
 362         /* bind the socket to GnuTls lib */
 363         connection->tls_session = pcmk__new_tls_session(connection->tcp_socket,
 364                                                         GNUTLS_CLIENT,
 365                                                         GNUTLS_CRD_ANON,
 366                                                         anon_cred_c);
 367         if (connection->tls_session == NULL) {
 368             cib_tls_close(cib);
 369             return -1;
 370         }
 371 
 372         rc = pcmk__tls_client_handshake(connection, TLS_HANDSHAKE_TIMEOUT,
 373                                         &tls_rc);
 374         if (rc != pcmk_rc_ok) {
 375             crm_err("Remote CIB session creation for %s:%d failed: %s",
 376                     private->server, private->port,
 377                     (rc == EPROTO)? gnutls_strerror(tls_rc) : pcmk_rc_str(rc));
 378             gnutls_deinit(*connection->tls_session);
 379             gnutls_free(connection->tls_session);
 380             connection->tls_session = NULL;
 381             cib_tls_close(cib);
 382             return -1;
 383         }
 384 #else
 385         return -EPROTONOSUPPORT;
 386 #endif
 387     }
 388 
 389     /* login to server */
 390     login = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
 391     crm_xml_add(login, PCMK_XA_OP, "authenticate");
 392     crm_xml_add(login, PCMK_XA_USER, private->user);
 393     crm_xml_add(login, PCMK__XA_PASSWORD, private->passwd);
 394     crm_xml_add(login, PCMK__XA_HIDDEN, PCMK__VALUE_PASSWORD);
 395 
 396     pcmk__remote_send_xml(connection, login);
 397     free_xml(login);
 398 
 399     rc = pcmk_ok;
 400     if (pcmk__read_remote_message(connection, -1) == ENOTCONN) {
 401         rc = -ENOTCONN;
 402     }
 403 
 404     answer = pcmk__remote_message_xml(connection);
 405 
 406     crm_log_xml_trace(answer, "Reply");
 407     if (answer == NULL) {
 408         rc = -EPROTO;
 409 
 410     } else {
 411         /* grab the token */
 412         const char *msg_type = crm_element_value(answer, PCMK__XA_CIB_OP);
 413         const char *tmp_ticket = crm_element_value(answer,
 414                                                    PCMK__XA_CIB_CLIENTID);
 415 
 416         if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
 417             crm_err("Invalid registration message: %s", msg_type);
 418             rc = -EPROTO;
 419 
 420         } else if (tmp_ticket == NULL) {
 421             rc = -EPROTO;
 422 
 423         } else {
 424             connection->token = strdup(tmp_ticket);
 425         }
 426     }
 427     free_xml(answer);
 428     answer = NULL;
 429 
 430     if (rc != 0) {
 431         cib_tls_close(cib);
 432         return rc;
 433     }
 434 
 435     crm_trace("remote client connection established");
 436     connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH,
 437                                          connection->tcp_socket, cib,
 438                                          &cib_fd_callbacks);
 439     return rc;
 440 }
 441 
 442 static int
 443 cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type)
     /* [previous][next][first][last][top][bottom][index][help] */
 444 {
 445     int rc = pcmk_ok;
 446     cib_remote_opaque_t *private = cib->variant_opaque;
 447     xmlNode *hello = NULL;
 448 
 449     if (private->passwd == NULL) {
 450         if (private->out == NULL) {
 451             /* If no pcmk__output_t is set, just assume that a text prompt
 452              * is good enough.
 453              */
 454             pcmk__text_prompt("Password", false, &(private->passwd));
 455         } else {
 456             private->out->prompt("Password", false, &(private->passwd));
 457         }
 458     }
 459 
 460     if (private->server == NULL || private->user == NULL) {
 461         rc = -EINVAL;
 462     }
 463 
 464     if (rc == pcmk_ok) {
 465         rc = cib_tls_signon(cib, &(private->command), FALSE);
 466     }
 467 
 468     if (rc == pcmk_ok) {
 469         rc = cib_tls_signon(cib, &(private->callback), TRUE);
 470     }
 471 
 472     if (rc == pcmk_ok) {
 473         rc = cib__create_op(cib, CRM_OP_REGISTER, NULL, NULL, NULL, cib_none,
 474                             NULL, name, &hello);
 475     }
 476 
 477     if (rc == pcmk_ok) {
 478         rc = pcmk__remote_send_xml(&private->command, hello);
 479         rc = pcmk_rc2legacy(rc);
 480         free_xml(hello);
 481     }
 482 
 483     if (rc == pcmk_ok) {
 484         crm_info("Opened connection to %s:%d for %s",
 485                  private->server, private->port, name);
 486         cib->state = cib_connected_command;
 487         cib->type = cib_command;
 488 
 489     } else {
 490         crm_info("Connection to %s:%d for %s failed: %s\n",
 491                  private->server, private->port, name, pcmk_strerror(rc));
 492     }
 493 
 494     return rc;
 495 }
 496 
 497 static int
 498 cib_remote_signoff(cib_t *cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 499 {
 500     int rc = pcmk_ok;
 501 
 502     crm_debug("Disconnecting from the CIB manager");
 503 #ifdef HAVE_GNUTLS_GNUTLS_H
 504     cib_tls_close(cib);
 505 #endif
 506 
 507     cib->cmds->end_transaction(cib, false, cib_none);
 508     cib->state = cib_disconnected;
 509     cib->type = cib_no_connection;
 510 
 511     return rc;
 512 }
 513 
 514 static int
 515 cib_remote_free(cib_t *cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 516 {
 517     int rc = pcmk_ok;
 518 
 519     crm_warn("Freeing CIB");
 520     if (cib->state != cib_disconnected) {
 521         rc = cib_remote_signoff(cib);
 522         if (rc == pcmk_ok) {
 523             cib_remote_opaque_t *private = cib->variant_opaque;
 524 
 525             free(private->server);
 526             free(private->user);
 527             free(private->passwd);
 528             free(cib->cmds);
 529             free(cib->user);
 530             free(private);
 531             free(cib);
 532         }
 533     }
 534 
 535     return rc;
 536 }
 537 
 538 static int
 539 cib_remote_inputfd(cib_t * cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 540 {
 541     cib_remote_opaque_t *private = cib->variant_opaque;
 542 
 543     return private->callback.tcp_socket;
 544 }
 545 
 546 static int
 547 cib_remote_register_notification(cib_t * cib, const char *callback, int enabled)
     /* [previous][next][first][last][top][bottom][index][help] */
 548 {
 549     xmlNode *notify_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
 550     cib_remote_opaque_t *private = cib->variant_opaque;
 551 
 552     crm_xml_add(notify_msg, PCMK__XA_CIB_OP, PCMK__VALUE_CIB_NOTIFY);
 553     crm_xml_add(notify_msg, PCMK__XA_CIB_NOTIFY_TYPE, callback);
 554     crm_xml_add_int(notify_msg, PCMK__XA_CIB_NOTIFY_ACTIVATE, enabled);
 555     pcmk__remote_send_xml(&private->callback, notify_msg);
 556     free_xml(notify_msg);
 557     return pcmk_ok;
 558 }
 559 
 560 static int
 561 cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
     /* [previous][next][first][last][top][bottom][index][help] */
 562 {
 563     return -EPROTONOSUPPORT;
 564 }
 565 
 566 /*!
 567  * \internal
 568  * \brief Get the given CIB connection's unique client identifiers
 569  *
 570  * These can be used to check whether this client requested the action that
 571  * triggered a CIB notification.
 572  *
 573  * \param[in]  cib       CIB connection
 574  * \param[out] async_id  If not \p NULL, where to store asynchronous client ID
 575  * \param[out] sync_id   If not \p NULL, where to store synchronous client ID
 576  *
 577  * \return Legacy Pacemaker return code (specifically, \p pcmk_ok)
 578  *
 579  * \note This is the \p cib_remote variant implementation of
 580  *       \p cib_api_operations_t:client_id().
 581  * \note The client IDs are assigned during CIB sign-on.
 582  */
 583 static int
 584 cib_remote_client_id(const cib_t *cib, const char **async_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 585                      const char **sync_id)
 586 {
 587     cib_remote_opaque_t *private = cib->variant_opaque;
 588 
 589     if (async_id != NULL) {
 590         // private->callback is the channel for async requests
 591         *async_id = private->callback.token;
 592     }
 593     if (sync_id != NULL) {
 594         // private->command is the channel for sync requests
 595         *sync_id = private->command.token;
 596     }
 597     return pcmk_ok;
 598 }
 599 
 600 cib_t *
 601 cib_remote_new(const char *server, const char *user, const char *passwd, int port,
     /* [previous][next][first][last][top][bottom][index][help] */
 602                gboolean encrypted)
 603 {
 604     cib_remote_opaque_t *private = NULL;
 605     cib_t *cib = cib_new_variant();
 606 
 607     if (cib == NULL) {
 608         return NULL;
 609     }
 610 
 611     private = calloc(1, sizeof(cib_remote_opaque_t));
 612 
 613     if (private == NULL) {
 614         free(cib);
 615         return NULL;
 616     }
 617 
 618     cib->variant = cib_remote;
 619     cib->variant_opaque = private;
 620 
 621     private->server = pcmk__str_copy(server);
 622     private->user = pcmk__str_copy(user);
 623     private->passwd = pcmk__str_copy(passwd);
 624     private->port = port;
 625     private->encrypted = encrypted;
 626 
 627     /* assign variant specific ops */
 628     cib->delegate_fn = cib_remote_perform_op;
 629     cib->cmds->signon = cib_remote_signon;
 630     cib->cmds->signoff = cib_remote_signoff;
 631     cib->cmds->free = cib_remote_free;
 632     cib->cmds->inputfd = cib_remote_inputfd; // Deprecated method
 633 
 634     cib->cmds->register_notification = cib_remote_register_notification;
 635     cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify;
 636 
 637     cib->cmds->client_id = cib_remote_client_id;
 638 
 639     return cib;
 640 }
 641 
 642 void
 643 cib__set_output(cib_t *cib, pcmk__output_t *out)
     /* [previous][next][first][last][top][bottom][index][help] */
 644 {
 645     cib_remote_opaque_t *private;
 646 
 647     if (cib->variant != cib_remote) {
 648         return;
 649     }
 650 
 651     private = cib->variant_opaque;
 652     private->out = out;
 653 }

/* [previous][next][first][last][top][bottom][index][help] */