1/*
2 * Copyright 2019 Leorize <leorize+oss@disroot.org>
3 * Copyright 2015 Tiancheng "Timothy" Gu, timothygu99@gmail.com
4 * Copyright 2001-2002 Fran��ois Revol (mmu_man)
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
7
8
9#ifndef _XOPEN_SOURCE
10#define _XOPEN_SOURCE 600 /* for NZERO */
11#endif
12
13#include "pthread_private.h"
14
15#include <errno.h>
16#include <sys/resource.h>
17
18#include <OS.h>
19#include <sys/param.h> // for MAX() and MIN() macros
20#include <syscall_utils.h> // RETURN_AND_SET_ERRNO()
21
22// Part of this file is adapted from src/bin/renice.c, by Fran��ois Revol.
23
24// Some notes on different priority notations:
25//
26// Notation system          | Real-time | High Prio | Default | Low Prio |
27// -------------------------|----------:|----------:|--------:|---------:|
28// BeOS/Haiku               |      120* |        99 |      10 |      1** |
29// UNIX [gs]etpriority()*** |      N.A. |       -20 |       0 |       19 |
30// UNIX internal            |      N.A. |         0 |      20 |       39 |
31//
32// * 	Note that BeOS/Haiku does not have an absolute highest priority value
33//   	(else than the maximum of int32), and (B_REAL_TIME_PRIORITY - 1) is
34//   	the highest non-real-time priority and usually used as the priority
35//   	limit. On UNIX systems there is no such concept as "real-time."
36// **	0 only for idle_thread
37// ***	Also used for UNIX nice(1) and nice(3)
38
39#ifdef CLIP
40#undef CLIP
41#endif
42#define CLIP(n, max, min) MAX(MIN((n), (max)), (min))
43
44// In these contexts, "min" does not mean "lowest value," but "lowest
45// priority," which is the maximum value in UNIX priority notation.
46// "Zero" means normal priority, which in UNIX function notation is 0.
47// POSIX specification also refers to it as "NZERO."
48#define NMAX 0
49#define NMIN (NZERO * 2 - 1)
50#define CLIP_TO_UNIX(n) CLIP(n, NMIN, NMAX)
51
52#define BZERO B_NORMAL_PRIORITY
53#define BMAX (B_REAL_TIME_DISPLAY_PRIORITY - 1)
54#define BMIN 1
55#define CLIP_TO_BEOS(n) CLIP(n, BMAX, BMIN)
56
57// To accurately convert the notation values, we need to use an exponential
58// function:
59//
60// 	f(x) = 99e^(-0.116x)
61//
62// where f() represents the BeOS priority value, and x represents the Unix
63// priority value.
64//
65// But that's too complicated. And slow. So we use a simple piecewise linear
66// approach here, by a simple rescaling of the values.
67
68// returns an equivalent UNIX priority for a given BeOS priority.
69static int32
70prio_be_to_unix(int32 prio)
71{
72	int out;
73	if (prio >= BZERO)
74		out = NZERO
75			- ((prio - BZERO) * NZERO + (BMAX - BZERO) / 2) / (BMAX - BZERO);
76			// `(BMAX - BZERO) / 2` for rounding
77	else
78		out = NZERO
79			+ ((BZERO - prio) * (NZERO - 1)) / (BZERO - BMIN)
80			+ 1;
81			// `+1` for rounding
82	return CLIP_TO_UNIX(out);
83}
84
85
86// returns an equivalent BeOS priority for a given UNIX priority.
87static int32
88prio_unix_to_be(int32 prio)
89{
90	int out;
91	// Do not need to care about rounding
92	if (prio >= NZERO)
93		out = BZERO - ((prio - NZERO) * (BZERO - BMIN)) / (NZERO - 1);
94	else
95		out = BZERO + ((NZERO - prio) * (BMAX - BZERO)) / (NZERO);
96	return CLIP_TO_BEOS(out);
97}
98
99
100int
101getpriority(int which, id_t who)
102{
103	bool found = false;
104	int out = -100;
105
106	if (who < 0)
107		RETURN_AND_SET_ERRNO(EINVAL);
108	switch (which) {
109		case PRIO_PROCESS:
110		{
111			int32 th_cookie = 0;
112			thread_info thread;
113
114			while (get_next_thread_info(who, &th_cookie, &thread) == B_OK) {
115				if (thread.priority > out) {
116					found = true;
117					out = thread.priority;
118				}
119			}
120			break;
121		}
122		case PRIO_PGRP:
123		{
124			int32 team_cookie = 0, th_cookie = 0;
125			team_info team;
126			thread_info thread;
127
128			who = who == 0 ? getpgrp() : who;
129			while (get_next_team_info(&team_cookie, &team) == B_OK) {
130				if (getpgid(team.team) != who)
131					continue;
132				th_cookie = 0;
133				while (get_next_thread_info(team.team, &th_cookie, &thread)
134					== B_OK) {
135					if (thread.priority > out) {
136						found = true;
137						out = thread.priority;
138					}
139				}
140			}
141			break;
142		}
143		case PRIO_USER:
144		{
145			// `who` (id_t) is int32, but uid_t is uint32, so using this
146			// indirection to get rid of compiler warnings
147			// `who` can never be negative because of the `who < 0` check
148			// above.
149			uid_t euid = who == 0 ? geteuid() : (uid_t)who;
150			int32 team_cookie = 0, th_cookie = 0;
151			team_info team;
152			thread_info thread;
153
154			while (get_next_team_info(&team_cookie, &team) == B_OK) {
155				if (team.uid != euid)
156					continue;
157				th_cookie = 0;
158				while (get_next_thread_info(team.team, &th_cookie, &thread)
159					== B_OK) {
160					if (thread.priority > out) {
161						found = true;
162						out = thread.priority;
163					}
164				}
165			}
166			break;
167		}
168		default:
169			RETURN_AND_SET_ERRNO(EINVAL);
170	}
171	if (!found)
172		RETURN_AND_SET_ERRNO(ESRCH);
173	return prio_be_to_unix(out) - NZERO;
174}
175
176
177int
178setpriority(int which, id_t who, int value)
179{
180	int32 th_cookie = 0;
181	thread_info thread;
182
183	// TODO: implement for other processes
184	if (who != 0 && which != PRIO_PROCESS)
185		RETURN_AND_SET_ERRNO(EINVAL);
186	value = value > NMIN ? NMIN : CLIP_TO_UNIX(value + NZERO);
187	value = prio_unix_to_be(value);
188
189	__pthread_set_default_priority(value);
190	while (get_next_thread_info(B_CURRENT_TEAM, &th_cookie, &thread) == B_OK)
191		set_thread_priority(thread.thread, value);
192
193	return 0;
194}
195