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