X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fbasic%2Fprocess-util.c;h=08ec66fb31a5f647747a77ce7c4174c37d1ad5fe;hb=9b00a533195b7fec54a791ca02090e0799214770;hp=8a1f54db81b61fd6949401cb81b389e4b9942b54;hpb=59047c572d1f8d9153a6c06fac8a866ddcf30260;p=elogind.git diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 8a1f54db8..08ec66fb3 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -17,22 +17,43 @@ along with systemd; If not, see . ***/ +#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include - +#include +#include +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "alloc-util.h" +//#include "architecture.h" +#include "escape.h" +#include "fd-util.h" #include "fileio.h" -#include "util.h" +#include "fs-util.h" +//#include "ioprio.h" #include "log.h" -#include "signal-util.h" +#include "macro.h" +#include "missing.h" #include "process-util.h" +#include "signal-util.h" +//#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" int get_process_state(pid_t pid) { const char *p; @@ -80,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"); @@ -96,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; @@ -165,15 +234,153 @@ 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 +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 */ + + 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. */ + 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]) { + 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]) + break; + + memzero(saved_argv[i], strlen(saved_argv[i])); + } + } + + return !truncated; +} +#endif // 0 + int is_kernel_thread(pid_t pid) { const char *p; size_t count; @@ -206,8 +413,7 @@ int is_kernel_thread(pid_t pid) { return 0; } -/// UNNEEDED by elogind -#if 0 +#if 0 /// UNNEEDED by elogind int get_process_capeff(pid_t pid, char **capeff) { const char *p; int r; @@ -259,8 +465,7 @@ int get_process_exe(pid_t pid, char **name) { return 0; } -/// UNNEEDED by elogind -#if 0 +#if 0 /// UNNEEDED by elogind static int get_process_id(pid_t pid, const char *field, uid_t *uid) { _cleanup_fclose_ FILE *f = NULL; char line[LINE_MAX]; @@ -269,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) { @@ -361,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; @@ -369,7 +571,7 @@ int get_process_environ(pid_t pid, char **env) { return 0; } -int get_parent_of_pid(pid_t pid, pid_t *_ppid) { +int get_process_ppid(pid_t pid, pid_t *_ppid) { int r; _cleanup_free_ char *line = NULL; long unsigned ppid; @@ -431,7 +633,7 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) { if (errno == EINTR) continue; - return -errno; + return negative_errno(); } return 0; @@ -482,15 +684,32 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_cod return -EPROTO; } -/// UNNEEDED by elogind -#if 0 +#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; + + sigkill_wait(*pid); +} + 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; } @@ -538,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; @@ -556,9 +775,12 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { bool pid_is_unwaited(pid_t pid) { /* Checks whether a PID is still valid at all, including a zombie */ - if (pid <= 0) + if (pid < 0) return false; + 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 (kill(pid, 0) >= 0) return true; @@ -570,12 +792,155 @@ bool pid_is_alive(pid_t pid) { /* Checks whether a PID is still valid and not a zombie */ - if (pid <= 0) + if (pid < 0) return false; + if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ + return true; + r = get_process_state(pid); if (r == -ESRCH || r == 'Z') return false; return true; } + +#if 0 /// UNNEEDED by elogind +int pid_from_same_root_fs(pid_t pid) { + const char *root; + + if (pid < 0) + return 0; + + root = procfs_file_alloca(pid, "root"); + + return files_same(root, "/proc/1/root"); +} +#endif // 0 + +bool is_main_thread(void) { + static thread_local int cached = 0; + + if (_unlikely_(cached == 0)) + cached = getpid() == gettid() ? 1 : -1; + + return cached > 0; +} + +#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); + + sync(); + + for (;;) + pause(); +} + +bool oom_score_adjust_is_valid(int oa) { + return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; +} + +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. */ + + architecture = architecture_from_string(p); + if (architecture < 0) + return PERSONALITY_INVALID; + + if (architecture == native_architecture()) + return PER_LINUX; +#ifdef SECONDARY_ARCHITECTURE + if (architecture == SECONDARY_ARCHITECTURE) + return PER_LINUX32; +#endif + + return PERSONALITY_INVALID; +} + +const char* personality_to_string(unsigned long p) { + int architecture = _ARCHITECTURE_INVALID; + + if (p == PER_LINUX) + architecture = native_architecture(); +#ifdef SECONDARY_ARCHITECTURE + else if (p == PER_LINUX32) + architecture = SECONDARY_ARCHITECTURE; +#endif + + if (architecture < 0) + return NULL; + + return architecture_to_string(architecture); +} + +void valgrind_summary_hack(void) { +#ifdef HAVE_VALGRIND_VALGRIND_H + if (getpid() == 1 && RUNNING_ON_VALGRIND) { + pid_t pid; + pid = raw_clone(SIGCHLD); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); + else if (pid == 0) + exit(EXIT_SUCCESS); + else { + log_info("Spawned valgrind helper as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } +#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; +} + +static const char *const ioprio_class_table[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); + +static const char *const sigchld_code_table[] = { + [CLD_EXITED] = "exited", + [CLD_KILLED] = "killed", + [CLD_DUMPED] = "dumped", + [CLD_TRAPPED] = "trapped", + [CLD_STOPPED] = "stopped", + [CLD_CONTINUED] = "continued", +}; + +DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); + +static const char* const sched_policy_table[] = { + [SCHED_OTHER] = "other", + [SCHED_BATCH] = "batch", + [SCHED_IDLE] = "idle", + [SCHED_FIFO] = "fifo", + [SCHED_RR] = "rr" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); +#endif // 0