xref: /haiku/src/system/libroot/posix/sys/priority.c (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
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.
69 static int32
70 prio_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.
87 static int32
88 prio_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 
100 int
101 getpriority(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 
177 int
178 setpriority(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