chiark / gitweb /
Prep v234: Apply missing upstream fixes in src/basic (1/6)
[elogind.git] / src / basic / process-util.c
index 69a41d90b53da5786f07e48afce294c636c08030..da825a7c3c5f6d2916980430d71f2e6d8bae870e 100644 (file)
@@ -38,7 +38,7 @@
 #endif
 
 #include "alloc-util.h"
-#include "architecture.h"
+//#include "architecture.h"
 #include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -101,13 +101,23 @@ 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;
-        char *r = NULL, *k;
+        bool space = false;
+        char *k, *ans = NULL;
         const char *p;
         int c;
 
         assert(line);
         assert(pid >= 0);
 
+        /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
+         * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
+         * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
+         * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
+         * 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). Returns 0 and sets *line otherwise. */
+
         p = procfs_file_alloca(pid, "cmdline");
 
         f = fopen(p, "re");
@@ -117,67 +127,105 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                 return -errno;
         }
 
-        if (max_length == 0) {
+        if (max_length == 1) {
+
+                /* If there's only room for one byte, return the empty string */
+                ans = new0(char, 1);
+                if (!ans)
+                        return -ENOMEM;
+
+                *line = ans;
+                return 0;
+
+        } else if (max_length == 0) {
                 size_t len = 0, allocated = 0;
 
                 while ((c = getc(f)) != EOF) {
 
-                        if (!GREEDY_REALLOC(r, allocated, len+2)) {
-                                free(r);
+                        if (!GREEDY_REALLOC(ans, allocated, len+3)) {
+                                free(ans);
                                 return -ENOMEM;
                         }
 
-                        r[len++] = isprint(c) ? c : ' ';
-                }
+                        if (isprint(c)) {
+                                if (space) {
+                                        ans[len++] = ' ';
+                                        space = false;
+                                }
+
+                                ans[len++] = c;
+                        } else if (len > 0)
+                                space = true;
+               }
 
                 if (len > 0)
-                        r[len-1] = 0;
+                        ans[len] = '\0';
+                else
+                        ans = mfree(ans);
 
         } else {
-                bool space = false;
+                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) {
 
                         if (isprint(c)) {
+
                                 if (space) {
-                                        if (left <= 4)
+                                        if (left <= 2) {
+                                                dotdotdot = true;
                                                 break;
+                                        }
 
                                         *(k++) = ' ';
                                         left--;
                                         space = false;
                                 }
 
-                                if (left <= 4)
+                                if (left <= 1) {
+                                        dotdotdot = true;
                                         break;
+                                }
 
                                 *(k++) = (char) c;
                                 left--;
-                        }  else
+                        } else if (k > ans)
                                 space = true;
                 }
 
-                if (left <= 4) {
-                        size_t n = MIN(left-1, 3U);
-                        memcpy(k, "...", n);
-                        k[n] = 0;
+                if (dotdotdot) {
+                        if (max_length <= 4) {
+                                k = ans;
+                                left = max_length;
+                        } else {
+                                k = ans + max_length - 4;
+                                left = 4;
+
+                                /* Eat up final spaces */
+                                while (k > ans && isspace(k[-1])) {
+                                        k--;
+                                        left++;
+                                }
+                        }
+
+                        strncpy(k, "...", left-1);
+                        k[left-1] = 0;
                 } else
                         *k = 0;
         }
 
         /* 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;
@@ -186,37 +234,140 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                 if (h < 0)
                         return h;
 
-                r = strjoin("[", t, "]", NULL);
-                if (!r)
+                if (max_length == 0)
+                        ans = strjoin("[", t, "]");
+                else {
+                        size_t l;
+
+                        l = strlen(t);
+
+                        if (l + 3 <= max_length)
+                                ans = strjoin("[", t, "]");
+                        else if (max_length <= 6) {
+
+                                ans = new(char, max_length);
+                                if (!ans)
+                                        return -ENOMEM;
+
+                                memcpy(ans, "[...]", max_length-1);
+                                ans[max_length-1] = 0;
+                        } else {
+                                char *e;
+
+                                t[max_length - 6] = 0;
+
+                                /* Chop off final spaces */
+                                e = strchr(t, 0);
+                                while (e > t && isspace(e[-1]))
+                                        e--;
+                                *e = 0;
+
+                                ans = strjoin("[", t, "...]");
+                        }
+                }
+                if (!ans)
                         return -ENOMEM;
         }
 
