Line data Source code
1 : /*
2 : * Copyright 2004-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 <stdio.h>
13 : #include <string.h>
14 : #include <sys/stat.h>
15 : #include <glib.h>
16 : #include <dirent.h>
17 :
18 : #include <crm/crm.h>
19 : #include <crm/common/xml.h>
20 : #include <crm/stonith-ng.h>
21 : #include <crm/fencing/internal.h>
22 :
23 : #include "fencing_private.h"
24 :
25 : #define RH_STONITH_PREFIX "fence_"
26 :
27 : /*!
28 : * \internal
29 : * \brief Add available RHCS-compatible agents to a list
30 : *
31 : * \param[in,out] List to add to
32 : *
33 : * \return Number of agents added
34 : */
35 : int
36 0 : stonith__list_rhcs_agents(stonith_key_value_t **devices)
37 : {
38 : // Essentially: ls -1 @sbin_dir@/fence_*
39 :
40 0 : int count = 0, i;
41 : struct dirent **namelist;
42 0 : const int file_num = scandir(PCMK__FENCE_BINDIR, &namelist, 0, alphasort);
43 :
44 : #if _POSIX_C_SOURCE < 200809L && !(defined(O_SEARCH) || defined(O_PATH))
45 : char buffer[FILENAME_MAX + 1];
46 : #elif defined(O_SEARCH)
47 : const int dirfd = open(PCMK__FENCE_BINDIR, O_SEARCH);
48 : #else
49 0 : const int dirfd = open(PCMK__FENCE_BINDIR, O_PATH);
50 : #endif
51 :
52 0 : for (i = 0; i < file_num; i++) {
53 : struct stat prop;
54 :
55 0 : if (pcmk__starts_with(namelist[i]->d_name, RH_STONITH_PREFIX)) {
56 : #if _POSIX_C_SOURCE < 200809L && !(defined(O_SEARCH) || defined(O_PATH))
57 : snprintf(buffer, sizeof(buffer), "%s/%s", PCMK__FENCE_BINDIR,
58 : namelist[i]->d_name);
59 : if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
60 : #else
61 0 : if (dirfd == -1) {
62 0 : if (i == 0) {
63 0 : crm_notice("Problem with listing %s directory"
64 : CRM_XS "errno=%d", RH_STONITH_PREFIX, errno);
65 : }
66 0 : free(namelist[i]);
67 0 : continue;
68 : }
69 : /* note: we can possibly prevent following symlinks here,
70 : which may be a good idea, but fall on the nose when
71 : these agents are moved elsewhere & linked back */
72 0 : if (fstatat(dirfd, namelist[i]->d_name, &prop, 0) == 0
73 0 : && S_ISREG(prop.st_mode)) {
74 : #endif
75 0 : *devices = stonith_key_value_add(*devices, NULL,
76 0 : namelist[i]->d_name);
77 0 : count++;
78 : }
79 : }
80 0 : free(namelist[i]);
81 : }
82 0 : if (file_num > 0) {
83 0 : free(namelist);
84 : }
85 : #if _POSIX_C_SOURCE >= 200809L || defined(O_SEARCH) || defined(O_PATH)
86 0 : if (dirfd >= 0) {
87 0 : close(dirfd);
88 : }
89 : #endif
90 0 : return count;
91 : }
92 :
93 : static void
94 0 : stonith_rhcs_parameter_not_required(xmlNode *metadata, const char *parameter)
95 : {
96 0 : char *xpath = NULL;
97 0 : xmlXPathObject *xpathObj = NULL;
98 :
99 0 : CRM_CHECK(metadata != NULL, return);
100 0 : CRM_CHECK(parameter != NULL, return);
101 :
102 0 : xpath = crm_strdup_printf("//" PCMK_XE_PARAMETER "[@" PCMK_XA_NAME "='%s']",
103 : parameter);
104 : /* Fudge metadata so that the parameter isn't required in config
105 : * Pacemaker handles and adds it */
106 0 : xpathObj = xpath_search(metadata, xpath);
107 0 : if (numXpathResults(xpathObj) > 0) {
108 0 : xmlNode *tmp = getXpathResult(xpathObj, 0);
109 :
110 0 : crm_xml_add(tmp, "required", "0");
111 : }
112 0 : freeXpathObject(xpathObj);
113 0 : free(xpath);
114 : }
115 :
116 : /*!
117 : * \brief Execute RHCS-compatible agent's metadata action
118 : *
119 : * \param[in] agent Agent to execute
120 : * \param[in] timeout_sec Action timeout
121 : * \param[out] metadata Where to store output xmlNode (or NULL to ignore)
122 : */
123 : static int
124 0 : stonith__rhcs_get_metadata(const char *agent, int timeout_sec,
125 : xmlNode **metadata)
126 : {
127 0 : xmlNode *xml = NULL;
128 0 : xmlNode *actions = NULL;
129 0 : xmlXPathObject *xpathObj = NULL;
130 0 : stonith_action_t *action = stonith__action_create(agent,
131 : PCMK_ACTION_METADATA,
132 : NULL, 0, timeout_sec,
133 : NULL, NULL, NULL);
134 0 : int rc = stonith__execute(action);
135 0 : pcmk__action_result_t *result = stonith__action_result(action);
136 :
137 0 : if (result == NULL) {
138 0 : if (rc < 0) {
139 0 : crm_warn("Could not execute metadata action for %s: %s "
140 : CRM_XS " rc=%d", agent, pcmk_strerror(rc), rc);
141 : }
142 0 : stonith__destroy_action(action);
143 0 : return rc;
144 : }
145 :
146 0 : if (result->execution_status != PCMK_EXEC_DONE) {
147 0 : crm_warn("Could not execute metadata action for %s: %s",
148 : agent, pcmk_exec_status_str(result->execution_status));
149 0 : rc = pcmk_rc2legacy(stonith__result2rc(result));
150 0 : stonith__destroy_action(action);
151 0 : return rc;
152 : }
153 :
154 0 : if (!pcmk__result_ok(result)) {
155 0 : crm_warn("Metadata action for %s returned error code %d",
156 : agent, result->exit_status);
157 0 : rc = pcmk_rc2legacy(stonith__result2rc(result));
158 0 : stonith__destroy_action(action);
159 0 : return rc;
160 : }
161 :
162 0 : if (result->action_stdout == NULL) {
163 0 : crm_warn("Metadata action for %s returned no data", agent);
164 0 : stonith__destroy_action(action);
165 0 : return -ENODATA;
166 : }
167 :
168 0 : xml = pcmk__xml_parse(result->action_stdout);
169 0 : stonith__destroy_action(action);
170 :
171 0 : if (xml == NULL) {
172 0 : crm_warn("Metadata for %s is invalid", agent);
173 0 : return -pcmk_err_schema_validation;
174 : }
175 :
176 0 : xpathObj = xpath_search(xml, "//" PCMK_XE_ACTIONS);
177 0 : if (numXpathResults(xpathObj) > 0) {
178 0 : actions = getXpathResult(xpathObj, 0);
179 : }
180 0 : freeXpathObject(xpathObj);
181 :
182 : // Add start and stop (implemented by pacemaker, not agent) to meta-data
183 0 : xpathObj = xpath_search(xml,
184 : "//" PCMK_XE_ACTION
185 : "[@" PCMK_XA_NAME "='" PCMK_ACTION_STOP "']");
186 0 : if (numXpathResults(xpathObj) <= 0) {
187 0 : xmlNode *tmp = NULL;
188 0 : const char *timeout_str = NULL;
189 :
190 0 : timeout_str = pcmk__readable_interval(PCMK_DEFAULT_ACTION_TIMEOUT_MS);
191 :
192 0 : tmp = pcmk__xe_create(actions, PCMK_XE_ACTION);
193 0 : crm_xml_add(tmp, PCMK_XA_NAME, PCMK_ACTION_STOP);
194 0 : crm_xml_add(tmp, PCMK_META_TIMEOUT, timeout_str);
195 :
196 0 : tmp = pcmk__xe_create(actions, PCMK_XE_ACTION);
197 0 : crm_xml_add(tmp, PCMK_XA_NAME, PCMK_ACTION_START);
198 0 : crm_xml_add(tmp, PCMK_META_TIMEOUT, timeout_str);
199 : }
200 0 : freeXpathObject(xpathObj);
201 :
202 : // Fudge metadata so parameters are not required in config (pacemaker adds them)
203 0 : stonith_rhcs_parameter_not_required(xml, "action");
204 0 : stonith_rhcs_parameter_not_required(xml, "plug");
205 0 : stonith_rhcs_parameter_not_required(xml, "port");
206 :
207 0 : if (metadata) {
208 0 : *metadata = xml;
209 :
210 : } else {
211 0 : free_xml(xml);
212 : }
213 :
214 0 : return pcmk_ok;
215 : }
216 :
217 : /*!
218 : * \brief Retrieve metadata for RHCS-compatible fence agent
219 : *
220 : * \param[in] agent Agent to execute
221 : * \param[in] timeout_sec Action timeout
222 : * \param[out] output Where to store action output (or NULL to ignore)
223 : */
224 : int
225 0 : stonith__rhcs_metadata(const char *agent, int timeout_sec, char **output)
226 : {
227 0 : GString *buffer = NULL;
228 0 : xmlNode *xml = NULL;
229 :
230 0 : int rc = stonith__rhcs_get_metadata(agent, timeout_sec, &xml);
231 :
232 0 : if (rc != pcmk_ok) {
233 0 : goto done;
234 : }
235 :
236 0 : buffer = g_string_sized_new(1024);
237 0 : pcmk__xml_string(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buffer, 0);
238 :
239 0 : if (pcmk__str_empty(buffer->str)) {
240 0 : rc = -pcmk_err_schema_validation;
241 0 : goto done;
242 : }
243 :
244 0 : if (output != NULL) {
245 0 : pcmk__str_update(output, buffer->str);
246 : }
247 :
248 0 : done:
249 0 : if (buffer != NULL) {
250 0 : g_string_free(buffer, TRUE);
251 : }
252 0 : free_xml(xml);
253 0 : return rc;
254 : }
255 :
256 : bool
257 0 : stonith__agent_is_rhcs(const char *agent)
258 : {
259 : struct stat prop;
260 0 : char *buffer = crm_strdup_printf(PCMK__FENCE_BINDIR "/%s", agent);
261 0 : int rc = stat(buffer, &prop);
262 :
263 0 : free(buffer);
264 0 : return (rc >= 0) && S_ISREG(prop.st_mode);
265 : }
266 :
267 : int
268 0 : stonith__rhcs_validate(stonith_t *st, int call_options, const char *target,
269 : const char *agent, GHashTable *params,
270 : const char * host_arg, int timeout,
271 : char **output, char **error_output)
272 : {
273 0 : int rc = pcmk_ok;
274 0 : int remaining_timeout = timeout;
275 0 : xmlNode *metadata = NULL;
276 0 : stonith_action_t *action = NULL;
277 0 : pcmk__action_result_t *result = NULL;
278 :
279 0 : if (host_arg == NULL) {
280 0 : time_t start_time = time(NULL);
281 :
282 0 : rc = stonith__rhcs_get_metadata(agent, remaining_timeout, &metadata);
283 :
284 0 : if (rc == pcmk_ok) {
285 0 : uint32_t device_flags = 0;
286 :
287 0 : stonith__device_parameter_flags(&device_flags, agent, metadata);
288 0 : if (pcmk_is_set(device_flags, st_device_supports_parameter_port)) {
289 0 : host_arg = "port";
290 :
291 0 : } else if (pcmk_is_set(device_flags,
292 : st_device_supports_parameter_plug)) {
293 0 : host_arg = "plug";
294 : }
295 : }
296 :
297 0 : free_xml(metadata);
298 :
299 0 : remaining_timeout -= time(NULL) - start_time;
300 :
301 0 : if (rc == -ETIME || remaining_timeout <= 0 ) {
302 0 : return -ETIME;
303 : }
304 :
305 0 : } else if (pcmk__str_eq(host_arg, PCMK_VALUE_NONE, pcmk__str_casei)) {
306 0 : host_arg = NULL;
307 : }
308 :
309 0 : action = stonith__action_create(agent, PCMK_ACTION_VALIDATE_ALL, target, 0,
310 : remaining_timeout, params, NULL, host_arg);
311 :
312 0 : rc = stonith__execute(action);
313 0 : result = stonith__action_result(action);
314 :
315 0 : if (result != NULL) {
316 0 : rc = pcmk_rc2legacy(stonith__result2rc(result));
317 :
318 : // Take ownership of output so stonith__destroy_action() doesn't free it
319 0 : if (output != NULL) {
320 0 : *output = result->action_stdout;
321 0 : result->action_stdout = NULL;
322 : }
323 0 : if (error_output != NULL) {
324 0 : *error_output = result->action_stderr;
325 0 : result->action_stderr = NULL;
326 : }
327 : }
328 0 : stonith__destroy_action(action);
329 0 : return rc;
330 : }
|