1 /*
2  * Copyright 2004-2020 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 General Public License version 2
7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <libgen.h>
16 
17 #include <sys/param.h>
18 #include <sys/types.h>
19 
20 #include <crm/crm.h>
21 #include <crm/msg_xml.h>
22 #include <crm/common/xml_internal.h>
23 #include <crm/common/ipc.h>
24 
25 #include <crm/common/attrd_internal.h>
26 
27 static pcmk__cli_option_t long_options[] = {
28     // long option, argument type, storage, short option, description, flags
29     {
30         "help", no_argument, NULL, '?',
31         "\tThis text", pcmk__option_default
32     },
33     {
34         "version", no_argument, NULL, '$',
35         "\tVersion information", pcmk__option_default
36     },
37     {
38         "verbose", no_argument, NULL, 'V',
39         "\tIncrease debug output\n", pcmk__option_default
40     },
41     {
42         "name", required_argument, NULL, 'n',
43         "The attribute's name", pcmk__option_default
44     },
45     {
46         "-spacer-", no_argument, NULL, '-',
47         "\nCommands:", pcmk__option_default
48     },
49     {
50         "update", required_argument, NULL, 'U',
51         "Update attribute's value in pacemaker-attrd. If this causes the value "
52             "to change, it will also be updated in the cluster configuration.",
53         pcmk__option_default
54     },
55     {
56         "update-both", required_argument, NULL, 'B',
57         "Update attribute's value and time to wait (dampening) in "
58             "pacemaker-attrd. If this causes the value or dampening to change, "
59             "the attribute will also be written to the cluster configuration, "
60             "so be aware that repeatedly changing the dampening reduces its "
61             "effectiveness.",
62         pcmk__option_default
63     },
64     {
65         "update-delay", no_argument, NULL, 'Y',
66         "Update attribute's dampening in pacemaker-attrd (requires "
67             "-d/--delay). If this causes the dampening to change, the "
68             "attribute will also be written to the cluster configuration, so "
69             "be aware that repeatedly changing the dampening reduces its "
70             "effectiveness.",
71         pcmk__option_default
72     },
73     {
74         "query", no_argument, NULL, 'Q',
75         "\tQuery the attribute's value from pacemaker-attrd",
76         pcmk__option_default
77     },
78     {
79         "delete", no_argument, NULL, 'D',
80         "\tDelete attribute from pacemaker-attrd. If a value was previously "
81             "set, it will also be removed from the cluster configuration",
82         pcmk__option_default
83     },
84     {
85         "refresh", no_argument, NULL, 'R',
86         "\t(Advanced) Force the pacemaker-attrd daemon to resend all current "
87             "values to the CIB",
88         pcmk__option_default
89     },
90 
91     {
92         "-spacer-", no_argument, NULL, '-',
93         "\nAdditional options:", pcmk__option_default
94     },
95     {
96         "delay", required_argument, NULL, 'd',
97         "The time to wait (dampening) in seconds for further changes "
98             "before writing",
99         pcmk__option_default
100     },
101     {
102         "set", required_argument, NULL, 's',
103         "(Advanced) The attribute set in which to place the value",
104         pcmk__option_default
105     },
106     {
107         "node", required_argument, NULL, 'N',
108         "Set the attribute for the named node (instead of the local one)",
109         pcmk__option_default
110     },
111     {
112         "all", no_argument, NULL, 'A',
113         "Show values of the attribute for all nodes (query only)",
114         pcmk__option_default
115     },
116 
117     // @TODO Implement --lifetime
118     {
119         "lifetime", required_argument, NULL, 'l',
120         "(Not yet implemented) Lifetime of the node attribute (silently "
121             "ignored by cluster)",
122         pcmk__option_default
123     },
124     {
125         "private", no_argument, NULL, 'p',
126         "\tIf this creates a new attribute, never write the attribute to CIB",
127         pcmk__option_default
128     },
129 
130     /* Legacy options */
131     {
132         "quiet", no_argument, NULL, 'q',
133         NULL, pcmk__option_hidden
134     },
135     {
136         "update", required_argument, NULL, 'v',
137         NULL, pcmk__option_hidden
138     },
139     {
140         "section", required_argument, NULL, 'S',
141         NULL, pcmk__option_hidden
142     },
143     { 0, 0, 0, 0 }
144 };
145 
146 static int do_query(const char *attr_name, const char *attr_node, gboolean query_all);
147 static int do_update(char command, const char *attr_node, const char *attr_name,
148                      const char *attr_value, const char *attr_section,
149                      const char *attr_set, const char *attr_dampen, int attr_options);
150 
151 // Free memory at exit to make analyzers happy
152 #define cleanup_memory() \
153     free(attr_dampen); \
154     free(attr_name); \
155     free(attr_node); \
156     free(attr_section); \
157     free(attr_set);
158 
159 #define set_option(option_var) \
160     if (option_var) { \
161         free(option_var); \
162     } \
163     option_var = strdup(optarg);
164 
165 int
main(int argc,char ** argv)166 main(int argc, char **argv)
167 {
168     int index = 0;
169     int argerr = 0;
170     int attr_options = pcmk__node_attr_none;
171     int flag;
172     crm_exit_t exit_code = CRM_EX_OK;
173     char *attr_node = NULL;
174     char *attr_name = NULL;
175     char *attr_set = NULL;
176     char *attr_section = NULL;
177     char *attr_dampen = NULL;
178     const char *attr_value = NULL;
179     char command = 'Q';
180 
181     gboolean query_all = FALSE;
182 
183     pcmk__cli_init_logging("attrd_updater", 0);
184     pcmk__set_cli_options(NULL, "-n <attribute> <command> [options]",
185                           long_options,
186                           "query and update Pacemaker node attributes");
187 
188     if (argc < 2) {
189         pcmk__cli_help('?', CRM_EX_USAGE);
190     }
191 
192     while (1) {
193         flag = pcmk__next_cli_option(argc, argv, &index, NULL);
194         if (flag == -1)
195             break;
196 
197         switch (flag) {
198             case 'V':
199                 crm_bump_log_level(argc, argv);
200                 break;
201             case '?':
202             case '$':
203                 cleanup_memory();
204                 pcmk__cli_help(flag, CRM_EX_OK);
205                 break;
206             case 'n':
207                 set_option(attr_name);
208                 break;
209             case 's':
210                 set_option(attr_set);
211                 break;
212             case 'd':
213                 set_option(attr_dampen);
214                 break;
215             case 'l':
216             case 'S':
217                 set_option(attr_section);
218                 break;
219             case 'N':
220                 set_option(attr_node);
221                 break;
222             case 'A':
223                 query_all = TRUE;
224                 break;
225             case 'p':
226                 pcmk__set_node_attr_flags(attr_options, pcmk__node_attr_private);
227                 break;
228             case 'q':
229                 break;
230             case 'Y':
231                 command = flag;
232                 crm_log_args(argc, argv); /* Too much? */
233                 break;
234             case 'Q':
235             case 'B':
236             case 'R':
237             case 'D':
238             case 'U':
239             case 'v':
240                 command = flag;
241                 attr_value = optarg;
242                 crm_log_args(argc, argv); /* Too much? */
243                 break;
244             default:
245                 ++argerr;
246                 break;
247         }
248     }
249 
250     if (optind > argc) {
251         ++argerr;
252     }
253 
254     if (command != 'R' && attr_name == NULL) {
255         ++argerr;
256     }
257 
258     if (argerr) {
259         cleanup_memory();
260         pcmk__cli_help('?', CRM_EX_USAGE);
261     }
262 
263     if (command == 'Q') {
264         exit_code = crm_errno2exit(do_query(attr_name, attr_node, query_all));
265     } else {
266         /* @TODO We don't know whether the specified node is a Pacemaker Remote
267          * node or not, so we can't set pcmk__node_attr_remote when appropriate.
268          * However, it's not a big problem, because pacemaker-attrd will learn
269          * and remember a node's "remoteness".
270          */
271         const char *target = pcmk__node_attr_target(attr_node);
272 
273         exit_code = pcmk_rc2exitc(do_update(command,
274                                             target == NULL ? attr_node : target,
275                                             attr_name, attr_value,
276                                             attr_section, attr_set,
277                                             attr_dampen, attr_options));
278     }
279 
280     cleanup_memory();
281     crm_exit(exit_code);
282 }
283 
284 /*!
285  * \internal
286  * \brief Submit a query request to pacemaker-attrd and wait for reply
287  *
288  * \param[in] name    Name of attribute to query
289  * \param[in] host    Query applies to this host only (or all hosts if NULL)
290  * \param[out] reply  On success, will be set to new XML tree with reply
291  *
292  * \return pcmk_ok on success, -errno on error
293  * \note On success, caller is responsible for freeing result via free_xml(*reply)
294  */
295 static int
send_attrd_query(const char * name,const char * host,xmlNode ** reply)296 send_attrd_query(const char *name, const char *host, xmlNode **reply)
297 {
298     int rc;
299     crm_ipc_t *ipc;
300     xmlNode *query;
301 
302     /* Build the query XML */
303     query = create_xml_node(NULL, __func__);
304     if (query == NULL) {
305         return -ENOMEM;
306     }
307     crm_xml_add(query, F_TYPE, T_ATTRD);
308     crm_xml_add(query, F_ORIG, crm_system_name);
309     crm_xml_add(query, PCMK__XA_ATTR_NODE_NAME, host);
310     crm_xml_add(query, PCMK__XA_TASK, PCMK__ATTRD_CMD_QUERY);
311     crm_xml_add(query, PCMK__XA_ATTR_NAME, name);
312 
313     /* Connect to pacemaker-attrd, send query XML and get reply */
314     crm_debug("Sending query for value of %s on %s", name, (host? host : "all nodes"));
315     ipc = crm_ipc_new(T_ATTRD, 0);
316     if (crm_ipc_connect(ipc) == FALSE) {
317         crm_perror(LOG_ERR, "Connection to cluster attribute manager failed");
318         rc = -ENOTCONN;
319     } else {
320         rc = crm_ipc_send(ipc, query, crm_ipc_client_response, 0, reply);
321         if (rc > 0) {
322             rc = pcmk_ok;
323         }
324         crm_ipc_close(ipc);
325     }
326     crm_ipc_destroy(ipc);
327 
328     free_xml(query);
329     return(rc);
330 }
331 
332 /*!
333  * \brief Validate pacemaker-attrd's XML reply to an query
334  *
335  * param[in] reply      Root of reply XML tree to validate
336  * param[in] attr_name  Name of attribute that was queried
337  *
338  * \return pcmk_ok on success,
339  *         -errno on error (-ENXIO = requested attribute does not exist)
340  */
341 static int
validate_attrd_reply(xmlNode * reply,const char * attr_name)342 validate_attrd_reply(xmlNode *reply, const char *attr_name)
343 {
344     const char *reply_attr;
345 
346     if (reply == NULL) {
347         fprintf(stderr, "Could not query value of %s: reply did not contain valid XML\n",
348                 attr_name);
349         return -pcmk_err_schema_validation;
350     }
351     crm_log_xml_trace(reply, "Reply");
352 
353     reply_attr = crm_element_value(reply, PCMK__XA_ATTR_NAME);
354     if (reply_attr == NULL) {
355         fprintf(stderr, "Could not query value of %s: attribute does not exist\n",
356                 attr_name);
357         return -ENXIO;
358     }
359 
360     if (!pcmk__str_eq(crm_element_value(reply, F_TYPE), T_ATTRD, pcmk__str_casei)
361         || (crm_element_value(reply, PCMK__XA_ATTR_VERSION) == NULL)
362         || strcmp(reply_attr, attr_name)) {
363             fprintf(stderr,
364                     "Could not query value of %s: reply did not contain expected identification\n",
365                     attr_name);
366             return -pcmk_err_schema_validation;
367     }
368     return pcmk_ok;
369 }
370 
371 /*!
372  * \brief Print the attribute values in a pacemaker-attrd XML query reply
373  *
374  * \param[in] reply     Root of XML tree with query reply
375  * \param[in] attr_name Name of attribute that was queried
376  *
377  * \return TRUE if any values were printed
378  */
379 static gboolean
print_attrd_values(xmlNode * reply,const char * attr_name)380 print_attrd_values(xmlNode *reply, const char *attr_name)
381 {
382     xmlNode *child;
383     const char *reply_host, *reply_value;
384     gboolean have_values = FALSE;
385 
386     /* Iterate through reply's XML tags (a node tag for each host-value pair) */
387     for (child = pcmk__xml_first_child(reply); child != NULL;
388          child = pcmk__xml_next(child)) {
389 
390         if (!pcmk__str_eq((const char *)child->name, XML_CIB_TAG_NODE,
391                           pcmk__str_casei)) {
392             crm_warn("Ignoring unexpected %s tag in query reply", child->name);
393         } else {
394             reply_host = crm_element_value(child, PCMK__XA_ATTR_NODE_NAME);
395             reply_value = crm_element_value(child, PCMK__XA_ATTR_VALUE);
396 
397             if (reply_host == NULL) {
398                 crm_warn("Ignoring %s tag without %s attribute in query reply",
399                          XML_CIB_TAG_NODE, PCMK__XA_ATTR_NODE_NAME);
400             } else {
401                 printf("name=\"%s\" host=\"%s\" value=\"%s\"\n",
402                        attr_name, reply_host, (reply_value? reply_value : ""));
403                 have_values = TRUE;
404             }
405         }
406     }
407     return have_values;
408 }
409 
410 /*!
411  * \brief Submit a query to pacemaker-attrd and print reply
412  *
413  * \param[in] attr_name  Name of attribute to be affected by request
414  * \param[in] attr_node  Name of host to query for (or NULL for localhost)
415  * \param[in] query_all  If TRUE, ignore attr_node and query all nodes instead
416  *
417  * \return pcmk_ok on success, -errno on error
418  */
419 static int
do_query(const char * attr_name,const char * attr_node,gboolean query_all)420 do_query(const char *attr_name, const char *attr_node, gboolean query_all)
421 {
422     xmlNode *reply = NULL;
423     int rc;
424 
425     /* Decide which node(s) to query */
426     if (query_all == TRUE) {
427         attr_node = NULL;
428     } else {
429         const char *target = pcmk__node_attr_target(attr_node);
430         if (target != NULL) {
431             attr_node = target;
432         }
433     }
434 
435     /* Build and send pacemaker-attrd request, and get XML reply */
436     rc = send_attrd_query(attr_name, attr_node, &reply);
437     if (rc != pcmk_ok) {
438         fprintf(stderr, "Could not query value of %s: %s (%d)\n", attr_name, pcmk_strerror(rc), rc);
439         return rc;
440     }
441 
442     /* Validate the XML reply */
443     rc = validate_attrd_reply(reply, attr_name);
444     if (rc != pcmk_ok) {
445         if (reply != NULL) {
446             free_xml(reply);
447         }
448         return rc;
449     }
450 
451     /* Print the values from the reply */
452     if (print_attrd_values(reply, attr_name) == FALSE) {
453         fprintf(stderr,
454                 "Could not query value of %s: reply had attribute name but no host values\n",
455                 attr_name);
456         free_xml(reply);
457         return -pcmk_err_schema_validation;
458     }
459 
460     return pcmk_ok;
461 }
462 
463 static int
do_update(char command,const char * attr_node,const char * attr_name,const char * attr_value,const char * attr_section,const char * attr_set,const char * attr_dampen,int attr_options)464 do_update(char command, const char *attr_node, const char *attr_name,
465           const char *attr_value, const char *attr_section,
466           const char *attr_set, const char *attr_dampen, int attr_options)
467 {
468     int rc = pcmk__node_attr_request(NULL, command, attr_node, attr_name,
469                                      attr_value, attr_section, attr_set,
470                                      attr_dampen, NULL, attr_options);
471     if (rc != pcmk_rc_ok) {
472         fprintf(stderr, "Could not update %s=%s: %s (%d)\n",
473                 attr_name, attr_value, pcmk_rc_str(rc), rc);
474     }
475     return rc;
476 }
477