#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
-#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include "alloc-util.h"
#include "architecture.h"
#include "escape.h"
+#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "ioprio.h"
+#include "locale-util.h"
#include "log.h"
#include "macro.h"
+#include "memory-util.h"
#include "missing.h"
+#include "namespace-util.h"
#include "process-util.h"
#include "raw-clone.h"
#include "rlimit-util.h"
#include "string-util.h"
#include "terminal-util.h"
#include "user-util.h"
-#include "util.h"
+#include "utf8.h"
-int get_process_state(pid_t pid) {
+/* The kernel limits userspace processes to TASK_COMM_LEN (16 bytes), but allows higher values for its own
+ * workers, e.g. "kworker/u9:3-kcryptd/253:0". Let's pick a fixed smallish limit that will work for the kernel.
+ */
+#define COMM_MAX_LEN 128
+
+static int get_process_state(pid_t pid) {
const char *p;
char state;
int r;
assert(ret);
assert(pid >= 0);
- escaped = new(char, TASK_COMM_LEN);
+ escaped = new(char, COMM_MAX_LEN);
if (!escaped)
return -ENOMEM;
return r;
/* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */
- cellescape(escaped, TASK_COMM_LEN, comm);
+ cellescape(escaped, COMM_MAX_LEN, comm);
*ret = TAKE_PTR(escaped);
return 0;
}
-int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
+int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line) {
_cleanup_fclose_ FILE *f = NULL;
- bool space = false;
- char *k, *ans = NULL;
+ _cleanup_free_ char *t = NULL, *ans = NULL;
const char *p;
- int c;
+ int r;
+ size_t k;
+
+ /* This is supposed to be a safety guard against runaway command lines. */
+ size_t max_length = sc_arg_max();
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.
+ /* Retrieves a process' command line. Replaces non-utf8 bytes by replacement character (�). If
+ * max_columns is != -1 will return a string of the specified console width at most, abbreviated with
+ * an ellipsis. If PROCESS_CMDLINE_COMM_FALLBACK is specified in flags 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. This will use at most _SC_ARG_MAX bytes of
+ * input data.
*
* 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");
+ r = fopen_unlocked(p, "re", &f);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
- f = fopen(p, "re");
- if (!f) {
- if (errno == ENOENT)
- return -ESRCH;
- return -errno;
- }
-
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
- 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(ans, allocated, len+3)) {
- free(ans);
- return -ENOMEM;
- }
-
- if (isprint(c)) {
- if (space) {
- ans[len++] = ' ';
- space = false;
- }
+ /* We assume that each four-byte character uses one or two columns. If we ever check for combining
+ * characters, this assumption will need to be adjusted. */
+ if ((size_t) 4 * max_columns + 1 < max_columns)
+ max_length = MIN(max_length, (size_t) 4 * max_columns + 1);
- ans[len++] = c;
- } else if (len > 0)
- space = true;
- }
+ t = new(char, max_length);
+ if (!t)
+ return -ENOMEM;
- if (len > 0)
- ans[len] = '\0';
- else
- ans = mfree(ans);
+ k = fread(t, 1, max_length, f);
+ if (k > 0) {
+ /* Arguments are separated by NULs. Let's replace those with spaces. */
+ for (size_t i = 0; i < k - 1; i++)
+ if (t[i] == '\0')
+ t[i] = ' ';
+ t[k] = '\0'; /* Normally, t[k] is already NUL, so this is just a guard in case of short read */
} else {
- bool dotdotdot = false;
- size_t left;
-
- ans = new(char, max_length);
- if (!ans)
- return -ENOMEM;
-
- k = ans;
- left = max_length;
- while ((c = getc(f)) != EOF) {
-
- if (isprint(c)) {
-
- if (space) {
- if (left <= 2) {
- dotdotdot = true;
- break;
- }
-
- *(k++) = ' ';
- left--;
- space = false;
- }
-
- if (left <= 1) {
- dotdotdot = true;
- break;
- }
-
- *(k++) = (char) c;
- left--;
- } else if (k > ans)
- space = true;
- }
-
- 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(ans)) {
- _cleanup_free_ char *t = NULL;
- int h;
-
- free(ans);
+ /* We only treat getting nothing as an error. We *could* also get an error after reading some
+ * data, but we ignore that case, as such an error is rather unlikely and we prefer to get
+ * some data rather than none. */
+ if (ferror(f))
+ return -errno;
- if (!comm_fallback)
+ if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK))
return -ENOENT;
- h = get_process_comm(pid, &t);
- if (h < 0)
- return h;
-
- if (max_length == 0)
- ans = strjoin("[", t, "]");
- else {
- size_t l;
-
- l = strlen(t);
+ /* Kernel threads have no argv[] */
+ _cleanup_free_ char *t2 = NULL;
- if (l + 3 <= max_length)
- ans = strjoin("[", t, "]");
- else if (max_length <= 6) {
+ r = get_process_comm(pid, &t2);
+ if (r < 0)
+ return r;
- ans = new(char, max_length);
- if (!ans)
- return -ENOMEM;
+ mfree(t);
+ t = strjoin("[", t2, "]");
+ if (!t)
+ return -ENOMEM;
+ }
- memcpy(ans, "[...]", max_length-1);
- ans[max_length-1] = 0;
- } else {
- t[max_length - 6] = 0;
+ delete_trailing_chars(t, WHITESPACE);
- /* Chop off final spaces */
- delete_trailing_chars(t, WHITESPACE);
+ bool eight_bit = (flags & PROCESS_CMDLINE_USE_LOCALE) && !is_locale_utf8();
- ans = strjoin("[", t, "...]");
- }
- }
- if (!ans)
- return -ENOMEM;
- }
+ ans = escape_non_printable_full(t, max_columns, eight_bit);
+ if (!ans)
+ return -ENOMEM;
- *line = ans;
+ (void) str_realloc(&ans);
+ *line = TAKE_PTR(ans);
return 0;
}
* can use PR_SET_NAME, which sets the thread name for the calling thread. */
if (prctl(PR_SET_NAME, name) < 0)
log_debug_errno(errno, "PR_SET_NAME failed: %m");
- if (l >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */
+ if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
truncated = true;
/* Second step, change glibc's ID of the process name. */
return -EINVAL;
p = procfs_file_alloca(pid, "status");
- f = fopen(p, "re");
- if (!f) {
- if (errno == ENOENT)
- return -ESRCH;
- return -errno;
- }
-
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ r = fopen_unlocked(p, "re", &f);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
for (;;) {
_cleanup_free_ char *line = NULL;
p = procfs_file_alloca(pid, "environ");
- f = fopen(p, "re");
- if (!f) {
- if (errno == ENOENT)
- return -ESRCH;
- return -errno;
- }
-
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ r = fopen_unlocked(p, "re", &f);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
for (;;) {
char c;
path = procfs_file_alloca(pid, "environ");
- f = fopen(path, "re");
- if (!f) {
- if (errno == ENOENT)
- return -ESRCH;
-
- return -errno;
- }
-
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ r = fopen_unlocked(path, "re", &f);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
l = strlen(field);
for (;;) {
return 0;
}
+int pid_is_my_child(pid_t pid) {
+ pid_t ppid;
+ int r;
+
+ if (pid <= 1)
+ return false;
+
+ r = get_process_ppid(pid, &ppid);
+ if (r < 0)
+ return r;
+
+ return ppid == getpid_cached();
+}
+
bool pid_is_unwaited(pid_t pid) {
/* Checks whether a PID is still valid at all, including a zombie */
log_close();
/* Make sure nobody waits for us on a socket anymore */
- close_all_fds(NULL, 0);
+ (void) close_all_fds(NULL, 0);
sync();
WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);
}
+int cpus_in_affinity_mask(void) {
+ size_t n = 16;
+ int r;
+
+ for (;;) {
+ cpu_set_t *c;
+
+ c = CPU_ALLOC(n);
+ if (!c)
+ return -ENOMEM;
+
+ if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) {
+ int k;
+
+ k = CPU_COUNT_S(CPU_ALLOC_SIZE(n), c);
+ CPU_FREE(c);
+
+ if (k <= 0)
+ return -EINVAL;
+
+ return k;
+ }
+
+ r = -errno;
+ CPU_FREE(c);
+
+ if (r != -EINVAL)
+ return r;
+ if (n > SIZE_MAX/2)
+ return -ENOMEM;
+ n *= 2;
+ }
+}
+
static const char *const ioprio_class_table[] = {
[IOPRIO_CLASS_NONE] = "none",
[IOPRIO_CLASS_RT] = "realtime",