]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/process-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / process-util.c
index 54b644ad56b5ea1efa74c6e0d99f193b0efe32bd..b3d96cdb0f6a72767342742552b03a19a83a80b5 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <syslog.h>
 #include <unistd.h>
-#ifdef HAVE_VALGRIND_VALGRIND_H
+#if HAVE_VALGRIND_VALGRIND_H
 #include <valgrind/valgrind.h>
 #endif
 
@@ -103,7 +105,7 @@ int get_process_comm(pid_t pid, char **name) {
 int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
         _cleanup_fclose_ FILE *f = NULL;
         bool space = false;
-        char *r = NULL, *k;
+        char *k, *ans = NULL;
         const char *p;
         int c;
 
@@ -117,7 +119,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
          * command line that resolves to the empty string will return the "comm" name of the process instead.
          *
          * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
-         * comm_fallback is false). */
+         * comm_fallback is false). Returns 0 and sets *line otherwise. */
 
         p = procfs_file_alloca(pid, "cmdline");
 
@@ -131,11 +133,11 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
         if (max_length == 1) {
 
                 /* If there's only room for one byte, return the empty string */
-                r = new0(char, 1);
-                if (!r)
+                ans = new0(char, 1);
+                if (!ans)
                         return -ENOMEM;
 
-                *line = r;
+                *line = ans;
                 return 0;
 
         } else if (max_length == 0) {
@@ -143,36 +145,36 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
 
                 while ((c = getc(f)) != EOF) {
 
-                        if (!GREEDY_REALLOC(r, allocated, len+3)) {
-                                free(r);
+                        if (!GREEDY_REALLOC(ans, allocated, len+3)) {
+                                free(ans);
                                 return -ENOMEM;
                         }
 
                         if (isprint(c)) {
                                 if (space) {
-                                        r[len++] = ' ';
+                                        ans[len++] = ' ';
                                         space = false;
                                 }
 
-                                r[len++] = c;
+                                ans[len++] = c;
                         } else if (len > 0)
                                 space = true;
                }
 
                 if (len > 0)
-                        r[len] = 0;
+                        ans[len] = '\0';
                 else
-                        r = mfree(r);
+                        ans = mfree(ans);
 
         } else {
                 bool dotdotdot = false;
                 size_t left;
 
-                r = new(char, max_length);
-                if (!r)
+                ans = new(char, max_length);
+                if (!ans)
                         return -ENOMEM;
 
-                k = r;
+                k = ans;
                 left = max_length;
                 while ((c = getc(f)) != EOF) {
 
@@ -196,20 +198,20 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
 
                                 *(k++) = (char) c;
                                 left--;
-                        } else if (k > r)
+                        } else if (k > ans)
                                 space = true;
                 }
 
                 if (dotdotdot) {
                         if (max_length <= 4) {
-                                k = r;
+                                k = ans;
                                 left = max_length;
                         } else {
-                                k = r + max_length - 4;
+                                k = ans + max_length - 4;
                                 left = 4;
 
                                 /* Eat up final spaces */
-                                while (k > r && isspace(k[-1])) {
+                                while (k > ans && isspace(k[-1])) {
                                         k--;
                                         left++;
                                 }
@@ -222,11 +224,11 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
         }
 
         /* Kernel threads have no argv[] */
-        if (isempty(r)) {
+        if (isempty(ans)) {
                 _cleanup_free_ char *t = NULL;
                 int h;
 
-                free(r);
+                free(ans);
 
                 if (!comm_fallback)
                         return -ENOENT;
@@ -236,22 +238,22 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                         return h;
 
                 if (max_length == 0)
-                        r = strjoin("[", t, "]", NULL);
+                        ans = strjoin("[", t, "]");
                 else {
                         size_t l;
 
                         l = strlen(t);
 
                         if (l + 3 <= max_length)
-                                r = strjoin("[", t, "]", NULL);
+                                ans = strjoin("[", t, "]");
                         else if (max_length <= 6) {
 
-                                r = new(char, max_length);
-                                if (!r)
+                                ans = new(char, max_length);
+                                if (!ans)
                                         return -ENOMEM;
 
-                                memcpy(r, "[...]", max_length-1);
-                                r[max_length-1] = 0;
+                                memcpy(ans, "[...]", max_length-1);
+                                ans[max_length-1] = 0;
                         } else {
                                 char *e;
 
@@ -263,38 +265,115 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                                         e--;
                                 *e = 0;
 
-                                r = strjoin("[", t, "...]", NULL);
+                                ans = strjoin("[", t, "...]");
                         }
                 }
-                if (!r)
+                if (!ans)
                         return -ENOMEM;
         }
 
-        *line = r;
+        *line = ans;
         return 0;
 }
 
-void rename_process(const char name[8]) {
-        assert(name);
+int rename_process(const char name[]) {
+        static size_t mm_size = 0;
+        static char *mm = NULL;
+        bool truncated = false;
+        size_t l;
+
+        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
+         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
+         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
+         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
+         * truncated.
+         *
+         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
+
+        if (isempty(name))
+                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
 
-        /* This is a like a poor man's setproctitle(). It changes the
-         * comm field, argv[0], and also the glibc's internally used
-         * name of the process. For the first one a limit of 16 chars
-         * applies, to the second one usually one of 10 (i.e. length
-         * of "/sbin/init"), to the third one one of 7 (i.e. length of
-         * "systemd"). If you pass a longer string it will be
-         * truncated */
+        l = strlen(name);
 
+        /* First step, change the comm field. */
         (void) prctl(PR_SET_NAME, name);
+        if (l > 15) /* Linux process names can be 15 chars at max */
+                truncated = true;
+
+        /* Second step, change glibc's ID of the process name. */
+        if (program_invocation_name) {
+                size_t k;
+
+                k = strlen(program_invocation_name);
+                strncpy(program_invocation_name, name, k);
+                if (l > k)
+                        truncated = true;
+        }
+
+        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
+         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
+         * the end. This is the best option for changing /proc/self/cmdline. */
+
+        /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
+         * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
+         * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
+         * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
+         * mmap() is not. */
+        if (geteuid() != 0)
+                log_debug("Skipping PR_SET_MM, as we don't have privileges.");
+        else if (mm_size < l+1) {
+                size_t nn_size;
+                char *nn;
+
+                nn_size = PAGE_ALIGN(l+1);
+                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+                if (nn == MAP_FAILED) {
+                        log_debug_errno(errno, "mmap() failed: %m");
+                        goto use_saved_argv;
+                }
+
+                strncpy(nn, name, nn_size);
 
-        if (program_invocation_name)
-                strncpy(program_invocation_name, name, strlen(program_invocation_name));
+                /* Now, let's tell the kernel about this new memory */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
+                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, proceeding without: %m");
+                        (void) munmap(nn, nn_size);
+                        goto use_saved_argv;
+                }
+
+                /* And update the end pointer to the new end, too. If this fails, we don't really know what to do, it's
+                 * pretty unlikely that we can rollback, hence we'll just accept the failure, and continue. */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
+                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+
+                if (mm)
+                        (void) munmap(mm, mm_size);
+
+                mm = nn;
+                mm_size = nn_size;
+        } else {
+                strncpy(mm, name, mm_size);
+
+                /* Update the end pointer, continuing regardless of any failure. */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
+                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+        }
+
+use_saved_argv:
+        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
+         * it still looks here */
 
         if (saved_argc > 0) {
                 int i;
 
-                if (saved_argv[0])
-                        strncpy(saved_argv[0], name, strlen(saved_argv[0]));
+                if (saved_argv[0]) {
+                        size_t k;
+
+                        k = strlen(saved_argv[0]);
+                        strncpy(saved_argv[0], name, k);
+                        if (l > k)
+                                truncated = true;
+                }
 
                 for (i = 1; i < saved_argc; i++) {
                         if (!saved_argv[i])
@@ -303,6 +382,8 @@ void rename_process(const char name[8]) {
                         memzero(saved_argv[i], strlen(saved_argv[i]));
                 }
         }
+
+        return !truncated;
 }
 
 int is_kernel_thread(pid_t pid) {
@@ -312,7 +393,7 @@ int is_kernel_thread(pid_t pid) {
         bool eof;
         FILE *f;
 
-        if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */
+        if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
                 return 0;
 
         assert(pid > 1);
@@ -395,6 +476,9 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
         assert(field);
         assert(uid);
 
+        if (pid < 0)
+                return -EINVAL;
+
         p = procfs_file_alloca(pid, "status");
         f = fopen(p, "re");
         if (!f) {
@@ -422,10 +506,22 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
 }
 
 int get_process_uid(pid_t pid, uid_t *uid) {
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *uid = getuid();
+                return 0;
+        }
+
         return get_process_id(pid, "Uid:", uid);
 }
 
 int get_process_gid(pid_t pid, gid_t *gid) {
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *gid = getgid();
+                return 0;
+        }
+
         assert_cc(sizeof(uid_t) == sizeof(gid_t));
         return get_process_id(pid, "Gid:", gid);
 }
@@ -501,7 +597,7 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) {
         assert(pid >= 0);
         assert(_ppid);
 
-        if (pid == 0) {
+        if (pid == 0 || pid == getpid_cached()) {
                 *_ppid = getppid();
                 return 0;
         }
@@ -593,8 +689,7 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_cod
                         log_debug("%s succeeded.", name);
 
                 return status.si_status;
-        } else if (status.si_code == CLD_KILLED ||
-                   status.si_code == CLD_DUMPED) {
+        } else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED)) {
 
                 log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
                 return -EPROTO;
@@ -627,7 +722,7 @@ int kill_and_sigcont(pid_t pid, int sig) {
 
         /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't
          * affected by a process being suspended anyway. */
-        if (r >= 0 && !IN_SET(SIGCONT, SIGKILL))
+        if (r >= 0 && !IN_SET(sig, SIGCONT, SIGKILL))
                 (void) kill(pid, SIGCONT);
 
         return r;
@@ -675,7 +770,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) {
                 }
                 line[i] = 0;
 
-                if (memcmp(line, field, l) == 0 && line[l] == '=') {
+                if (strneq(line, field, l) && line[l] == '=') {
                         value = strdup(line + l + 1);
                         if (!value)
                                 return -ENOMEM;
@@ -699,6 +794,9 @@ bool pid_is_unwaited(pid_t pid) {
         if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */
                 return true;
 
+        if (pid == getpid_cached())
+                return true;
+
         if (kill(pid, 0) >= 0)
                 return true;
 
@@ -716,8 +814,11 @@ bool pid_is_alive(pid_t pid) {
         if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */
                 return true;
 
+        if (pid == getpid_cached())
+                return true;
+
         r = get_process_state(pid);
-        if (r == -ESRCH || r == 'Z')
+        if (IN_SET(r, -ESRCH, 'Z'))
                 return false;
 
         return true;
@@ -727,18 +828,21 @@ int pid_from_same_root_fs(pid_t pid) {
         const char *root;
 
         if (pid < 0)
-                return 0;
+                return false;
+
+        if (pid == 0 || pid == getpid_cached())
+                return true;
 
         root = procfs_file_alloca(pid, "root");
 
-        return files_same(root, "/proc/1/root");
+        return files_same(root, "/proc/1/root", 0);
 }
 
 bool is_main_thread(void) {
         static thread_local int cached = 0;
 
         if (_unlikely_(cached == 0))
-                cached = getpid() == gettid() ? 1 : -1;
+                cached = getpid_cached() == gettid() ? 1 : -1;
 
         return cached > 0;
 }
@@ -800,9 +904,50 @@ const char* personality_to_string(unsigned long p) {
         return architecture_to_string(architecture);
 }
 
+int safe_personality(unsigned long p) {
+        int ret;
+
+        /* So here's the deal, personality() is weirdly defined by glibc. In some cases it returns a failure via errno,
+         * and in others as negative return value containing an errno-like value. Let's work around this: this is a
+         * wrapper that uses errno if it is set, and uses the return value otherwise. And then it sets both errno and
+         * the return value indicating the same issue, so that we are definitely on the safe side.
+         *
+         * See https://github.com/systemd/systemd/issues/6737 */
+
+        errno = 0;
+        ret = personality(p);
+        if (ret < 0) {
+                if (errno != 0)
+                        return -errno;
+
+                errno = -ret;
+        }
+
+        return ret;
+}
+
+int opinionated_personality(unsigned long *ret) {
+        int current;
+
+        /* Returns the current personality, or PERSONALITY_INVALID if we can't determine it. This function is a bit
+         * opinionated though, and ignores all the finer-grained bits and exotic personalities, only distinguishing the
+         * two most relevant personalities: PER_LINUX and PER_LINUX32. */
+
+        current = safe_personality(PERSONALITY_INVALID);
+        if (current < 0)
+                return current;
+
+        if (((unsigned long) current & 0xffff) == PER_LINUX32)
+                *ret = PER_LINUX32;
+        else
+                *ret = PER_LINUX;
+
+        return 0;
+}
+
 void valgrind_summary_hack(void) {
-#ifdef HAVE_VALGRIND_VALGRIND_H
-        if (getpid() == 1 && RUNNING_ON_VALGRIND) {
+#if HAVE_VALGRIND_VALGRIND_H
+        if (getpid_cached() == 1 && RUNNING_ON_VALGRIND) {
                 pid_t pid;
                 pid = raw_clone(SIGCHLD);
                 if (pid < 0)
@@ -829,6 +974,85 @@ int pid_compare_func(const void *a, const void *b) {
         return 0;
 }
 
+int ioprio_parse_priority(const char *s, int *ret) {
+        int i, r;
+
+        assert(s);
+        assert(ret);
+
+        r = safe_atoi(s, &i);
+        if (r < 0)
+                return r;
+
+        if (!ioprio_priority_is_valid(i))
+                return -EINVAL;
+
+        *ret = i;
+        return 0;
+}
+
+/* The cached PID, possible values:
+ *
+ *     == UNSET [0]  → cache not initialized yet
+ *     == BUSY [-1]  → some thread is initializing it at the moment
+ *     any other     → the cached PID
+ */
+
+#define CACHED_PID_UNSET ((pid_t) 0)
+#define CACHED_PID_BUSY ((pid_t) -1)
+
+static pid_t cached_pid = CACHED_PID_UNSET;
+
+static void reset_cached_pid(void) {
+        /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */
+        cached_pid = CACHED_PID_UNSET;
+}
+
+/* We use glibc __register_atfork() + __dso_handle directly here, as they are not included in the glibc
+ * headers. __register_atfork() is mostly equivalent to pthread_atfork(), but doesn't require us to link against
+ * libpthread, as it is part of glibc anyway. */
+extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void * __dso_handle);
+extern void* __dso_handle __attribute__ ((__weak__));
+
+pid_t getpid_cached(void) {
+        pid_t current_value;
+
+        /* getpid_cached() is much like getpid(), but caches the value in local memory, to avoid having to invoke a
+         * system call each time. This restores glibc behaviour from before 2.24, when getpid() was unconditionally
+         * cached. Starting with 2.24 getpid() started to become prohibitively expensive when used for detecting when
+         * objects were used across fork()s. With this caching the old behaviour is somewhat restored.
+         *
+         * https://bugzilla.redhat.com/show_bug.cgi?id=1443976
+         * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c579f48edba88380635ab98cb612030e3ed8691e
+         */
+
+        current_value = __sync_val_compare_and_swap(&cached_pid, CACHED_PID_UNSET, CACHED_PID_BUSY);
+
+        switch (current_value) {
+
+        case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */
+                pid_t new_pid;
+
+                new_pid = getpid();
+
+                if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) {
+                        /* OOM? Let's try again later */
+                        cached_pid = CACHED_PID_UNSET;
+                        return new_pid;
+                }
+
+                cached_pid = new_pid;
+                return new_pid;
+        }
+
+        case CACHED_PID_BUSY: /* Somebody else is currently initializing */
+                return getpid();
+
+        default: /* Properly initialized */
+                return current_value;
+        }
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",