1 /*
2 * Copyright 2017-2025 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 <ctype.h>
14 #include <stdlib.h>
15 #include <signal.h>
16 #include <unistd.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19
20 #include <crm/crm.h>
21 #include "pacemaker-execd.h"
22
23 static pid_t main_pid = 0;
24
25 static void
26 sigdone(void)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
27 {
28 crm_exit(CRM_EX_OK);
29 }
30
31 static void
32 sigreap(void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
33 {
34 pid_t pid = 0;
35 int status;
36
37 do {
38 /*
39 * Opinions seem to differ as to what to put here:
40 * -1, any child process
41 * 0, any child process whose process group ID is equal to that of the calling process
42 */
43 pid = waitpid(-1, &status, WNOHANG);
44 if (pid == main_pid) {
45 /* Exit when pacemaker-remote exits and use the same return code */
46 if (WIFEXITED(status)) {
47 crm_exit(WEXITSTATUS(status));
48 }
49 crm_exit(CRM_EX_ERROR);
50 }
51 } while (pid > 0);
52 }
53
54 static struct {
55 int sig;
56 void (*handler)(void);
57 } sigmap[] = {
58 { SIGCHLD, sigreap },
59 { SIGINT, sigdone },
60 };
61
62 /*!
63 * \internal
64 * \brief Check a line of text for a valid environment variable name
65 *
66 * \param[in] line Text to check
67 * \param[out] first First character of valid name if found, NULL otherwise
68 * \param[out] last Last character of valid name if found, NULL otherwise
69 *
70 * \return TRUE if valid name found, FALSE otherwise
71 * \note It's reasonable to impose limitations on environment variable names
72 * beyond what C or setenv() does: We only allow names that contain only
73 * [a-zA-Z0-9_] characters and do not start with a digit.
74 */
75 static bool
76 find_env_var_name(char *line, char **first, char **last)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
77 {
78 // Skip leading whitespace
79 *first = line;
80 while (isspace(**first)) {
81 ++*first;
82 }
83
84 if (isalpha(**first) || (**first == '_')) { // Valid first character
85 *last = *first;
86 while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) {
87 ++*last;
88 }
89 return TRUE;
90 }
91
92 *first = *last = NULL;
93 return FALSE;
94 }
95
96 static void
97 load_env_vars(const char *filename)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
98 {
99 /* We haven't forked or initialized logging yet, so don't leave any file
100 * descriptors open, and don't log -- silently ignore errors.
101 */
102 FILE *fp = fopen(filename, "r");
103
104 if (fp != NULL) {
105 char line[LINE_MAX] = { '\0', };
106
107 while (fgets(line, LINE_MAX, fp) != NULL) {
108 char *name = NULL;
109 char *end = NULL;
110 char *value = NULL;
111 char *quote = NULL;
112
113 // Look for valid name immediately followed by equals sign
114 if (find_env_var_name(line, &name, &end) && (*++end == '=')) {
115
116 // Null-terminate name, and advance beyond equals sign
117 *end++ = '\0';
118
119 // Check whether value is quoted
120 if ((*end == '\'') || (*end == '"')) {
121 quote = end++;
122 }
123 value = end;
124
125 if (quote) {
126 /* Value is remaining characters up to next non-backslashed
127 * matching quote character.
128 */
129 while (((*end != *quote) || (*(end - 1) == '\\'))
130 && (*end != '\0')) {
131 end++;
132 }
133 if (*end == *quote) {
134 // Null-terminate value, and advance beyond close quote
135 *end++ = '\0';
136 } else {
137 // Matching closing quote wasn't found
138 value = NULL;
139 }
140
141 } else {
142 /* Value is remaining characters up to next non-backslashed
143 * whitespace.
144 */
145 while ((!isspace(*end) || (*(end - 1) == '\\'))
146 && (*end != '\0')) {
147 ++end;
148 }
149
150 if (end == (line + LINE_MAX - 1)) {
151 // Line was too long
152 value = NULL;
153 }
154 // Do NOT null-terminate value (yet)
155 }
156
157 /* We have a valid name and value, and end is now the character
158 * after the closing quote or the first whitespace after the
159 * unquoted value. Make sure the rest of the line is just
160 * whitespace or a comment.
161 */
162 if (value) {
163 char *value_end = end;
164
165 while (isspace(*end) && (*end != '\n')) {
166 ++end;
167 }
168 if ((*end == '\n') || (*end == '#')) {
169 if (quote == NULL) {
170 // Now we can null-terminate an unquoted value
171 *value_end = '\0';
172 }
173
174 // Don't overwrite (bundle options take precedence)
175 // coverity[tainted_string] This can't easily be changed right now
176 setenv(name, value, 0);
177
178 } else {
179 value = NULL;
180 }
181 }
182 }
183
184 if ((value == NULL) && (strchr(line, '\n') == NULL)) {
185 // Eat remainder of line beyond LINE_MAX
186 if (fscanf(fp, "%*[^\n]\n") == EOF) {
187 value = NULL; // Don't care, make compiler happy
188 }
189 }
190 }
191 fclose(fp);
192 }
193 }
194
195 void
196 remoted_spawn_pidone(int argc, char **argv, char **envp)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
197 {
198 sigset_t set;
199
200 /* This environment variable exists for two purposes:
201 * - For testing, setting it to "full" enables full PID 1 behavior even
202 * when PID is not 1
203 * - Setting to "vars" enables just the loading of environment variables
204 * from /etc/pacemaker/pcmk-init.env, which could be useful for testing or
205 * containers with a custom PID 1 script that launches the remote
206 * executor.
207 */
208 const char *pid1 = PCMK_VALUE_DEFAULT;
209
210 if (getpid() != 1) {
211 pid1 = pcmk__env_option(PCMK__ENV_REMOTE_PID1);
212 if (!pcmk__str_any_of(pid1, "full", "vars", NULL)) {
213 // Default, unset, or invalid
214 return;
215 }
216 }
217
218 /* When a container is launched, it may be given specific environment
219 * variables, which for Pacemaker bundles are given in the bundle
220 * configuration. However, that does not allow for host-specific values.
221 * To allow for that, look for a special file containing a shell-like syntax
222 * of name/value pairs, and export those into the environment.
223 */
224 load_env_vars("/etc/pacemaker/pcmk-init.env");
225
226 if (strcmp(pid1, "vars") == 0) {
227 return;
228 }
229
230 /* Containers can be expected to have /var/log, but they may not have
231 * /var/log/pacemaker, so use a different default if no value has been
232 * explicitly configured in the container's environment.
233 */
234 if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
235 pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log", true);
236 }
237
238 sigfillset(&set);
239 sigprocmask(SIG_BLOCK, &set, 0);
240
241 main_pid = fork();
242 switch (main_pid) {
243 case 0:
244 sigprocmask(SIG_UNBLOCK, &set, NULL);
245 setsid();
246 setpgid(0, 0);
247
248 // Child remains as pacemaker-remoted
249 return;
250 case -1:
251 crm_err("fork failed: %s", pcmk_rc_str(errno));
252 }
253
254 /* Parent becomes the reaper of zombie processes */
255 /* Safe to initialize logging now if needed */
256
257 # ifdef HAVE_PROGNAME
258 /* Differentiate ourselves in the 'ps' output */
259 {
260 char *p;
261 int i, maxlen;
262 char *LastArgv = NULL;
263 const char *name = "pcmk-init";
264
265 for (i = 0; i < argc; i++) {
266 if (!i || (LastArgv + 1 == argv[i]))
267 LastArgv = argv[i] + strlen(argv[i]);
268 }
269
270 for (i = 0; envp[i] != NULL; i++) {
271 if ((LastArgv + 1) == envp[i]) {
272 LastArgv = envp[i] + strlen(envp[i]);
273 }
274 }
275
276 maxlen = (LastArgv - argv[0]) - 2;
277
278 i = strlen(name);
279
280 /* We can overwrite individual argv[] arguments */
281 snprintf(argv[0], maxlen, "%s", name);
282
283 /* Now zero out everything else */
284 p = &argv[0][i];
285 while (p < LastArgv) {
286 *p++ = '\0';
287 }
288 argv[1] = NULL;
289 }
290 # endif // HAVE_PROGNAME
291
292 while (1) {
293 int sig;
294 size_t i;
295
296 sigwait(&set, &sig);
297 for (i = 0; i < PCMK__NELEM(sigmap); i++) {
298 if (sigmap[i].sig == sig) {
299 sigmap[i].handler();
300 break;
301 }
302 }
303 }
304 }