Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : :
5 : : #include "alloc-util.h"
6 : : #include "def.h"
7 : : #include "fd-util.h"
8 : : #include "fileio.h"
9 : : #include "parse-util.h"
10 : : #include "process-util.h"
11 : : #include "procfs-util.h"
12 : : #include "stdio-util.h"
13 : : #include "string-util.h"
14 : :
15 : 76 : int procfs_tasks_get_limit(uint64_t *ret) {
16 : 76 : _cleanup_free_ char *value = NULL;
17 : : uint64_t pid_max, threads_max;
18 : : int r;
19 : :
20 [ - + ]: 76 : assert(ret);
21 : :
22 : : /* So there are two sysctl files that control the system limit of processes:
23 : : *
24 : : * 1. kernel.threads-max: this is probably the sysctl that makes more sense, as it directly puts a limit on
25 : : * concurrent tasks.
26 : : *
27 : : * 2. kernel.pid_max: this limits the numeric range PIDs can take, and thus indirectly also limits the number
28 : : * of concurrent threads. AFAICS it's primarily a compatibility concept: some crappy old code used a signed
29 : : * 16bit type for PIDs, hence the kernel provides a way to ensure the PIDs never go beyond INT16_MAX by
30 : : * default.
31 : : *
32 : : * By default #2 is set to much lower values than #1, hence the limit people come into contact with first, as
33 : : * it's the lowest boundary they need to bump when they want higher number of processes.
34 : : *
35 : : * Also note the weird definition of #2: PIDs assigned will be kept below this value, which means the number of
36 : : * tasks that can be created is one lower, as PID 0 is not a valid process ID. */
37 : :
38 : 76 : r = read_one_line_file("/proc/sys/kernel/pid_max", &value);
39 [ - + ]: 76 : if (r < 0)
40 : 0 : return r;
41 : :
42 : 76 : r = safe_atou64(value, &pid_max);
43 [ - + ]: 76 : if (r < 0)
44 : 0 : return r;
45 : :
46 : 76 : value = mfree(value);
47 : 76 : r = read_one_line_file("/proc/sys/kernel/threads-max", &value);
48 [ - + ]: 76 : if (r < 0)
49 : 0 : return r;
50 : :
51 : 76 : r = safe_atou64(value, &threads_max);
52 [ - + ]: 76 : if (r < 0)
53 : 0 : return r;
54 : :
55 : : /* Subtract one from pid_max, since PID 0 is not a valid PID */
56 : 76 : *ret = MIN(pid_max-1, threads_max);
57 : 76 : return 0;
58 : : }
59 : :
60 : 12 : int procfs_tasks_set_limit(uint64_t limit) {
61 : : char buffer[DECIMAL_STR_MAX(uint64_t)+1];
62 : 12 : _cleanup_free_ char *value = NULL;
63 : : uint64_t pid_max;
64 : : int r;
65 : :
66 [ - + ]: 12 : if (limit == 0) /* This makes no sense, we are userspace and hence count as tasks too, and we want to live,
67 : : * hence the limit conceptually has to be above 0. Also, most likely if anyone asks for a zero
68 : : * limit he/she probably means "no limit", hence let's better refuse this to avoid
69 : : * confusion. */
70 : 0 : return -EINVAL;
71 : :
72 : : /* The Linux kernel doesn't allow this value to go below 20, hence don't allow this either, higher values than
73 : : * TASKS_MAX are not accepted by the pid_max sysctl. We'll treat anything this high as "unbounded" and hence
74 : : * set it to the maximum. */
75 [ - + ]: 12 : limit = CLAMP(limit, 20U, TASKS_MAX);
76 : :
77 : 12 : r = read_one_line_file("/proc/sys/kernel/pid_max", &value);
78 [ - + ]: 12 : if (r < 0)
79 : 0 : return r;
80 : 12 : r = safe_atou64(value, &pid_max);
81 [ - + ]: 12 : if (r < 0)
82 : 0 : return r;
83 : :
84 : : /* As pid_max is about the numeric pid_t range we'll bump it if necessary, but only ever increase it, never
85 : : * decrease it, as threads-max is the much more relevant sysctl. */
86 [ - + ]: 12 : if (limit > pid_max-1) {
87 : 0 : sprintf(buffer, "%" PRIu64, limit+1); /* Add one, since PID 0 is not a valid PID */
88 : 0 : r = write_string_file("/proc/sys/kernel/pid_max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER);
89 [ # # ]: 0 : if (r < 0)
90 : 0 : return r;
91 : : }
92 : :
93 : 12 : sprintf(buffer, "%" PRIu64, limit);
94 : 12 : r = write_string_file("/proc/sys/kernel/threads-max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER);
95 [ + - ]: 12 : if (r < 0) {
96 : : uint64_t threads_max;
97 : :
98 : : /* Hmm, we couldn't write this? If so, maybe it was already set properly? In that case let's not
99 : : * generate an error */
100 : :
101 : 12 : value = mfree(value);
102 [ - + ]: 12 : if (read_one_line_file("/proc/sys/kernel/threads-max", &value) < 0)
103 : 4 : return r; /* return original error */
104 : :
105 [ - + ]: 12 : if (safe_atou64(value, &threads_max) < 0)
106 : 0 : return r; /* return original error */
107 : :
108 [ + + ]: 12 : if (MIN(pid_max-1, threads_max) != limit)
109 : 4 : return r; /* return original error */
110 : :
111 : : /* Yay! Value set already matches what we were trying to set, hence consider this a success. */
112 : : }
113 : :
114 : 8 : return 0;
115 : : }
116 : :
117 : 4 : int procfs_tasks_get_current(uint64_t *ret) {
118 : 4 : _cleanup_free_ char *value = NULL;
119 : : const char *p, *nr;
120 : : size_t n;
121 : : int r;
122 : :
123 [ - + ]: 4 : assert(ret);
124 : :
125 : 4 : r = read_one_line_file("/proc/loadavg", &value);
126 [ - + ]: 4 : if (r < 0)
127 : 0 : return r;
128 : :
129 : : /* Look for the second part of the fourth field, which is separated by a slash from the first part. None of the
130 : : * earlier fields use a slash, hence let's use this to find the right spot. */
131 : 4 : p = strchr(value, '/');
132 [ - + ]: 4 : if (!p)
133 : 0 : return -EINVAL;
134 : :
135 : 4 : p++;
136 : 4 : n = strspn(p, DIGITS);
137 : 4 : nr = strndupa(p, n);
138 : :
139 : 4 : return safe_atou64(nr, ret);
140 : : }
141 : :
142 : 4 : static uint64_t calc_gcd64(uint64_t a, uint64_t b) {
143 : :
144 [ + + ]: 8 : while (b > 0) {
145 : : uint64_t t;
146 : :
147 : 4 : t = a % b;
148 : :
149 : 4 : a = b;
150 : 4 : b = t;
151 : : }
152 : :
153 : 4 : return a;
154 : : }
155 : :
156 : 4 : int procfs_cpu_get_usage(nsec_t *ret) {
157 : 4 : _cleanup_free_ char *first_line = NULL;
158 : : unsigned long user_ticks, nice_ticks, system_ticks, irq_ticks, softirq_ticks,
159 : 4 : guest_ticks = 0, guest_nice_ticks = 0;
160 : : long ticks_per_second;
161 : : uint64_t sum, gcd, a, b;
162 : : const char *p;
163 : : int r;
164 : :
165 [ - + ]: 4 : assert(ret);
166 : :
167 : 4 : r = read_one_line_file("/proc/stat", &first_line);
168 [ - + ]: 4 : if (r < 0)
169 : 0 : return r;
170 : :
171 : 4 : p = first_word(first_line, "cpu");
172 [ - + ]: 4 : if (!p)
173 : 0 : return -EINVAL;
174 : :
175 [ - + ]: 4 : if (sscanf(p, "%lu %lu %lu %*u %*u %lu %lu %*u %lu %lu",
176 : : &user_ticks,
177 : : &nice_ticks,
178 : : &system_ticks,
179 : : &irq_ticks,
180 : : &softirq_ticks,
181 : : &guest_ticks,
182 : : &guest_nice_ticks) < 5) /* we only insist on the first five fields */
183 : 0 : return -EINVAL;
184 : :
185 : 4 : ticks_per_second = sysconf(_SC_CLK_TCK);
186 [ - + ]: 4 : if (ticks_per_second < 0)
187 : 0 : return -errno;
188 [ - + ]: 4 : assert(ticks_per_second > 0);
189 : :
190 : 8 : sum = (uint64_t) user_ticks + (uint64_t) nice_ticks + (uint64_t) system_ticks +
191 : 4 : (uint64_t) irq_ticks + (uint64_t) softirq_ticks +
192 : 4 : (uint64_t) guest_ticks + (uint64_t) guest_nice_ticks;
193 : :
194 : : /* Let's reduce this fraction before we apply it to avoid overflows when converting this to µsec */
195 : 4 : gcd = calc_gcd64(NSEC_PER_SEC, ticks_per_second);
196 : :
197 : 4 : a = (uint64_t) NSEC_PER_SEC / gcd;
198 : 4 : b = (uint64_t) ticks_per_second / gcd;
199 : :
200 : 4 : *ret = DIV_ROUND_UP((nsec_t) sum * (nsec_t) a, (nsec_t) b);
201 : 4 : return 0;
202 : : }
203 : :
204 : 4 : int procfs_memory_get(uint64_t *ret_total, uint64_t *ret_used) {
205 : 4 : uint64_t mem_total = UINT64_MAX, mem_free = UINT64_MAX;
206 : 4 : _cleanup_fclose_ FILE *f = NULL;
207 : : int r;
208 : :
209 : 4 : f = fopen("/proc/meminfo", "re");
210 [ - + ]: 4 : if (!f)
211 : 0 : return -errno;
212 : :
213 : 4 : for (;;) {
214 [ + - - + ]: 8 : _cleanup_free_ char *line = NULL;
215 : : uint64_t *v;
216 : : char *p, *e;
217 : : size_t n;
218 : :
219 : 8 : r = read_line(f, LONG_LINE_MAX, &line);
220 [ - + ]: 8 : if (r < 0)
221 : 0 : return r;
222 [ - + ]: 8 : if (r == 0)
223 : 0 : return -EINVAL; /* EOF: Couldn't find one or both fields? */
224 : :
225 : 8 : p = first_word(line, "MemTotal:");
226 [ + + ]: 8 : if (p)
227 : 4 : v = &mem_total;
228 : : else {
229 : 4 : p = first_word(line, "MemFree:");
230 [ + - ]: 4 : if (p)
231 : 4 : v = &mem_free;
232 : : else
233 : 0 : continue;
234 : : }
235 : :
236 : : /* Determine length of numeric value */
237 : 8 : n = strspn(p, DIGITS);
238 [ - + ]: 8 : if (n == 0)
239 : 0 : return -EINVAL;
240 : 8 : e = p + n;
241 : :
242 : : /* Ensure the line ends in " kB" */
243 : 8 : n = strspn(e, WHITESPACE);
244 [ - + ]: 8 : if (n == 0)
245 : 0 : return -EINVAL;
246 [ - + ]: 8 : if (!streq(e + n, "kB"))
247 : 0 : return -EINVAL;
248 : :
249 : 8 : *e = 0;
250 : 8 : r = safe_atou64(p, v);
251 [ - + ]: 8 : if (r < 0)
252 : 0 : return r;
253 [ - + ]: 8 : if (*v == UINT64_MAX)
254 : 0 : return -EINVAL;
255 : :
256 [ + - + + ]: 8 : if (mem_total != UINT64_MAX && mem_free != UINT64_MAX)
257 : 4 : break;
258 : : }
259 : :
260 [ - + ]: 4 : if (mem_free > mem_total)
261 : 0 : return -EINVAL;
262 : :
263 [ - + ]: 4 : if (ret_total)
264 : 0 : *ret_total = mem_total * 1024U;
265 [ + - ]: 4 : if (ret_used)
266 : 4 : *ret_used = (mem_total - mem_free) * 1024U;
267 : 4 : return 0;
268 : : }
|