diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 743c7b1dad..5de366f830 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -22,6 +22,7 @@ #include "alloc-util.h" #include "architecture.h" #include "env-util.h" +#include "errno-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -1527,6 +1528,62 @@ int pidfd_get_pid(int fd, pid_t *ret) { return parse_pid(p, ret); } +static int rlimit_to_nice(rlim_t limit) { + if (limit <= 1) + return PRIO_MAX-1; /* i.e. 19 */ + + if (limit >= -PRIO_MIN + PRIO_MAX) + return PRIO_MIN; /* i.e. -20 */ + + return PRIO_MAX - (int) limit; +} + +int setpriority_closest(int priority) { + int current, limit, saved_errno; + struct rlimit highest; + + /* Try to set requested nice level */ + if (setpriority(PRIO_PROCESS, 0, priority) >= 0) + return 1; + + /* Permission failed */ + saved_errno = -errno; + if (!ERRNO_IS_PRIVILEGE(saved_errno)) + return saved_errno; + + errno = 0; + current = getpriority(PRIO_PROCESS, 0); + if (errno != 0) + return -errno; + + if (priority == current) + return 1; + + /* Hmm, we'd expect that raising the nice level from our status quo would always work. If it doesn't, + * then the whole setpriority() system call is blocked to us, hence let's propagate the error + * right-away */ + if (priority > current) + return saved_errno; + + if (getrlimit(RLIMIT_NICE, &highest) < 0) + return -errno; + + limit = rlimit_to_nice(highest.rlim_cur); + + /* We are already less nice than limit allows us */ + if (current < limit) { + log_debug("Cannot raise nice level, permissions and the resource limit do not allow it."); + return 0; + } + + /* Push to the allowed limit */ + if (setpriority(PRIO_PROCESS, 0, limit) < 0) + return -errno; + + log_debug("Cannot set requested nice level (%i), used next best (%i).", priority, limit); + return 0; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 57582738b3..a238b25796 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -200,3 +200,5 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); }) int pidfd_get_pid(int fd, pid_t *ret); + +int setpriority_closest(int priority); diff --git a/src/core/execute.c b/src/core/execute.c index 2cb2392d16..11372958a6 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3236,11 +3236,11 @@ static int exec_child( } } - if (context->nice_set) - if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { - *exit_status = EXIT_NICE; - return log_unit_error_errno(unit, errno, "Failed to set up process scheduling priority (nice level): %m"); - } + if (context->nice_set) { + r = setpriority_closest(context->nice); + if (r < 0) + return log_unit_error_errno(unit, r, "Failed to set up process scheduling priority (nice level): %m"); + } if (context->cpu_sched_set) { struct sched_param param = { diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index d0f126b236..7b6a38b824 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -14,6 +14,7 @@ #include "alloc-util.h" #include "architecture.h" +#include "errno-util.h" #include "fd-util.h" #include "log.h" #include "macro.h" @@ -21,11 +22,13 @@ #include "missing_syscall.h" #include "parse-util.h" #include "process-util.h" +#include "rlimit-util.h" #include "signal-util.h" #include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" #include "tests.h" +#include "user-util.h" #include "util.h" #include "virt.h" @@ -601,6 +604,92 @@ static void test_ioprio_class_from_to_string(void) { test_ioprio_class_from_to_string_one("-1", -1); } +static void test_setpriority_closest(void) { + int r; + + r = safe_fork("(test-setprio)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL); + assert_se(r >= 0); + + if (r == 0) { + bool full_test; + int p, q; + /* child */ + + /* rlimit of 30 equals nice level of -10 */ + if (setrlimit(RLIMIT_NICE, &RLIMIT_MAKE_CONST(30)) < 0) { + /* If this fails we are probably unprivielged or in a userns of some kind, let's skip + * the full test */ + assert_se(ERRNO_IS_PRIVILEGE(errno)); + full_test = false; + } else { + assert_se(setresgid(GID_NOBODY, GID_NOBODY, GID_NOBODY) >= 0); + assert_se(setresuid(UID_NOBODY, UID_NOBODY, UID_NOBODY) >= 0); + full_test = true; + } + + errno = 0; + p = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0); + + /* It should always be possible to set our nice level to the current one */ + assert_se(setpriority_closest(p) > 0); + + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && p == q); + + /* It should also be possible to to set the nice level to one higher */ + if (p < PRIO_MAX-1) { + assert_se(setpriority_closest(++p) > 0); + + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && p == q); + } + + /* It should also be possible to to set the nice level to two higher */ + if (p < PRIO_MAX-1) { + assert_se(setpriority_closest(++p) > 0); + + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && p == q); + } + + if (full_test) { + /* These two should work, given the RLIMIT_NICE we set above */ + assert_se(setpriority_closest(-10) > 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -10); + + assert_se(setpriority_closest(-9) > 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -9); + + /* This should succeed but should be clamped to the limit */ + assert_se(setpriority_closest(-11) == 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -10); + + assert_se(setpriority_closest(-8) > 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -8); + + /* This should succeed but should be clamped to the limit */ + assert_se(setpriority_closest(-12) == 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -10); + } + + _exit(EXIT_SUCCESS); + } +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -627,6 +716,7 @@ int main(int argc, char *argv[]) { test_safe_fork(); test_pid_to_ptr(); test_ioprio_class_from_to_string(); + test_setpriority_closest(); return 0; }