#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 "signal-util.h"
#include "stat-util.h"
#include "string-table.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;
+ /* Kernel threads have no argv[] */
+ _cleanup_free_ char *t2 = NULL;
- 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) {
+ 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;
return get_process_link_contents(p, root);
}
+#define ENVIRONMENT_BLOCK_MAX (5U*1024U*1024U)
+
int get_process_environ(pid_t pid, char **env) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *outcome = NULL;
- int c;
- const char *p;
size_t allocated = 0, sz = 0;
+ const char *p;
+ int r;
assert(pid >= 0);
assert(env);
p = procfs_file_alloca(pid, "environ");
- f = fopen(p, "re");
- if (!f) {
- if (errno == ENOENT)
- return -ESRCH;
- return -errno;
- }
+ r = fopen_unlocked(p, "re", &f);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ char c;
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ if (sz >= ENVIRONMENT_BLOCK_MAX)
+ return -ENOBUFS;
- while ((c = fgetc(f)) != EOF) {
if (!GREEDY_REALLOC(outcome, allocated, sz + 5))
return -ENOMEM;
+ r = safe_fgetc(f, &c);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
if (c == '\0')
outcome[sz++] = '\n';
else
sz += cescape_char(c, outcome + sz);
}
- if (!outcome) {
- outcome = strdup("");
- if (!outcome)
- return -ENOMEM;
- } else
- outcome[sz] = '\0';
-
+ outcome[sz] = '\0';
*env = TAKE_PTR(outcome);
return 0;
int getenv_for_pid(pid_t pid, const char *field, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
char *value = NULL;
- bool done = false;
const char *path;
- size_t l;
+ size_t l, sum = 0;
+ int r;
assert(pid >= 0);
assert(field);
return 1;
}
- path = procfs_file_alloca(pid, "environ");
-
- f = fopen(path, "re");
- if (!f) {
- if (errno == ENOENT)
- return -ESRCH;
+ if (!pid_is_valid(pid))
+ return -EINVAL;
- return -errno;
- }
+ path = procfs_file_alloca(pid, "environ");
- (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 (;;) {
+ _cleanup_free_ char *line = NULL;
- do {
- char line[LINE_MAX];
- size_t i;
-
- for (i = 0; i < sizeof(line)-1; i++) {
- int c;
+ if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */
+ return -ENOBUFS;
- c = getc(f);
- if (_unlikely_(c == EOF)) {
- done = true;
- break;
- } else if (c == 0)
- break;
+ r = read_nul_string(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return r;
+ if (r == 0) /* EOF */
+ break;
- line[i] = c;
- }
- line[i] = 0;
+ sum += r;
if (strneq(line, field, l) && line[l] == '=') {
value = strdup(line + l + 1);
*ret = value;
return 1;
}
-
- } while (!done);
+ }
*ret = NULL;
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();
* 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__));
+extern void* __dso_handle _weak_;
pid_t getpid_cached(void) {
static bool installed = false;
if (geteuid() == 0)
return 0;
- log_error("Need to be root.");
- return -EPERM;
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root.");
}
int safe_fork_full(
original_pid = getpid_cached();
if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG)) {
-
/* We temporarily block all signals, so that the new child has them blocked initially. This way, we can
* be sure that SIGTERMs are not lost we might send to the child. */
- if (sigfillset(&ss) < 0)
- return log_full_errno(prio, errno, "Failed to reset signal set: %m");
-
+ assert_se(sigfillset(&ss) >= 0);
block_signals = true;
} else if (flags & FORK_WAIT) {
-
/* Let's block SIGCHLD at least, so that we can safely watch for the child process */
- if (sigemptyset(&ss) < 0)
- return log_full_errno(prio, errno, "Failed to clear signal set: %m");
-
- if (sigaddset(&ss, SIGCHLD) < 0)
- return log_full_errno(prio, errno, "Failed to add SIGCHLD to signal set: %m");
-
+ assert_se(sigemptyset(&ss) >= 0);
+ assert_se(sigaddset(&ss, SIGCHLD) >= 0);
block_signals = true;
}
}
}
+ if (flags & FORK_RLIMIT_NOFILE_SAFE) {
+ r = rlimit_nofile_safe();
+ if (r < 0) {
+ log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m");
+ _exit(EXIT_FAILURE);
+ }
+ }
+
if (ret_pid)
*ret_pid = getpid_cached();
safe_close_above_stdio(fd);
}
+ (void) rlimit_nofile_safe();
+
/* Count arguments */
va_start(ap, path);
for (n = 0; va_arg(ap, char*); n++)
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",