summaryrefslogtreecommitdiffstats
path: root/daemons/execd/remoted_pidone.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/execd/remoted_pidone.c')
-rw-r--r--daemons/execd/remoted_pidone.c298
1 files changed, 298 insertions, 0 deletions
diff --git a/daemons/execd/remoted_pidone.c b/daemons/execd/remoted_pidone.c
new file mode 100644
index 0000000..4f914eb
--- /dev/null
+++ b/daemons/execd/remoted_pidone.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2017-2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <crm/crm.h>
+#include "pacemaker-execd.h"
+
+static pid_t main_pid = 0;
+
+static void
+sigdone(void)
+{
+ exit(CRM_EX_OK);
+}
+
+static void
+sigreap(void)
+{
+ pid_t pid = 0;
+ int status;
+
+ do {
+ /*
+ * Opinions seem to differ as to what to put here:
+ * -1, any child process
+ * 0, any child process whose process group ID is equal to that of the calling process
+ */
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid == main_pid) {
+ /* Exit when pacemaker-remote exits and use the same return code */
+ if (WIFEXITED(status)) {
+ exit(WEXITSTATUS(status));
+ }
+ exit(CRM_EX_ERROR);
+ }
+ } while (pid > 0);
+}
+
+static struct {
+ int sig;
+ void (*handler)(void);
+} sigmap[] = {
+ { SIGCHLD, sigreap },
+ { SIGINT, sigdone },
+};
+
+/*!
+ * \internal
+ * \brief Check a line of text for a valid environment variable name
+ *
+ * \param[in] line Text to check
+ * \param[out] first First character of valid name if found, NULL otherwise
+ * \param[out] last Last character of valid name if found, NULL otherwise
+ *
+ * \return TRUE if valid name found, FALSE otherwise
+ * \note It's reasonable to impose limitations on environment variable names
+ * beyond what C or setenv() does: We only allow names that contain only
+ * [a-zA-Z0-9_] characters and do not start with a digit.
+ */
+static bool
+find_env_var_name(char *line, char **first, char **last)
+{
+ // Skip leading whitespace
+ *first = line;
+ while (isspace(**first)) {
+ ++*first;
+ }
+
+ if (isalpha(**first) || (**first == '_')) { // Valid first character
+ *last = *first;
+ while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) {
+ ++*last;
+ }
+ return TRUE;
+ }
+
+ *first = *last = NULL;
+ return FALSE;
+}
+
+static void
+load_env_vars(const char *filename)
+{
+ /* We haven't forked or initialized logging yet, so don't leave any file
+ * descriptors open, and don't log -- silently ignore errors.
+ */
+ FILE *fp = fopen(filename, "r");
+
+ if (fp != NULL) {
+ char line[LINE_MAX] = { '\0', };
+
+ while (fgets(line, LINE_MAX, fp) != NULL) {
+ char *name = NULL;
+ char *end = NULL;
+ char *value = NULL;
+ char *quote = NULL;
+
+ // Look for valid name immediately followed by equals sign
+ if (find_env_var_name(line, &name, &end) && (*++end == '=')) {
+
+ // Null-terminate name, and advance beyond equals sign
+ *end++ = '\0';
+
+ // Check whether value is quoted
+ if ((*end == '\'') || (*end == '"')) {
+ quote = end++;
+ }
+ value = end;
+
+ if (quote) {
+ /* Value is remaining characters up to next non-backslashed
+ * matching quote character.
+ */
+ while (((*end != *quote) || (*(end - 1) == '\\'))
+ && (*end != '\0')) {
+ end++;
+ }
+ if (*end == *quote) {
+ // Null-terminate value, and advance beyond close quote
+ *end++ = '\0';
+ } else {
+ // Matching closing quote wasn't found
+ value = NULL;
+ }
+
+ } else {
+ /* Value is remaining characters up to next non-backslashed
+ * whitespace.
+ */
+ while ((!isspace(*end) || (*(end - 1) == '\\'))
+ && (*end != '\0')) {
+ ++end;
+ }
+
+ if (end == (line + LINE_MAX - 1)) {
+ // Line was too long
+ value = NULL;
+ }
+ // Do NOT null-terminate value (yet)
+ }
+
+ /* We have a valid name and value, and end is now the character
+ * after the closing quote or the first whitespace after the
+ * unquoted value. Make sure the rest of the line is just
+ * whitespace or a comment.
+ */
+ if (value) {
+ char *value_end = end;
+
+ while (isspace(*end) && (*end != '\n')) {
+ ++end;
+ }
+ if ((*end == '\n') || (*end == '#')) {
+ if (quote == NULL) {
+ // Now we can null-terminate an unquoted value
+ *value_end = '\0';
+ }
+
+ // Don't overwrite (bundle options take precedence)
+ setenv(name, value, 0);
+
+ } else {
+ value = NULL;
+ }
+ }
+ }
+
+ if ((value == NULL) && (strchr(line, '\n') == NULL)) {
+ // Eat remainder of line beyond LINE_MAX
+ if (fscanf(fp, "%*[^\n]\n") == EOF) {
+ value = NULL; // Don't care, make compiler happy
+ }
+ }
+ }
+ fclose(fp);
+ }
+}
+
+void
+remoted_spawn_pidone(int argc, char **argv, char **envp)
+{
+ sigset_t set;
+
+ /* This environment variable exists for two purposes:
+ * - For testing, setting it to "full" enables full PID 1 behavior even
+ * when PID is not 1
+ * - Setting to "vars" enables just the loading of environment variables
+ * from /etc/pacemaker/pcmk-init.env, which could be useful for testing or
+ * containers with a custom PID 1 script that launches pacemaker-remoted.
+ */
+ const char *pid1 = (getpid() == 1)? "full" : getenv("PCMK_remote_pid1");
+
+ if (pid1 == NULL) {
+ return;
+ }
+
+ /* When a container is launched, it may be given specific environment
+ * variables, which for Pacemaker bundles are given in the bundle
+ * configuration. However, that does not allow for host-specific values.
+ * To allow for that, look for a special file containing a shell-like syntax
+ * of name/value pairs, and export those into the environment.
+ */
+ load_env_vars("/etc/pacemaker/pcmk-init.env");
+
+ if (strcmp(pid1, "full")) {
+ return;
+ }
+
+ /* Containers can be expected to have /var/log, but they may not have
+ * /var/log/pacemaker, so use a different default if no value has been
+ * explicitly configured in the container's environment.
+ */
+ if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
+ pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log");
+ }
+
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, 0);
+
+ main_pid = fork();
+ switch (main_pid) {
+ case 0:
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ setsid();
+ setpgid(0, 0);
+
+ // Child remains as pacemaker-remoted
+ return;
+ case -1:
+ perror("fork");
+ }
+
+ /* Parent becomes the reaper of zombie processes */
+ /* Safe to initialize logging now if needed */
+
+# ifdef HAVE_PROGNAME
+ /* Differentiate ourselves in the 'ps' output */
+ {
+ char *p;
+ int i, maxlen;
+ char *LastArgv = NULL;
+ const char *name = "pcmk-init";
+
+ for (i = 0; i < argc; i++) {
+ if (!i || (LastArgv + 1 == argv[i]))
+ LastArgv = argv[i] + strlen(argv[i]);
+ }
+
+ for (i = 0; envp[i] != NULL; i++) {
+ if ((LastArgv + 1) == envp[i]) {
+ LastArgv = envp[i] + strlen(envp[i]);
+ }
+ }
+
+ maxlen = (LastArgv - argv[0]) - 2;
+
+ i = strlen(name);
+
+ /* We can overwrite individual argv[] arguments */
+ snprintf(argv[0], maxlen, "%s", name);
+
+ /* Now zero out everything else */
+ p = &argv[0][i];
+ while (p < LastArgv) {
+ *p++ = '\0';
+ }
+ argv[1] = NULL;
+ }
+# endif // HAVE_PROGNAME
+
+ while (1) {
+ int sig;
+ size_t i;
+
+ sigwait(&set, &sig);
+ for (i = 0; i < PCMK__NELEM(sigmap); i++) {
+ if (sigmap[i].sig == sig) {
+ sigmap[i].handler();
+ break;
+ }
+ }
+ }
+}