root/daemons/execd/remoted_pidone.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. sigdone
  2. sigreap
  3. find_env_var_name
  4. load_env_vars
  5. remoted_spawn_pidone

   1 /*
   2  * Copyright 2017-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 <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][next][first][last][top][bottom][index][help] */
  27 {
  28     crm_exit(CRM_EX_OK);
  29 }
  30 
  31 static void
  32 sigreap(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  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][next][first][last][top][bottom][index][help] */
  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][next][first][last][top][bottom][index][help] */
  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                         setenv(name, value, 0);
 176 
 177                     } else {
 178                         value = NULL;
 179                     }
 180                 }
 181             }
 182 
 183             if ((value == NULL) && (strchr(line, '\n') == NULL)) {
 184                 // Eat remainder of line beyond LINE_MAX
 185                 if (fscanf(fp, "%*[^\n]\n") == EOF) {
 186                     value = NULL; // Don't care, make compiler happy
 187                 }
 188             }
 189         }
 190         fclose(fp);
 191     }
 192 }
 193 
 194 void
 195 remoted_spawn_pidone(int argc, char **argv, char **envp)
     /* [previous][next][first][last][top][bottom][index][help] */
 196 {
 197     sigset_t set;
 198 
 199     /* This environment variable exists for two purposes:
 200      * - For testing, setting it to "full" enables full PID 1 behavior even
 201      *   when PID is not 1
 202      * - Setting to "vars" enables just the loading of environment variables
 203      *   from /etc/pacemaker/pcmk-init.env, which could be useful for testing or
 204      *   containers with a custom PID 1 script that launches pacemaker-remoted.
 205      */
 206     const char *pid1 = PCMK_VALUE_DEFAULT;
 207 
 208     if (getpid() != 1) {
 209         pid1 = pcmk__env_option(PCMK__ENV_REMOTE_PID1);
 210         if (!pcmk__str_any_of(pid1, "full", "vars", NULL)) {
 211             // Default, unset, or invalid
 212             return;
 213         }
 214     }
 215 
 216     /* When a container is launched, it may be given specific environment
 217      * variables, which for Pacemaker bundles are given in the bundle
 218      * configuration. However, that does not allow for host-specific values.
 219      * To allow for that, look for a special file containing a shell-like syntax
 220      * of name/value pairs, and export those into the environment.
 221      */
 222     load_env_vars("/etc/pacemaker/pcmk-init.env");
 223 
 224     if (strcmp(pid1, "vars") == 0) {
 225         return;
 226     }
 227 
 228     /* Containers can be expected to have /var/log, but they may not have
 229      * /var/log/pacemaker, so use a different default if no value has been
 230      * explicitly configured in the container's environment.
 231      */
 232     if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
 233         pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log", true);
 234     }
 235 
 236     sigfillset(&set);
 237     sigprocmask(SIG_BLOCK, &set, 0);
 238 
 239     main_pid = fork();
 240     switch (main_pid) {
 241         case 0:
 242             sigprocmask(SIG_UNBLOCK, &set, NULL);
 243             setsid();
 244             setpgid(0, 0);
 245 
 246             // Child remains as pacemaker-remoted
 247             return;
 248         case -1:
 249             crm_err("fork failed: %s", pcmk_rc_str(errno));
 250     }
 251 
 252     /* Parent becomes the reaper of zombie processes */
 253     /* Safe to initialize logging now if needed */
 254 
 255 #  ifdef HAVE_PROGNAME
 256     /* Differentiate ourselves in the 'ps' output */
 257     {
 258         char *p;
 259         int i, maxlen;
 260         char *LastArgv = NULL;
 261         const char *name = "pcmk-init";
 262 
 263         for (i = 0; i < argc; i++) {
 264             if (!i || (LastArgv + 1 == argv[i]))
 265                 LastArgv = argv[i] + strlen(argv[i]);
 266         }
 267 
 268         for (i = 0; envp[i] != NULL; i++) {
 269             if ((LastArgv + 1) == envp[i]) {
 270                 LastArgv = envp[i] + strlen(envp[i]);
 271             }
 272         }
 273 
 274         maxlen = (LastArgv - argv[0]) - 2;
 275 
 276         i = strlen(name);
 277 
 278         /* We can overwrite individual argv[] arguments */
 279         snprintf(argv[0], maxlen, "%s", name);
 280 
 281         /* Now zero out everything else */
 282         p = &argv[0][i];
 283         while (p < LastArgv) {
 284             *p++ = '\0';
 285         }
 286         argv[1] = NULL;
 287     }
 288 #  endif // HAVE_PROGNAME
 289 
 290     while (1) {
 291         int sig;
 292         size_t i;
 293 
 294         sigwait(&set, &sig);
 295         for (i = 0; i < PCMK__NELEM(sigmap); i++) {
 296             if (sigmap[i].sig == sig) {
 297                 sigmap[i].handler();
 298                 break;
 299             }
 300         }
 301     }
 302 }

/* [previous][next][first][last][top][bottom][index][help] */