diff --git a/include/crm/crm.h b/include/crm/crm.h index dc2adc12c9..ce2074b63b 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,231 +1,231 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM__H # define CRM__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief A dumping ground * \ingroup core */ # include # include # include # include # include # include /*! * The CRM feature set assists with compatibility in mixed-version clusters. * The major version number increases when nodes with different versions * would not work (rolling upgrades are not allowed). The minor version * number increases when mixed-version clusters are allowed only during * rolling upgrades (a node with the oldest feature set will be elected DC). The * minor-minor version number is ignored, but allows resource agents to detect * cluster support for various features. * * The feature set also affects the processing of old saved CIBs (such as for * many scheduler regression tests). * * Particular feature points currently used by pacemaker: * * >2.1: Operation updates include timing data * >=3.0.5: XML v2 digests are created * >=3.0.8: Peers do not need acks for cancellations * >=3.0.9: DC will send its own shutdown request to all peers * XML v2 patchsets are created by default * >=3.0.13: Fail counts include operation name and interval * >=3.2.0: DC supports PCMK_LRM_OP_INVALID and PCMK_LRM_OP_NOT_CONNECTED */ -# define CRM_FEATURE_SET "3.4.0" +# define CRM_FEATURE_SET "3.4.1" # define EOS '\0' # define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) # ifndef MAX_NAME # define MAX_NAME 256 # endif # ifndef __GNUC__ # define __builtin_expect(expr, result) (expr) # endif /* Some handy macros used by the Linux kernel */ # define __likely(expr) __builtin_expect(expr, 1) # define __unlikely(expr) __builtin_expect(expr, 0) # define CRM_META "CRM_meta" extern char *crm_system_name; /* *INDENT-OFF* */ // Used for some internal IPC timeouts (maybe should be configurable option) # define MAX_IPC_DELAY 120 // How we represent "infinite" scores # define CRM_SCORE_INFINITY 1000000 # define CRM_INFINITY_S "INFINITY" # define CRM_PLUS_INFINITY_S "+" CRM_INFINITY_S # define CRM_MINUS_INFINITY_S "-" CRM_INFINITY_S /* @COMPAT API < 2.0.0 Deprecated "infinity" aliases * * INFINITY might be defined elsewhere (e.g. math.h), so undefine it first. * This, of course, complicates any attempt to use the other definition in any * code that includes this header. */ # undef INFINITY # define INFINITY_S "INFINITY" # define MINUS_INFINITY_S "-INFINITY" # define INFINITY 1000000 /* Sub-systems */ # define CRM_SYSTEM_DC "dc" # define CRM_SYSTEM_DCIB "dcib" /* The master CIB */ # define CRM_SYSTEM_CIB "cib" # define CRM_SYSTEM_CRMD "crmd" # define CRM_SYSTEM_LRMD "lrmd" # define CRM_SYSTEM_PENGINE "pengine" # define CRM_SYSTEM_TENGINE "tengine" # define CRM_SYSTEM_STONITHD "stonithd" # define CRM_SYSTEM_MCP "pacemakerd" // Names of internally generated node attributes # define CRM_ATTR_UNAME "#uname" # define CRM_ATTR_ID "#id" # define CRM_ATTR_KIND "#kind" # define CRM_ATTR_ROLE "#role" # define CRM_ATTR_IS_DC "#is_dc" # define CRM_ATTR_CLUSTER_NAME "#cluster-name" # define CRM_ATTR_SITE_NAME "#site-name" # define CRM_ATTR_UNFENCED "#node-unfenced" # define CRM_ATTR_DIGESTS_ALL "#digests-all" # define CRM_ATTR_DIGESTS_SECURE "#digests-secure" # define CRM_ATTR_RA_VERSION "#ra-version" # define CRM_ATTR_PROTOCOL "#attrd-protocol" /* Valid operations */ # define CRM_OP_NOOP "noop" # define CRM_OP_JOIN_ANNOUNCE "join_announce" # define CRM_OP_JOIN_OFFER "join_offer" # define CRM_OP_JOIN_REQUEST "join_request" # define CRM_OP_JOIN_ACKNAK "join_ack_nack" # define CRM_OP_JOIN_CONFIRM "join_confirm" # define CRM_OP_PING "ping" # define CRM_OP_NODE_INFO "node-info" # define CRM_OP_THROTTLE "throttle" # define CRM_OP_VOTE "vote" # define CRM_OP_NOVOTE "no-vote" # define CRM_OP_HELLO "hello" # define CRM_OP_PECALC "pe_calc" # define CRM_OP_QUIT "quit" # define CRM_OP_LOCAL_SHUTDOWN "start_shutdown" # define CRM_OP_SHUTDOWN_REQ "req_shutdown" # define CRM_OP_SHUTDOWN "do_shutdown" # define CRM_OP_FENCE "stonith" # define CRM_OP_REGISTER "register" # define CRM_OP_IPC_FWD "ipc_fwd" # define CRM_OP_INVOKE_LRM "lrm_invoke" # define CRM_OP_LRM_REFRESH "lrm_refresh" /* Deprecated */ # define CRM_OP_LRM_QUERY "lrm_query" # define CRM_OP_LRM_DELETE "lrm_delete" # define CRM_OP_LRM_FAIL "lrm_fail" # define CRM_OP_PROBED "probe_complete" # define CRM_OP_REPROBE "probe_again" # define CRM_OP_CLEAR_FAILCOUNT "clear_failcount" # define CRM_OP_REMOTE_STATE "remote_state" # define CRM_OP_RELAXED_SET "one-or-more" # define CRM_OP_RELAXED_CLONE "clone-one-or-more" # define CRM_OP_RM_NODE_CACHE "rm_node_cache" # define CRM_OP_MAINTENANCE_NODES "maintenance_nodes" /* Possible cluster membership states */ # define CRMD_JOINSTATE_DOWN "down" # define CRMD_JOINSTATE_PENDING "pending" # define CRMD_JOINSTATE_MEMBER "member" # define CRMD_JOINSTATE_NACK "banned" # define CRMD_ACTION_DELETE "delete" # define CRMD_ACTION_CANCEL "cancel" # define CRMD_ACTION_RELOAD "reload" # define CRMD_ACTION_MIGRATE "migrate_to" # define CRMD_ACTION_MIGRATED "migrate_from" # define CRMD_ACTION_START "start" # define CRMD_ACTION_STARTED "running" # define CRMD_ACTION_STOP "stop" # define CRMD_ACTION_STOPPED "stopped" # define CRMD_ACTION_PROMOTE "promote" # define CRMD_ACTION_PROMOTED "promoted" # define CRMD_ACTION_DEMOTE "demote" # define CRMD_ACTION_DEMOTED "demoted" # define CRMD_ACTION_NOTIFY "notify" # define CRMD_ACTION_NOTIFIED "notified" # define CRMD_ACTION_STATUS "monitor" # define CRMD_ACTION_METADATA "meta-data" # define CRMD_METADATA_CALL_TIMEOUT 30000 /* short names */ # define RSC_DELETE CRMD_ACTION_DELETE # define RSC_CANCEL CRMD_ACTION_CANCEL # define RSC_MIGRATE CRMD_ACTION_MIGRATE # define RSC_MIGRATED CRMD_ACTION_MIGRATED # define RSC_START CRMD_ACTION_START # define RSC_STARTED CRMD_ACTION_STARTED # define RSC_STOP CRMD_ACTION_STOP # define RSC_STOPPED CRMD_ACTION_STOPPED # define RSC_PROMOTE CRMD_ACTION_PROMOTE # define RSC_PROMOTED CRMD_ACTION_PROMOTED # define RSC_DEMOTE CRMD_ACTION_DEMOTE # define RSC_DEMOTED CRMD_ACTION_DEMOTED # define RSC_NOTIFY CRMD_ACTION_NOTIFY # define RSC_NOTIFIED CRMD_ACTION_NOTIFIED # define RSC_STATUS CRMD_ACTION_STATUS # define RSC_METADATA CRMD_ACTION_METADATA /* *INDENT-ON* */ typedef GList *GListPtr; # include # include static inline const char * crm_action_str(const char *task, guint interval_ms) { if(safe_str_eq(task, RSC_STATUS) && !interval_ms) { return "probe"; } return task; } #ifdef __cplusplus } #endif #endif diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 4688458988..2ebdd14588 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,473 +1,614 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include // atoi() #include // gboolean, GMainLoop, etc. #include // xmlNode #include #include #include #include +#include #include +#include #include #define DEFAULT_MESSAGE_TIMEOUT_MS 30000 static guint message_timer_id = 0; static guint message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; static GMainLoop *mainloop = NULL; bool do_work(pcmk_ipc_api_t *api); void do_find_node_list(xmlNode *xml_node); +static char *ipc_name = NULL; + gboolean admin_message_timeout(gpointer data); static enum { cmd_none, cmd_shutdown, cmd_health, cmd_elect_dc, cmd_whois_dc, cmd_list_nodes, + cmd_pacemakerd_health, } command = cmd_none; static gboolean BE_VERBOSE = FALSE; static gboolean BASH_EXPORT = FALSE; static gboolean BE_SILENT = FALSE; static char *dest_node = NULL; static crm_exit_t exit_code = CRM_EX_OK; static pcmk__cli_option_t long_options[] = { // long option, argument type, storage, short option, description, flags { "help", no_argument, NULL, '?', "\tThis text", pcmk__option_default }, { "version", no_argument, NULL, '$', "\tVersion information", pcmk__option_default }, { "quiet", no_argument, NULL, 'q', "\tDisplay only the essential query information", pcmk__option_default }, { "verbose", no_argument, NULL, 'V', "\tIncrease debug output", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nCommands:", pcmk__option_default }, /* daemon options */ { "status", required_argument, NULL, 'S', "Display the status of the specified node.", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\n\tResult is state of node's internal finite state machine, which " "can be useful for debugging\n", pcmk__option_default }, + { + "pacemakerd", no_argument, NULL, 'P', + "Display the status of local pacemakerd.", pcmk__option_default + }, + { + "-spacer-", no_argument, NULL, '-', + "\n\tResult is the state of the sub-daemons watched by pacemakerd.\n", + pcmk__option_default + }, { "dc_lookup", no_argument, NULL, 'D', "Display the uname of the node co-ordinating the cluster.", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\n\tThis is an internal detail rarely useful to administrators " "except when deciding on which node to examine the logs.\n", pcmk__option_default }, { "nodes", no_argument, NULL, 'N', "\tDisplay the uname of all member nodes", pcmk__option_default }, { "election", no_argument, NULL, 'E', "(Advanced) Start an election for the cluster co-ordinator", pcmk__option_default }, { "kill", required_argument, NULL, 'K', "(Advanced) Stop controller (not rest of cluster stack) on " "specified node", pcmk__option_default }, { "health", no_argument, NULL, 'H', NULL, pcmk__option_hidden }, { "-spacer-", no_argument, NULL, '-', "\nAdditional Options:", pcmk__option_default }, { XML_ATTR_TIMEOUT, required_argument, NULL, 't', "Time (in milliseconds) to wait before declaring the operation failed", pcmk__option_default }, { "bash-export", no_argument, NULL, 'B', "Display nodes as shell commands of the form 'export uname=uuid' " - "(valid with -N/--nodes)'\n", + "(valid with -N/--nodes)", + pcmk__option_default + }, + { + "ipc-name", required_argument, NULL, 'i', + "Name to use for ipc instead of 'crmadmin' (with -P/--pacemakerd).", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', - "Notes:", pcmk__option_default + "\nNotes:", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', - "The -K and -E commands do not work and may be removed in a future " + "\nThe -K and -E commands do not work and may be removed in a future " "version.", pcmk__option_default }, { 0, 0, 0, 0 } }; static void quit_main_loop(crm_exit_t ec) { exit_code = ec; if (mainloop != NULL) { GMainLoop *mloop = mainloop; mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static void controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_controld_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected fprintf(stderr, "error: Lost connection to controller\n"); } goto done; break; case pcmk_ipc_event_reply: break; default: return; } if (message_timer_id != 0) { g_source_remove(message_timer_id); message_timer_id = 0; } if (status != CRM_EX_OK) { fprintf(stderr, "error: Bad reply from controller: %s", crm_exit_str(status)); exit_code = status; goto done; } if (reply->reply_type != pcmk_controld_reply_ping) { fprintf(stderr, "error: Unknown reply type %d from controller\n", reply->reply_type); goto done; } // Parse desired information from reply switch (command) { case cmd_health: printf("Status of %s@%s: %s (%s)\n", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); if (BE_SILENT && (reply->data.ping.fsa_state != NULL)) { fprintf(stderr, "%s\n", reply->data.ping.fsa_state); } exit_code = CRM_EX_OK; break; case cmd_whois_dc: printf("Designated Controller is: %s\n", reply->host_from); if (BE_SILENT && (reply->host_from != NULL)) { fprintf(stderr, "%s\n", reply->host_from); } exit_code = CRM_EX_OK; break; default: // Not really possible here exit_code = CRM_EX_SOFTWARE; break; } done: pcmk_disconnect_ipc(controld_api); quit_main_loop(exit_code); } +static void +pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, + enum pcmk_ipc_event event_type, crm_exit_t status, + void *event_data, void *user_data) +{ + pcmk_pacemakerd_api_reply_t *reply = event_data; + + switch (event_type) { + case pcmk_ipc_event_disconnect: + if (exit_code == CRM_EX_DISCONNECT) { // Unexpected + fprintf(stderr, "error: Lost connection to pacemakerd\n"); + } + goto done; + break; + + case pcmk_ipc_event_reply: + break; + + default: + return; + } + + if (message_timer_id != 0) { + g_source_remove(message_timer_id); + message_timer_id = 0; + } + + if (status != CRM_EX_OK) { + fprintf(stderr, "error: Bad reply from pacemakerd: %s", + crm_exit_str(status)); + exit_code = status; + goto done; + } + + if (reply->reply_type != pcmk_pacemakerd_reply_ping) { + fprintf(stderr, "error: Unknown reply type %d from pacemakerd\n", + reply->reply_type); + goto done; + } + + // Parse desired information from reply + switch (command) { + case cmd_pacemakerd_health: + { + crm_time_t *crm_when = crm_time_new(NULL); + char *pinged_buf = NULL; + + crm_time_set_timet(crm_when, &reply->data.ping.last_good); + pinged_buf = crm_time_as_string(crm_when, + crm_time_log_date | crm_time_log_timeofday | + crm_time_log_with_timezone); + + printf("Status of %s: '%s' %s %s\n", + reply->data.ping.sys_from, + (reply->data.ping.status == pcmk_rc_ok)? + pcmk_pacemakerd_api_daemon_state_enum2text( + reply->data.ping.state):"query failed", + (reply->data.ping.status == pcmk_rc_ok)?"last updated":"", + (reply->data.ping.status == pcmk_rc_ok)?pinged_buf:""); + if (BE_SILENT && + (reply->data.ping.state != pcmk_pacemakerd_state_invalid)) { + fprintf(stderr, "%s\n", + (reply->data.ping.status == pcmk_rc_ok)? + pcmk_pacemakerd_api_daemon_state_enum2text( + reply->data.ping.state): + "query failed"); + } + exit_code = CRM_EX_OK; + free(pinged_buf); + } + break; + + default: // Not really possible here + exit_code = CRM_EX_SOFTWARE; + break; + } + +done: + pcmk_disconnect_ipc(pacemakerd_api); + quit_main_loop(exit_code); +} + // \return Standard Pacemaker return code static int list_nodes() { cib_t *the_cib = cib_new(); xmlNode *output = NULL; int rc; if (the_cib == NULL) { return ENOMEM; } rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { return pcmk_legacy2rc(rc); } rc = the_cib->cmds->query(the_cib, NULL, &output, cib_scope_local | cib_sync_call); if (rc == pcmk_ok) { do_find_node_list(output); free_xml(output); } the_cib->cmds->signoff(the_cib); return pcmk_legacy2rc(rc); } int main(int argc, char **argv) { int option_index = 0; int argerr = 0; int flag; int rc; pcmk_ipc_api_t *controld_api = NULL; + pcmk_ipc_api_t *pacemakerd_api = NULL; bool need_controld_api = true; + bool need_pacemakerd_api = false; crm_log_cli_init("crmadmin"); pcmk__set_cli_options(NULL, " [options]", long_options, "query and manage the Pacemaker controller"); if (argc < 2) { pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'V': BE_VERBOSE = TRUE; crm_bump_log_level(argc, argv); break; case 't': message_timeout_ms = (guint) atoi(optarg); if (message_timeout_ms < 1) { message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; } break; - + case 'i': + ipc_name = strdup(optarg); + break; case '$': case '?': pcmk__cli_help(flag, CRM_EX_OK); break; case 'D': command = cmd_whois_dc; break; case 'B': BASH_EXPORT = TRUE; break; case 'K': command = cmd_shutdown; crm_trace("Option %c => %s", flag, optarg); if (dest_node != NULL) { free(dest_node); } dest_node = strdup(optarg); break; case 'q': BE_SILENT = TRUE; break; + case 'P': + command = cmd_pacemakerd_health; + need_pacemakerd_api = true; + need_controld_api = false; + break; case 'S': command = cmd_health; crm_trace("Option %c => %s", flag, optarg); if (dest_node != NULL) { free(dest_node); } dest_node = strdup(optarg); break; case 'E': command = cmd_elect_dc; break; case 'N': command = cmd_list_nodes; need_controld_api = false; break; case 'H': fprintf(stderr, "Cluster-wide health option not supported\n"); ++argerr; break; default: printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (optind > argc) { ++argerr; } if (command == cmd_none) { fprintf(stderr, "error: Must specify a command option\n\n"); ++argerr; } if (argerr) { pcmk__cli_help('?', CRM_EX_USAGE); } // Connect to the controller if needed if (need_controld_api) { rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (controld_api == NULL) { fprintf(stderr, "error: Could not connect to controller: %s\n", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; } pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL); rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { fprintf(stderr, "error: Could not connect to controller: %s\n", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); goto done; } } - if (do_work(controld_api)) { + // Connect to pacemakerd if needed + if (need_pacemakerd_api) { + rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd); + if (pacemakerd_api == NULL) { + fprintf(stderr, "error: Could not connect to pacemakerd: %s\n", + pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); + goto done; + } + pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, NULL); + rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_main); + if (rc != pcmk_rc_ok) { + fprintf(stderr, "error: Could not connect to pacemakerd: %s\n", + pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); + goto done; + } + } + + if (do_work(controld_api?controld_api:pacemakerd_api)) { // A reply is needed from controller, so run main loop to get it exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects mainloop = g_main_loop_new(NULL, FALSE); message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL); g_main_loop_run(mainloop); } done: + if (controld_api != NULL) { pcmk_ipc_api_t *capi = controld_api; - controld_api = NULL; // Ensure we can't free this twice pcmk_free_ipc_api(capi); } + + if (pacemakerd_api != NULL) { + pcmk_ipc_api_t *capi = pacemakerd_api; + pacemakerd_api = NULL; // Ensure we can't free this twice + pcmk_free_ipc_api(capi); + } + if (mainloop != NULL) { g_main_loop_unref(mainloop); mainloop = NULL; } return crm_exit(exit_code); } // \return True if reply from controller is needed bool -do_work(pcmk_ipc_api_t *controld_api) +do_work(pcmk_ipc_api_t *api) { bool need_reply = false; int rc = pcmk_rc_ok; switch (command) { case cmd_shutdown: - rc = pcmk_controld_api_shutdown(controld_api, dest_node); + rc = pcmk_controld_api_shutdown(api, dest_node); break; case cmd_health: // dest_node != NULL case cmd_whois_dc: // dest_node == NULL - rc = pcmk_controld_api_ping(controld_api, dest_node); + rc = pcmk_controld_api_ping(api, dest_node); need_reply = true; break; case cmd_elect_dc: - rc = pcmk_controld_api_start_election(controld_api); + rc = pcmk_controld_api_start_election(api); break; case cmd_list_nodes: rc = list_nodes(); break; + case cmd_pacemakerd_health: + rc = pcmk_pacemakerd_api_ping(api, ipc_name); + need_reply = true; + break; + case cmd_none: // not actually possible here break; } if (rc != pcmk_rc_ok) { fprintf(stderr, "error: Command failed: %s", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); } return need_reply; } gboolean admin_message_timeout(gpointer data) { fprintf(stderr, "error: No reply received from controller before timeout (%dms)\n", message_timeout_ms); message_timer_id = 0; quit_main_loop(CRM_EX_TIMEOUT); return FALSE; // Tells glib to remove source } void do_find_node_list(xmlNode * xml_node) { int found = 0; xmlNode *node = NULL; xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node); for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL; node = crm_next_same_xml(node)) { if (BASH_EXPORT) { printf("export %s=%s\n", crm_element_value(node, XML_ATTR_UNAME), crm_element_value(node, XML_ATTR_ID)); } else { const char *node_type = crm_element_value(node, XML_ATTR_TYPE); if (node_type == NULL) { node_type = "member"; } printf("%s node: %s (%s)\n", node_type, crm_element_value(node, XML_ATTR_UNAME), crm_element_value(node, XML_ATTR_ID)); } found++; } // @TODO List Pacemaker Remote nodes that don't have a entry if (found == 0) { printf("No nodes configured\n"); } }