Line data Source code
1 : /*
2 : * Copyright 2010-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 : #ifndef _GNU_SOURCE
13 : # define _GNU_SOURCE
14 : #endif
15 :
16 : #include <stdio.h>
17 : #include <errno.h>
18 : #include <sys/stat.h>
19 :
20 : #include <crm/crm.h>
21 : #include <crm/common/xml.h>
22 : #include <crm/services.h>
23 : #include "services_private.h"
24 : #include "services_lsb.h"
25 :
26 : // @TODO Use XML string constants and maybe a real XML object
27 : #define lsb_metadata_template \
28 : "<?xml " PCMK_XA_VERSION "='1.0'?>\n" \
29 : "<" PCMK_XE_RESOURCE_AGENT " " \
30 : PCMK_XA_NAME "='%s' " \
31 : PCMK_XA_VERSION "='" PCMK_DEFAULT_AGENT_VERSION "'>\n" \
32 : " <" PCMK_XE_VERSION ">1.1</" PCMK_XE_VERSION ">\n" \
33 : " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "='" PCMK__VALUE_EN "'>\n" \
34 : "%s" \
35 : " </" PCMK_XE_LONGDESC ">\n" \
36 : " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "='" PCMK__VALUE_EN "'>" \
37 : "%s" \
38 : "</" PCMK_XE_SHORTDESC ">\n" \
39 : " <" PCMK_XE_PARAMETERS "/>\n" \
40 : " <" PCMK_XE_ACTIONS ">\n" \
41 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='" PCMK_ACTION_META_DATA "'" \
42 : " " PCMK_META_TIMEOUT "='5s' />\n" \
43 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='" PCMK_ACTION_START "'" \
44 : " " PCMK_META_TIMEOUT "='15s' />\n" \
45 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='" PCMK_ACTION_STOP "'" \
46 : " " PCMK_META_TIMEOUT "='15s' />\n" \
47 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='" PCMK_ACTION_STATUS "'" \
48 : " " PCMK_META_TIMEOUT "='15s' />\n" \
49 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='restart'" \
50 : " " PCMK_META_TIMEOUT "='15s' />\n" \
51 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='force-reload'" \
52 : " " PCMK_META_TIMEOUT "='15s' />\n" \
53 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "='" PCMK_ACTION_MONITOR "'" \
54 : " " PCMK_META_TIMEOUT "='15s'" \
55 : " " PCMK_META_INTERVAL "='15s' />\n" \
56 : " </" PCMK_XE_ACTIONS ">\n" \
57 : " <" PCMK_XE_SPECIAL " " PCMK_XA_TAG "='LSB'>\n" \
58 : " <Provides>%s</Provides>\n" \
59 : " <Required-Start>%s</Required-Start>\n" \
60 : " <Required-Stop>%s</Required-Stop>\n" \
61 : " <Should-Start>%s</Should-Start>\n" \
62 : " <Should-Stop>%s</Should-Stop>\n" \
63 : " <Default-Start>%s</Default-Start>\n" \
64 : " <Default-Stop>%s</Default-Stop>\n" \
65 : " </" PCMK_XE_SPECIAL ">\n" \
66 : "</" PCMK_XE_RESOURCE_AGENT ">\n"
67 :
68 : /* See "Comment Conventions for Init Scripts" in the LSB core specification at:
69 : * http://refspecs.linuxfoundation.org/lsb.shtml
70 : */
71 : #define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO"
72 : #define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO"
73 : #define PROVIDES "# Provides:"
74 : #define REQUIRED_START "# Required-Start:"
75 : #define REQUIRED_STOP "# Required-Stop:"
76 : #define SHOULD_START "# Should-Start:"
77 : #define SHOULD_STOP "# Should-Stop:"
78 : #define DEFAULT_START "# Default-Start:"
79 : #define DEFAULT_STOP "# Default-Stop:"
80 : #define SHORT_DESC "# Short-Description:"
81 : #define DESCRIPTION "# Description:"
82 :
83 : /*!
84 : * \internal
85 : * \brief Grab an LSB header value
86 : *
87 : * \param[in] line Line read from LSB init script
88 : * \param[in,out] value If not set, will be set to XML-safe copy of value
89 : * \param[in] prefix Set value if line starts with this pattern
90 : *
91 : * \return TRUE if value was set, FALSE otherwise
92 : */
93 : static inline gboolean
94 0 : lsb_meta_helper_get_value(const char *line, gchar **value, const char *prefix)
95 : {
96 : /* @TODO Perhaps update later to use pcmk__xml_needs_escape(). Involves many
97 : * extra variables in the caller.
98 : */
99 0 : if ((*value == NULL) && pcmk__starts_with(line, prefix)) {
100 0 : *value = pcmk__xml_escape(line + strlen(prefix), pcmk__xml_escape_text);
101 0 : return TRUE;
102 : }
103 0 : return FALSE;
104 : }
105 :
106 : int
107 0 : services__get_lsb_metadata(const char *type, char **output)
108 : {
109 0 : char ra_pathname[PATH_MAX] = { 0, };
110 0 : FILE *fp = NULL;
111 0 : char buffer[1024] = { 0, };
112 0 : gchar *provides = NULL;
113 0 : gchar *required_start = NULL;
114 0 : gchar *required_stop = NULL;
115 0 : gchar *should_start = NULL;
116 0 : gchar *should_stop = NULL;
117 0 : gchar *default_start = NULL;
118 0 : gchar *default_stop = NULL;
119 0 : gchar *short_desc = NULL;
120 0 : gchar *long_desc = NULL;
121 0 : bool in_header = FALSE;
122 :
123 0 : if (type[0] == '/') {
124 0 : snprintf(ra_pathname, sizeof(ra_pathname), "%s", type);
125 : } else {
126 0 : snprintf(ra_pathname, sizeof(ra_pathname), "%s/%s",
127 : PCMK__LSB_INIT_DIR, type);
128 : }
129 :
130 0 : crm_trace("Looking into %s", ra_pathname);
131 0 : fp = fopen(ra_pathname, "r");
132 0 : if (fp == NULL) {
133 0 : return -errno;
134 : }
135 :
136 : /* Enter into the LSB-compliant comment block */
137 0 : while (fgets(buffer, sizeof(buffer), fp)) {
138 :
139 : // Ignore lines up to and including the block delimiter
140 0 : if (pcmk__starts_with(buffer, LSB_INITSCRIPT_INFOBEGIN_TAG)) {
141 0 : in_header = TRUE;
142 0 : continue;
143 : }
144 0 : if (!in_header) {
145 0 : continue;
146 : }
147 :
148 : /* Assume each of the following eight arguments contain one line */
149 0 : if (lsb_meta_helper_get_value(buffer, &provides, PROVIDES)) {
150 0 : continue;
151 : }
152 0 : if (lsb_meta_helper_get_value(buffer, &required_start,
153 : REQUIRED_START)) {
154 0 : continue;
155 : }
156 0 : if (lsb_meta_helper_get_value(buffer, &required_stop, REQUIRED_STOP)) {
157 0 : continue;
158 : }
159 0 : if (lsb_meta_helper_get_value(buffer, &should_start, SHOULD_START)) {
160 0 : continue;
161 : }
162 0 : if (lsb_meta_helper_get_value(buffer, &should_stop, SHOULD_STOP)) {
163 0 : continue;
164 : }
165 0 : if (lsb_meta_helper_get_value(buffer, &default_start, DEFAULT_START)) {
166 0 : continue;
167 : }
168 0 : if (lsb_meta_helper_get_value(buffer, &default_stop, DEFAULT_STOP)) {
169 0 : continue;
170 : }
171 0 : if (lsb_meta_helper_get_value(buffer, &short_desc, SHORT_DESC)) {
172 0 : continue;
173 : }
174 :
175 : /* Long description may cross multiple lines */
176 0 : if ((long_desc == NULL) // Haven't already found long description
177 0 : && pcmk__starts_with(buffer, DESCRIPTION)) {
178 0 : bool processed_line = TRUE;
179 0 : GString *desc = g_string_sized_new(2048);
180 :
181 : // Get remainder of description line itself
182 0 : g_string_append(desc, buffer + sizeof(DESCRIPTION) - 1);
183 :
184 : // Read any continuation lines of the description
185 0 : buffer[0] = '\0';
186 0 : while (fgets(buffer, sizeof(buffer), fp)) {
187 0 : if (pcmk__starts_with(buffer, "# ")
188 0 : || pcmk__starts_with(buffer, "#\t")) {
189 : /* '#' followed by a tab or more than one space indicates a
190 : * continuation of the long description.
191 : */
192 0 : g_string_append(desc, buffer + 1);
193 : } else {
194 : /* This line is not part of the long description,
195 : * so continue with normal processing.
196 : */
197 0 : processed_line = FALSE;
198 0 : break;
199 : }
200 : }
201 :
202 : // Make long description safe to use in XML
203 0 : long_desc = pcmk__xml_escape(desc->str, pcmk__xml_escape_text);
204 0 : g_string_free(desc, TRUE);
205 :
206 0 : if (processed_line) {
207 : // We grabbed the line into the long description
208 0 : continue;
209 : }
210 : }
211 :
212 : // Stop if we leave the header block
213 0 : if (pcmk__starts_with(buffer, LSB_INITSCRIPT_INFOEND_TAG)) {
214 0 : break;
215 : }
216 0 : if (buffer[0] != '#') {
217 0 : break;
218 : }
219 : }
220 0 : fclose(fp);
221 :
222 0 : *output = crm_strdup_printf(lsb_metadata_template, type,
223 : pcmk__s(long_desc, type),
224 : pcmk__s(short_desc, type),
225 : pcmk__s(provides, ""),
226 : pcmk__s(required_start, ""),
227 : pcmk__s(required_stop, ""),
228 : pcmk__s(should_start, ""),
229 : pcmk__s(should_stop, ""),
230 : pcmk__s(default_start, ""),
231 : pcmk__s(default_stop, ""));
232 :
233 0 : g_free(long_desc);
234 0 : g_free(short_desc);
235 0 : g_free(provides);
236 0 : g_free(required_start);
237 0 : g_free(required_stop);
238 0 : g_free(should_start);
239 0 : g_free(should_stop);
240 0 : g_free(default_start);
241 0 : g_free(default_stop);
242 0 : return pcmk_ok;
243 : }
244 :
245 : GList *
246 0 : services__list_lsb_agents(void)
247 : {
248 0 : return services_os_get_directory_list(PCMK__LSB_INIT_DIR, TRUE, TRUE);
249 : }
250 :
251 : bool
252 0 : services__lsb_agent_exists(const char *agent)
253 : {
254 0 : bool rc = FALSE;
255 : struct stat st;
256 0 : char *path = pcmk__full_path(agent, PCMK__LSB_INIT_DIR);
257 :
258 0 : rc = (stat(path, &st) == 0);
259 0 : free(path);
260 0 : return rc;
261 : }
262 :
263 : /*!
264 : * \internal
265 : * \brief Prepare an LSB action
266 : *
267 : * \param[in,out] op Action to prepare
268 : *
269 : * \return Standard Pacemaker return code
270 : */
271 : int
272 0 : services__lsb_prepare(svc_action_t *op)
273 : {
274 0 : op->opaque->exec = pcmk__full_path(op->agent, PCMK__LSB_INIT_DIR);
275 0 : op->opaque->args[0] = strdup(op->opaque->exec);
276 0 : op->opaque->args[1] = strdup(op->action);
277 0 : if ((op->opaque->args[0] == NULL) || (op->opaque->args[1] == NULL)) {
278 0 : return ENOMEM;
279 : }
280 0 : return pcmk_rc_ok;
281 : }
282 :
283 : /*!
284 : * \internal
285 : * \brief Map an LSB result to a standard OCF result
286 : *
287 : * \param[in] action Action that result is for
288 : * \param[in] exit_status LSB agent exit status
289 : *
290 : * \return Standard OCF result
291 : */
292 : enum ocf_exitcode
293 0 : services__lsb2ocf(const char *action, int exit_status)
294 : {
295 : // For non-status actions, LSB and OCF share error codes <= 7
296 0 : if (!pcmk__str_any_of(action, PCMK_ACTION_STATUS, PCMK_ACTION_MONITOR,
297 : NULL)) {
298 0 : if ((exit_status < 0) || (exit_status > PCMK_LSB_NOT_RUNNING)) {
299 0 : return PCMK_OCF_UNKNOWN_ERROR;
300 : }
301 0 : return (enum ocf_exitcode) exit_status;
302 : }
303 :
304 : // LSB status actions have their own codes
305 0 : switch (exit_status) {
306 0 : case PCMK_LSB_STATUS_OK:
307 0 : return PCMK_OCF_OK;
308 :
309 0 : case PCMK_LSB_STATUS_NOT_INSTALLED:
310 0 : return PCMK_OCF_NOT_INSTALLED;
311 :
312 0 : case PCMK_LSB_STATUS_INSUFFICIENT_PRIV:
313 0 : return PCMK_OCF_INSUFFICIENT_PRIV;
314 :
315 0 : case PCMK_LSB_STATUS_VAR_PID:
316 : case PCMK_LSB_STATUS_VAR_LOCK:
317 : case PCMK_LSB_STATUS_NOT_RUNNING:
318 0 : return PCMK_OCF_NOT_RUNNING;
319 :
320 0 : default:
321 0 : return PCMK_OCF_UNKNOWN_ERROR;
322 : }
323 : }
324 :
325 : // Deprecated functions kept only for backward API compatibility
326 : // LCOV_EXCL_START
327 :
328 : #include <crm/services_compat.h>
329 :
330 : svc_action_t *
331 : services_action_create(const char *name, const char *action,
332 : guint interval_ms, int timeout)
333 : {
334 : return resources_action_create(name, PCMK_RESOURCE_CLASS_LSB, NULL, name,
335 : action, interval_ms, timeout, NULL, 0);
336 : }
337 :
338 : GList *
339 : services_list(void)
340 : {
341 : return resources_list_agents(PCMK_RESOURCE_CLASS_LSB, NULL);
342 : }
343 :
344 : // LCOV_EXCL_STOP
345 : // End deprecated API
|