-        *line = r;
+        *line = ans;
         return 0;
 }
 
 #if 0 /// UNNEEDED by elogind
-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);
 
-        prctl(PR_SET_NAME, 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;
 
-        if (program_invocation_name)
-                strncpy(program_invocation_name, name, strlen(program_invocation_name));
+        /* 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. */
+        if (mm_size < l+1) {
+                size_t nn_size;
+                char *nn;
+
+                /* Let's not bother with this if we don't have euid == 0. Strictly speaking if people do weird stuff
+                 * with capabilities this could work even for euid != 0, but our own code generally doesn't do that,
+                 * hence let's use this as quick bypass check, to avoid calling mmap() if PR_SET_MM_ARG_START 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_ARG_START, as we don't have privileges.");
+                        goto use_saved_argv;
+                }
+
+                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);
+
+                /* 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);
+
+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])
@@ -225,6 +376,8 @@ void rename_process(const char name[8]) {
                         memzero(saved_argv[i], strlen(saved_argv[i]));
                 }
         }
+
+        return !truncated;
 }
 #endif // 0
 
@@ -321,9 +474,6 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
         assert(field);
         assert(uid);
 
-        if (pid == 0)
-                return getuid();
-
         p = procfs_file_alloca(pid, "status");
         f = fopen(p, "re");
         if (!f) {
@@ -413,7 +563,7 @@ int get_process_environ(pid_t pid, char **env) {
                 if (!outcome)
                         return -ENOMEM;
         } else
-        outcome[sz] = '\0';
+                outcome[sz] = '\0';
 
         *env = outcome;
         outcome = NULL;
@@ -483,7 +633,7 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) {
                         if (errno == EINTR)
                                 continue;
 
-                        return -errno;
+                        return negative_errno();
                 }
 
                 return 0;
@@ -534,24 +684,32 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_cod
         return -EPROTO;
 }
 
-void sigkill_wait(pid_t *pid) {
+#if 0 /// UNNEEDED by elogind
+void sigkill_wait(pid_t pid) {
+        assert(pid > 1);
+
+        if (kill(pid, SIGKILL) > 0)
+                (void) wait_for_terminate(pid, NULL);
+}
+
+void sigkill_waitp(pid_t *pid) {
         if (!pid)
                 return;
         if (*pid <= 1)
                 return;
 
-        if (kill(*pid, SIGKILL) > 0)
-                (void) wait_for_terminate(*pid, NULL);
+        sigkill_wait(*pid);
 }
 
-#if 0 /// UNNEEDED by elogind
 int kill_and_sigcont(pid_t pid, int sig) {
         int r;
 
         r = kill(pid, sig) < 0 ? -errno : 0;
 
-        if (r >= 0)
-                kill(pid, SIGCONT);
+        /* 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(sig, SIGCONT, SIGKILL))
+                (void) kill(pid, SIGCONT);
 
         return r;
 }
@@ -599,7 +757,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;
@@ -656,7 +814,7 @@ int pid_from_same_root_fs(pid_t pid) {
 
         root = procfs_file_alloca(pid, "root");
 
-        return files_same(root, "/proc/1/root");
+        return files_same(root, "/proc/1/root", 0);
 }
 #endif // 0
 
@@ -672,6 +830,8 @@ bool is_main_thread(void) {
 #if 0 /// UNNEEDED by elogind
 noreturn void freeze(void) {
 
+        log_close();
+
         /* Make sure nobody waits for us on a socket anymore */
         close_all_fds(NULL, 0);
 
@@ -688,6 +848,9 @@ bool oom_score_adjust_is_valid(int oa) {
 unsigned long personality_from_string(const char *p) {
         int architecture;
 
+        if (!p)
+                return PERSONALITY_INVALID;
+
         /* Parse a personality specifier. We use our own identifiers that indicate specific ABIs, rather than just
          * hints regarding the register size, since we want to keep things open for multiple locally supported ABIs for
          * the same register size. */
@@ -696,62 +859,11 @@ unsigned long personality_from_string(const char *p) {
         if (architecture < 0)
                 return PERSONALITY_INVALID;
 
-#if defined(__x86_64__)
-
-        if (architecture == ARCHITECTURE_X86)
-                return PER_LINUX32;
-
-        if (architecture == ARCHITECTURE_X86_64)
+        if (architecture == native_architecture())
                 return PER_LINUX;
-
-#elif defined(__i386__)
-
-        if (architecture == ARCHITECTURE_X86)
-                return PER_LINUX;
-
-#elif defined(__s390x__)
-
-        if (architecture == ARCHITECTURE_S390)
+#ifdef SECONDARY_ARCHITECTURE
+        if (architecture == SECONDARY_ARCHITECTURE)
                 return PER_LINUX32;
-
-        if (architecture == ARCHITECTURE_S390X)
-                return PER_LINUX;
-
-#elif defined(__s390__)
-
-        if (architecture == ARCHITECTURE_S390)
-                return PER_LINUX;
-
-#elif defined(__powerpc64__)
-#  if __BYTE_ORDER == __BIG_ENDIAN
-
-        if (architecture == ARCHITECTURE_PPC_LE)
-                return PER_LINUX32;
-
-        if (architecture == ARCHITECTURE_PPC64_LE)
-                return PER_LINUX;
-
-#  else
-
-        if (architecture == ARCHITECTURE_PPC)
-                return PER_LINUX32;
-
-        if (architecture == ARCHITECTURE_PPC64)
-                return PER_LINUX;
-
-#  endif
-#elif defined(__powerpc__)
-#  if __BYTE_ORDER == __BIG_ENDIAN
-
-        if (architecture == ARCHITECTURE_PPC)
-                return PER_LINUX;
-
-#  else
-
-        if (architecture == ARCHITECTURE_PPC_LE)
-                return PER_LINUX;
-
-#  endif
 #endif
 
         return PERSONALITY_INVALID;
@@ -760,58 +872,11 @@ unsigned long personality_from_string(const char *p) {
 const char* personality_to_string(unsigned long p) {
         int architecture = _ARCHITECTURE_INVALID;
 
-#if defined(__x86_64__)
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_X86_64;
-        else if (p == PER_LINUX32)
-                architecture = ARCHITECTURE_X86;
-
-#elif defined(__i386__)
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_X86;
-
-#elif defined(__s390x__)
-
         if (p == PER_LINUX)
-                architecture = ARCHITECTURE_S390X;
+                architecture = native_architecture();
+#ifdef SECONDARY_ARCHITECTURE
         else if (p == PER_LINUX32)
-                architecture = ARCHITECTURE_S390;
-
-#elif defined(__s390__)
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_S390;
-
-#elif defined(__powerpc64__)
-#  if __BYTE_ORDER == __BIG_ENDIAN
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_PPC64;
-        else if (p == PER_LINUX32)
-                 architecture = ARCHITECTURE_PPC;
-
-#  else
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_PPC64_LE;
-        else if (p == PER_LINUX32)
-                architecture = ARCHITECTURE_PPC_LE;
-
-#  endif
-#elif defined(__powerpc__)
-#  if __BYTE_ORDER == __BIG_ENDIAN
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_PPC;
-
-#  else
-
-        if (p == PER_LINUX)
-                architecture = ARCHITECTURE_PPC_LE;
-
-#  endif
+                architecture = SECONDARY_ARCHITECTURE;
 #endif
 
         if (architecture < 0)
@@ -824,7 +889,7 @@ void valgrind_summary_hack(void) {
 #ifdef HAVE_VALGRIND_VALGRIND_H
         if (getpid() == 1 && RUNNING_ON_VALGRIND) {
                 pid_t pid;
-                pid = raw_clone(SIGCHLD, NULL);
+                pid = raw_clone(SIGCHLD);
                 if (pid < 0)
                         log_emergency_errno(errno, "Failed to fork off valgrind helper: %m");
                 else if (pid == 0)
@@ -837,6 +902,35 @@ void valgrind_summary_hack(void) {
 #endif
 }
 
+int pid_compare_func(const void *a, const void *b) {
+        const pid_t *p = a, *q = b;
+
+        /* Suitable for usage in qsort() */
+
+        if (*p < *q)
+                return -1;
+        if (*p > *q)
+                return 1;
+        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;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",