/* * Copyright (c) 2015-2020 Red Hat, Inc. * * All rights reserved. * * Author: Jan Friesse (jfriesse@redhat.com) * * This software licensed under BSD license, the text of which follows: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the Red Hat, Inc. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "log.h" #include "qdevice-model.h" #include "qdevice-model-net.h" #include "qdevice-net-cast-vote-timer.h" #include "qdevice-net-instance.h" #include "qdevice-net-ipc-cmd.h" #include "qdevice-net-algorithm.h" #include "qdevice-net-heuristics.h" #include "qdevice-net-send.h" #include "qdevice-net-socket.h" #include "qdevice-net-votequorum.h" #include "qnet-config.h" #include "nss-sock.h" /* * Callbacks */ static int check_schedule_disconnect_cb(void *user_data1, void *user_data2) { struct qdevice_net_instance *instance = (struct qdevice_net_instance *)user_data1; if (instance->schedule_disconnect) { return (-1); } return (0); } static int qdevice_model_net_timer_connect_timeout(void *data1, void *data2) { struct qdevice_net_instance *instance; instance = (struct qdevice_net_instance *)data1; log(LOG_ERR, "Connect timeout"); instance->schedule_disconnect = 1; instance->connect_timer = NULL; instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER; return (0); } /* * Exported functions */ int qdevice_model_net_init(struct qdevice_instance *instance) { struct qdevice_net_instance *net_instance; log(LOG_DEBUG, "Initializing qdevice_net_instance"); if (qdevice_net_instance_init_from_cmap(instance) != 0) { return (-1); } net_instance = instance->model_data; log(LOG_DEBUG, "Registering algorithms"); if (qdevice_net_algorithm_register_all() != 0) { return (-1); } log(LOG_DEBUG, "Initializing NSS"); if (nss_sock_init_nss((net_instance->tls_supported != TLV_TLS_UNSUPPORTED ? instance->advanced_settings->net_nss_db_dir : NULL)) != 0) { log_nss(LOG_ERR, "Can't init nss"); return (-1); } if (qdevice_net_cast_vote_timer_update(net_instance, TLV_VOTE_ASK_LATER) != 0) { log(LOG_ERR, "Can't update cast vote timer"); return (-1); } if (qdevice_net_algorithm_init(net_instance) != 0) { log(LOG_ERR, "Algorithm init failed"); return (-1); } if (qdevice_net_heuristics_init(net_instance) != 0) { log(LOG_ERR, "Can't initialize net heuristics"); return (-1); } return (0); } int qdevice_model_net_destroy(struct qdevice_instance *instance) { struct qdevice_net_instance *net_instance; net_instance = instance->model_data; log(LOG_DEBUG, "Destroying algorithm"); qdevice_net_algorithm_destroy(net_instance); log(LOG_DEBUG, "Destroying qdevice_net_instance"); qdevice_net_instance_destroy(net_instance); log(LOG_DEBUG, "Shutting down NSS"); SSL_ClearSessionCache(); if (NSS_Shutdown() != SECSuccess) { log_nss(LOG_WARNING, "Can't shutdown NSS"); } if (PR_Cleanup() != PR_SUCCESS) { log_nss(LOG_WARNING, "Can't shutdown NSPR"); } free(net_instance); return (0); } static PRIntn qdevice_model_net_get_af(const struct qdevice_net_instance *instance) { PRIntn af; af = PR_AF_UNSPEC; if (instance->force_ip_version == 4) { af = PR_AF_INET; } if (instance->force_ip_version == 6) { af = PR_AF_INET6; } return (af); } /* * 0 - Continue * -1 - End loop */ int qdevice_model_net_pre_poll_loop(struct qdevice_instance *instance) { struct qdevice_net_instance *net_instance; int res; net_instance = instance->model_data; net_instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT; net_instance->socket = NULL; net_instance->connect_timer = timer_list_add(pr_poll_loop_get_timer_list(&instance->main_poll_loop), net_instance->connect_timeout, qdevice_model_net_timer_connect_timeout, (void *)net_instance, NULL); if (net_instance->connect_timer == NULL) { log(LOG_CRIT, "Can't schedule connect timer"); return (-1); } log(LOG_DEBUG, "Trying connect to qnetd server %s:%u (timeout = %ums)", net_instance->host_addr, net_instance->host_port, net_instance->connect_timeout); res = nss_sock_non_blocking_client_init(net_instance->host_addr, net_instance->host_port, qdevice_model_net_get_af(net_instance), &net_instance->non_blocking_client); if (res == -1) { log_nss(LOG_ERR, "Can't initialize non blocking client connection"); } res = nss_sock_non_blocking_client_try_next(&net_instance->non_blocking_client); if (res == -1) { log_nss(LOG_ERR, "Can't connect to qnetd host"); } res = qdevice_net_socket_add_to_main_poll_loop(net_instance); if (res == -1) { goto error_free_non_blocking_client; } res = pr_poll_loop_add_pre_poll_cb(&instance->main_poll_loop, check_schedule_disconnect_cb, net_instance, NULL); if (res == -1) { log(LOG_CRIT, "Can't add pre poll callback to main loop"); goto error_del_from_main_poll_loop; } return (0); error_del_from_main_poll_loop: (void)qdevice_net_socket_del_from_main_poll_loop(net_instance); error_free_non_blocking_client: nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client); return (-1); } /* * 1 - Restart loop * 0 - End loop with no error * -1 - End loop with error -1 */ int qdevice_model_net_post_poll_loop(struct qdevice_instance *instance, enum qdevice_model_post_poll_loop_exit_reason exit_reason) { struct qdevice_net_instance *net_instance; int restart_loop; int ret_val; enum tlv_vote vote; int delay_before_reconnect; net_instance = instance->model_data; restart_loop = 1; ret_val = -1; if (net_instance->connect_timer != NULL) { timer_list_entry_delete(pr_poll_loop_get_timer_list(&instance->main_poll_loop), net_instance->connect_timer); net_instance->connect_timer = NULL; } if (net_instance->echo_request_timer != NULL) { timer_list_entry_delete(pr_poll_loop_get_timer_list(&instance->main_poll_loop), net_instance->echo_request_timer); net_instance->echo_request_timer = NULL; } /* * Map qdevice exit_reason to qdevice-net disconnect reason */ switch (exit_reason) { case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_MODEL: /* * Disconnect reason should be already set */ break; case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_VOTEQUORUM_CLOSED: net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED; break; case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_CMAP_CLOSED: net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED; break; case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_HEURISTICS_CLOSED: net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED; break; case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_IPC_SOCKET_CLOSED: net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED; break; /* * Default not handled intentionally. Compiler shows warning when new exit reason is added */ } restart_loop = qdevice_net_disconnect_reason_try_reconnect(net_instance->disconnect_reason); /* * Unpause cast vote timer, because if it is paused we cannot remove tracking */ qdevice_net_cast_vote_timer_set_paused(net_instance, 0); vote = TLV_VOTE_NO_CHANGE; if (qdevice_net_algorithm_disconnected(net_instance, net_instance->disconnect_reason, &restart_loop, &vote) != 0) { log(LOG_ERR, "Algorithm returned error, force exit"); return (-1); } else { log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(vote)); } if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { log(LOG_ERR, "qdevice_model_net_run fatal error. " " Can't update cast vote timer vote"); } if (qdevice_net_disconnect_reason_force_disconnect(net_instance->disconnect_reason)) { restart_loop = 0; } /* * Return 0 only when local socket was closed -> regular exit */ if (net_instance->disconnect_reason == QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED) { ret_val = 0; } /* * Remove pre poll cb */ if (pr_poll_loop_del_pre_poll_cb(&instance->main_poll_loop, check_schedule_disconnect_cb) == -1) { log(LOG_ERR, "Can't delete pre poll callback from main loop"); restart_loop = 0; ret_val = -1; } /* * Remove socket from loop */ if (qdevice_net_socket_del_from_main_poll_loop(net_instance) == -1) { restart_loop = 0; ret_val = -1; } if (net_instance->socket != NULL) { if (PR_Close(net_instance->socket) != PR_SUCCESS) { log_nss(LOG_WARNING, "Unable to close connection"); } net_instance->socket = NULL; } if (!net_instance->non_blocking_client.destroyed) { nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client); } if (net_instance->non_blocking_client.socket != NULL) { if (PR_Close(net_instance->non_blocking_client.socket) != PR_SUCCESS) { log_nss(LOG_WARNING, "Unable to close non-blocking client connection"); } net_instance->non_blocking_client.socket = NULL; } if (restart_loop && net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) { /* * Give qnetd server a little time before reconnect */ delay_before_reconnect = random() % (int)(net_instance->cast_vote_timer_interval * 0.9); log(LOG_DEBUG, "Sleeping for %u ms before reconnect", delay_before_reconnect); (void)poll(NULL, 0, delay_before_reconnect); } qdevice_net_instance_clean(net_instance); if (restart_loop) { return (1); } return (ret_val); } /* * Called when cmap reload (or nodelist) was requested. * * nlist is node list * config_version is valid only if config_version_set != 0 * * Should return 0 if processing should continue or -1 to call exit */ int qdevice_model_net_config_node_list_changed(struct qdevice_instance *instance, const struct node_list *nlist, int config_version_set, uint64_t config_version) { struct qdevice_net_instance *net_instance; int send_node_list; enum tlv_vote vote; net_instance = instance->model_data; if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { /* * Nodelist changed, but connection to qnetd not initiated yet. */ send_node_list = 0; if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { vote = TLV_VOTE_NACK; } else { vote = TLV_VOTE_NO_CHANGE; } } else { send_node_list = 1; vote = TLV_VOTE_NO_CHANGE; } if (qdevice_net_algorithm_config_node_list_changed(net_instance, nlist, config_version_set, config_version, &send_node_list, &vote) != 0) { log(LOG_ERR, "Algorithm returned error, Disconnecting"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_CONFIG_NODE_LIST_CHANGED_ERR; net_instance->schedule_disconnect = 1; return (0); } else { log(LOG_DEBUG, "Algorithm decided to %s node list and result vote is %s", (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote)); } if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { log(LOG_CRIT, "qdevice_model_net_config_node_list_changed fatal error. " " Can't update cast vote timer vote"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; net_instance->schedule_disconnect = 1; return (0); } if (send_node_list) { if (qdevice_net_send_config_node_list(net_instance, nlist, config_version_set, config_version, 0) != 0) { net_instance->schedule_disconnect = 1; net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; return (0); } } return (0); } /* * Called when cmap reload (or nodelist) was requested, but it was not possible to * get node list. * * Should return 0 if processing should continue or -1 to call exit */ int qdevice_model_net_get_config_node_list_failed(struct qdevice_instance *instance) { struct qdevice_net_instance *net_instance; net_instance = instance->model_data; net_instance->schedule_disconnect = 1; net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; return (0); } int qdevice_model_net_votequorum_quorum_notify(struct qdevice_instance *instance, uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[]) { struct qdevice_net_instance *net_instance; int send_node_list; enum tlv_vote vote; net_instance = instance->model_data; if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { /* * Nodelist changed, but connection to qnetd not initiated yet. */ send_node_list = 0; if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { vote = TLV_VOTE_NACK; } else { vote = TLV_VOTE_NO_CHANGE; } } else { send_node_list = 1; vote = TLV_VOTE_NO_CHANGE; } if (qdevice_net_algorithm_votequorum_quorum_notify(net_instance, quorate, node_list_entries, node_list, &send_node_list, &vote) != 0) { log(LOG_ERR, "Algorithm returned error. Disconnecting."); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_QUORUM_NOTIFY_ERR; net_instance->schedule_disconnect = 1; return (0); } else { log(LOG_DEBUG, "Algorithm decided to %s list and result vote is %s", (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote)); } if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { log(LOG_CRIT, "qdevice_model_net_votequorum_quorum_notify fatal error. " " Can't update cast vote timer vote"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; net_instance->schedule_disconnect = 1; return (0); } if (send_node_list) { if (qdevice_net_send_quorum_node_list(net_instance, (quorate ? TLV_QUORATE_QUORATE : TLV_QUORATE_INQUORATE), node_list_entries, node_list) != 0) { /* * Fatal error -> schedule disconnect */ net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; net_instance->schedule_disconnect = 1; return (0); } } return (0); } int qdevice_model_net_votequorum_node_list_heuristics_notify(struct qdevice_instance *instance, votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[], enum qdevice_heuristics_exec_result heuristics_exec_result) { struct qdevice_net_instance *net_instance; struct tlv_ring_id tlv_rid; enum tlv_vote vote; enum tlv_heuristics heuristics; int send_node_list; net_instance = instance->model_data; qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id); heuristics = qdevice_net_heuristics_exec_result_to_tlv(heuristics_exec_result); if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { /* * Nodelist changed, but connection to qnetd not initiated yet. */ send_node_list = 0; if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { vote = TLV_VOTE_NACK; } else { vote = TLV_VOTE_NO_CHANGE; } } else { send_node_list = 1; vote = TLV_VOTE_WAIT_FOR_REPLY; } if (qdevice_net_algorithm_votequorum_node_list_heuristics_notify(net_instance, &tlv_rid, node_list_entries, node_list, &send_node_list, &vote, &heuristics) != 0) { log(LOG_ERR, "Algorithm returned error. Disconnecting."); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_HEURISTICS_NOTIFY_ERR; net_instance->schedule_disconnect = 1; return (0); } else { log(LOG_DEBUG, "Algorithm decided to %s list, result vote is %s and heuristics is %s", (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote), tlv_heuristics_to_str(heuristics)); } if (send_node_list) { if (qdevice_net_send_membership_node_list(net_instance, &tlv_rid, node_list_entries, node_list, heuristics) != 0) { /* * Fatal error -> schedule disconnect */ net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; net_instance->schedule_disconnect = 1; return (0); } } /* * Unpause cast vote timer */ qdevice_net_cast_vote_timer_set_paused(net_instance, 0); if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error " "Can't update cast vote timer"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; net_instance->schedule_disconnect = 1; return (0); } net_instance->latest_vq_heuristics_result = heuristics; net_instance->latest_heuristics_result = heuristics; if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) { return (0); } return (0); } int qdevice_model_net_votequorum_node_list_notify(struct qdevice_instance *instance, votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[]) { struct qdevice_net_instance *net_instance; struct tlv_ring_id tlv_rid; enum tlv_vote vote; int pause_cast_vote_timer; net_instance = instance->model_data; /* * Stop regular heuristics till qdevice_model_net_votequorum_node_list_heuristics_notify * is called */ if (qdevice_net_heuristics_stop_timer(net_instance) != 0) { return (0); } pause_cast_vote_timer = 1; vote = TLV_VOTE_NO_CHANGE; if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS && net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { /* * Nodelist changed and vote timer still votes ACK. It's needed to start voting * NACK. */ if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { vote = TLV_VOTE_NACK; } } qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id); if (qdevice_net_algorithm_votequorum_node_list_notify(net_instance, &tlv_rid, node_list_entries, node_list, &pause_cast_vote_timer, &vote) != 0) { log(LOG_ERR, "Algorithm returned error. Disconnecting."); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_NOTIFY_ERR; net_instance->schedule_disconnect = 1; return (0); } else { log(LOG_DEBUG, "Algorithm decided to %s cast vote timer and result vote is %s ", (pause_cast_vote_timer ? "pause" : "not pause"), tlv_vote_to_str(vote)); } if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error " "Can't update cast vote timer"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; net_instance->schedule_disconnect = 1; return (0); } qdevice_net_cast_vote_timer_set_paused(net_instance, pause_cast_vote_timer); return (0); } int qdevice_model_net_votequorum_expected_votes_notify(struct qdevice_instance *instance, uint32_t expected_votes) { struct qdevice_net_instance *net_instance; enum tlv_vote vote; net_instance = instance->model_data; log(LOG_DEBUG, "qdevice_model_net_votequorum_expected_votes_notify" " (expected votes old=%"PRIu32" / new=%"PRIu32")", net_instance->qdevice_instance_ptr->vq_expected_votes, expected_votes); vote = TLV_VOTE_NO_CHANGE; if (qdevice_net_algorithm_votequorum_expected_votes_notify(net_instance, expected_votes, &vote) != 0) { log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_EXPECTED_VOTES_NOTIFY_ERR; net_instance->schedule_disconnect = 1; return (0); } else { log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(vote)); } if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { log(LOG_CRIT, "qdevice_model_net_votequorum_expected_votes_notify fatal error. " " Can't update cast vote timer vote"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; net_instance->schedule_disconnect = 1; return (0); } return (0); } int qdevice_model_net_cmap_changed(struct qdevice_instance *instance, const struct qdevice_cmap_change_events *events) { struct qdevice_net_instance *net_instance; enum qdevice_heuristics_mode active_heuristics_mode; int heuristics_enabled; net_instance = instance->model_data; if (events->heuristics) { active_heuristics_mode = instance->heuristics_instance.mode; heuristics_enabled = (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED || active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC); if (net_instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS && !net_instance->server_supports_heuristics && heuristics_enabled) { log(LOG_ERR, "Heuristics are enabled but not supported by the server"); net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT; net_instance->schedule_disconnect = 1; return (0); } if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) { return (0); } } return (0); } int qdevice_model_net_ipc_cmd_status(struct qdevice_instance *instance, struct dynar *outbuf, int verbose) { struct qdevice_net_instance *net_instance; net_instance = instance->model_data; if (!qdevice_net_ipc_cmd_status(net_instance, outbuf, verbose)) { return (-1); } return (0); } static struct qdevice_model qdevice_model_net = { .name = "net", .init = qdevice_model_net_init, .destroy = qdevice_model_net_destroy, .pre_poll_loop = qdevice_model_net_pre_poll_loop, .post_poll_loop = qdevice_model_net_post_poll_loop, .get_config_node_list_failed = qdevice_model_net_get_config_node_list_failed, .config_node_list_changed = qdevice_model_net_config_node_list_changed, .votequorum_quorum_notify = qdevice_model_net_votequorum_quorum_notify, .votequorum_node_list_notify = qdevice_model_net_votequorum_node_list_notify, .votequorum_node_list_heuristics_notify = qdevice_model_net_votequorum_node_list_heuristics_notify, .votequorum_expected_votes_notify = qdevice_model_net_votequorum_expected_votes_notify, .cmap_changed = qdevice_model_net_cmap_changed, .ipc_cmd_status = qdevice_model_net_ipc_cmd_status, }; int qdevice_model_net_register(void) { return (qdevice_model_register(QDEVICE_MODEL_TYPE_NET, &qdevice_model_net)); }