]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <errno.h> | |
4 | ||
5 | #include "alloc-util.h" | |
6 | #include "errno-util.h" | |
7 | #include "extract-word.h" | |
8 | #include "fd-util.h" | |
9 | #include "fileio.h" | |
10 | #include "format-util.h" | |
11 | #include "macro.h" | |
12 | #include "missing_resource.h" | |
13 | #include "process-util.h" | |
14 | #include "rlimit-util.h" | |
15 | #include "string-table.h" | |
16 | #include "strv.h" | |
17 | #include "time-util.h" | |
18 | ||
19 | int setrlimit_closest(int resource, const struct rlimit *rlim) { | |
20 | struct rlimit highest, fixed; | |
21 | ||
22 | assert(rlim); | |
23 | ||
24 | if (setrlimit(resource, rlim) >= 0) | |
25 | return 0; | |
26 | ||
27 | if (errno != EPERM) | |
28 | return -errno; | |
29 | ||
30 | /* So we failed to set the desired setrlimit, then let's try | |
31 | * to get as close as we can */ | |
32 | if (getrlimit(resource, &highest) < 0) | |
33 | return -errno; | |
34 | ||
35 | /* If the hard limit is unbounded anyway, then the EPERM had other reasons, let's propagate the original EPERM | |
36 | * then */ | |
37 | if (highest.rlim_max == RLIM_INFINITY) | |
38 | return -EPERM; | |
39 | ||
40 | fixed = (struct rlimit) { | |
41 | .rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max), | |
42 | .rlim_max = MIN(rlim->rlim_max, highest.rlim_max), | |
43 | }; | |
44 | ||
45 | /* Shortcut things if we wouldn't change anything. */ | |
46 | if (fixed.rlim_cur == highest.rlim_cur && | |
47 | fixed.rlim_max == highest.rlim_max) | |
48 | return 0; | |
49 | ||
50 | log_debug("Failed at setting rlimit " RLIM_FMT " for resource RLIMIT_%s. Will attempt setting value " RLIM_FMT " instead.", rlim->rlim_max, rlimit_to_string(resource), fixed.rlim_max); | |
51 | ||
52 | return RET_NERRNO(setrlimit(resource, &fixed)); | |
53 | } | |
54 | ||
55 | int setrlimit_closest_all(const struct rlimit *const *rlim, int *which_failed) { | |
56 | int r; | |
57 | ||
58 | assert(rlim); | |
59 | ||
60 | /* On failure returns the limit's index that failed in *which_failed, but only if non-NULL */ | |
61 | ||
62 | for (int i = 0; i < _RLIMIT_MAX; i++) { | |
63 | if (!rlim[i]) | |
64 | continue; | |
65 | ||
66 | r = setrlimit_closest(i, rlim[i]); | |
67 | if (r < 0) { | |
68 | if (which_failed) | |
69 | *which_failed = i; | |
70 | ||
71 | return r; | |
72 | } | |
73 | } | |
74 | ||
75 | if (which_failed) | |
76 | *which_failed = -1; | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | static int rlimit_parse_u64(const char *val, rlim_t *ret) { | |
82 | uint64_t u; | |
83 | int r; | |
84 | ||
85 | assert(val); | |
86 | assert(ret); | |
87 | ||
88 | if (streq(val, "infinity")) { | |
89 | *ret = RLIM_INFINITY; | |
90 | return 0; | |
91 | } | |
92 | ||
93 | /* setrlimit(2) suggests rlim_t is always 64-bit on Linux. */ | |
94 | assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); | |
95 | ||
96 | r = safe_atou64(val, &u); | |
97 | if (r < 0) | |
98 | return r; | |
99 | if (u >= (uint64_t) RLIM_INFINITY) | |
100 | return -ERANGE; | |
101 | ||
102 | *ret = (rlim_t) u; | |
103 | return 0; | |
104 | } | |
105 | ||
106 | static int rlimit_parse_size(const char *val, rlim_t *ret) { | |
107 | uint64_t u; | |
108 | int r; | |
109 | ||
110 | assert(val); | |
111 | assert(ret); | |
112 | ||
113 | if (streq(val, "infinity")) { | |
114 | *ret = RLIM_INFINITY; | |
115 | return 0; | |
116 | } | |
117 | ||
118 | r = parse_size(val, 1024, &u); | |
119 | if (r < 0) | |
120 | return r; | |
121 | if (u >= (uint64_t) RLIM_INFINITY) | |
122 | return -ERANGE; | |
123 | ||
124 | *ret = (rlim_t) u; | |
125 | return 0; | |
126 | } | |
127 | ||
128 | static int rlimit_parse_sec(const char *val, rlim_t *ret) { | |
129 | uint64_t u; | |
130 | usec_t t; | |
131 | int r; | |
132 | ||
133 | assert(val); | |
134 | assert(ret); | |
135 | ||
136 | if (streq(val, "infinity")) { | |
137 | *ret = RLIM_INFINITY; | |
138 | return 0; | |
139 | } | |
140 | ||
141 | r = parse_sec(val, &t); | |
142 | if (r < 0) | |
143 | return r; | |
144 | if (t == USEC_INFINITY) { | |
145 | *ret = RLIM_INFINITY; | |
146 | return 0; | |
147 | } | |
148 | ||
149 | u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC); | |
150 | if (u >= (uint64_t) RLIM_INFINITY) | |
151 | return -ERANGE; | |
152 | ||
153 | *ret = (rlim_t) u; | |
154 | return 0; | |
155 | } | |
156 | ||
157 | static int rlimit_parse_usec(const char *val, rlim_t *ret) { | |
158 | usec_t t; | |
159 | int r; | |
160 | ||
161 | assert(val); | |
162 | assert(ret); | |
163 | ||
164 | if (streq(val, "infinity")) { | |
165 | *ret = RLIM_INFINITY; | |
166 | return 0; | |
167 | } | |
168 | ||
169 | r = parse_time(val, &t, 1); | |
170 | if (r < 0) | |
171 | return r; | |
172 | if (t == USEC_INFINITY) { | |
173 | *ret = RLIM_INFINITY; | |
174 | return 0; | |
175 | } | |
176 | ||
177 | *ret = (rlim_t) t; | |
178 | return 0; | |
179 | } | |
180 | ||
181 | static int rlimit_parse_nice(const char *val, rlim_t *ret) { | |
182 | uint64_t rl; | |
183 | int r; | |
184 | ||
185 | /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the | |
186 | * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is | |
187 | * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight | |
188 | * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we | |
189 | * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0. | |
190 | * | |
191 | * Yeah, Linux is quality engineering sometimes... */ | |
192 | ||
193 | if (val[0] == '+') { | |
194 | ||
195 | /* Prefixed with "+": Parse as positive user-friendly nice value */ | |
196 | r = safe_atou64(val + 1, &rl); | |
197 | if (r < 0) | |
198 | return r; | |
199 | ||
200 | if (rl >= PRIO_MAX) | |
201 | return -ERANGE; | |
202 | ||
203 | rl = 20 - rl; | |
204 | ||
205 | } else if (val[0] == '-') { | |
206 | ||
207 | /* Prefixed with "-": Parse as negative user-friendly nice value */ | |
208 | r = safe_atou64(val + 1, &rl); | |
209 | if (r < 0) | |
210 | return r; | |
211 | ||
212 | if (rl > (uint64_t) (-PRIO_MIN)) | |
213 | return -ERANGE; | |
214 | ||
215 | rl = 20 + rl; | |
216 | } else { | |
217 | ||
218 | /* Not prefixed: parse as raw resource limit value */ | |
219 | r = safe_atou64(val, &rl); | |
220 | if (r < 0) | |
221 | return r; | |
222 | ||
223 | if (rl > (uint64_t) (20 - PRIO_MIN)) | |
224 | return -ERANGE; | |
225 | } | |
226 | ||
227 | *ret = (rlim_t) rl; | |
228 | return 0; | |
229 | } | |
230 | ||
231 | static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { | |
232 | [RLIMIT_CPU] = rlimit_parse_sec, | |
233 | [RLIMIT_FSIZE] = rlimit_parse_size, | |
234 | [RLIMIT_DATA] = rlimit_parse_size, | |
235 | [RLIMIT_STACK] = rlimit_parse_size, | |
236 | [RLIMIT_CORE] = rlimit_parse_size, | |
237 | [RLIMIT_RSS] = rlimit_parse_size, | |
238 | [RLIMIT_NOFILE] = rlimit_parse_u64, | |
239 | [RLIMIT_AS] = rlimit_parse_size, | |
240 | [RLIMIT_NPROC] = rlimit_parse_u64, | |
241 | [RLIMIT_MEMLOCK] = rlimit_parse_size, | |
242 | [RLIMIT_LOCKS] = rlimit_parse_u64, | |
243 | [RLIMIT_SIGPENDING] = rlimit_parse_u64, | |
244 | [RLIMIT_MSGQUEUE] = rlimit_parse_size, | |
245 | [RLIMIT_NICE] = rlimit_parse_nice, | |
246 | [RLIMIT_RTPRIO] = rlimit_parse_u64, | |
247 | [RLIMIT_RTTIME] = rlimit_parse_usec, | |
248 | }; | |
249 | ||
250 | int rlimit_parse_one(int resource, const char *val, rlim_t *ret) { | |
251 | assert(val); | |
252 | assert(ret); | |
253 | ||
254 | if (resource < 0) | |
255 | return -EINVAL; | |
256 | if (resource >= _RLIMIT_MAX) | |
257 | return -EINVAL; | |
258 | ||
259 | return rlimit_parse_table[resource](val, ret); | |
260 | } | |
261 | ||
262 | int rlimit_parse(int resource, const char *val, struct rlimit *ret) { | |
263 | _cleanup_free_ char *hard = NULL, *soft = NULL; | |
264 | rlim_t hl, sl; | |
265 | int r; | |
266 | ||
267 | assert(val); | |
268 | assert(ret); | |
269 | ||
270 | r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS); | |
271 | if (r < 0) | |
272 | return r; | |
273 | if (r == 0) | |
274 | return -EINVAL; | |
275 | ||
276 | r = rlimit_parse_one(resource, soft, &sl); | |
277 | if (r < 0) | |
278 | return r; | |
279 | ||
280 | r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS); | |
281 | if (r < 0) | |
282 | return r; | |
283 | if (!isempty(val)) | |
284 | return -EINVAL; | |
285 | if (r == 0) | |
286 | hl = sl; | |
287 | else { | |
288 | r = rlimit_parse_one(resource, hard, &hl); | |
289 | if (r < 0) | |
290 | return r; | |
291 | if (sl > hl) | |
292 | return -EILSEQ; | |
293 | } | |
294 | ||
295 | *ret = (struct rlimit) { | |
296 | .rlim_cur = sl, | |
297 | .rlim_max = hl, | |
298 | }; | |
299 | ||
300 | return 0; | |
301 | } | |
302 | ||
303 | int rlimit_format(const struct rlimit *rl, char **ret) { | |
304 | _cleanup_free_ char *s = NULL; | |
305 | int r; | |
306 | ||
307 | assert(rl); | |
308 | assert(ret); | |
309 | ||
310 | if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) | |
311 | r = free_and_strdup(&s, "infinity"); | |
312 | else if (rl->rlim_cur >= RLIM_INFINITY) | |
313 | r = asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); | |
314 | else if (rl->rlim_max >= RLIM_INFINITY) | |
315 | r = asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); | |
316 | else if (rl->rlim_cur == rl->rlim_max) | |
317 | r = asprintf(&s, RLIM_FMT, rl->rlim_cur); | |
318 | else | |
319 | r = asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); | |
320 | if (r < 0) | |
321 | return -ENOMEM; | |
322 | ||
323 | *ret = TAKE_PTR(s); | |
324 | return 0; | |
325 | } | |
326 | ||
327 | static const char* const rlimit_table[_RLIMIT_MAX] = { | |
328 | [RLIMIT_AS] = "AS", | |
329 | [RLIMIT_CORE] = "CORE", | |
330 | [RLIMIT_CPU] = "CPU", | |
331 | [RLIMIT_DATA] = "DATA", | |
332 | [RLIMIT_FSIZE] = "FSIZE", | |
333 | [RLIMIT_LOCKS] = "LOCKS", | |
334 | [RLIMIT_MEMLOCK] = "MEMLOCK", | |
335 | [RLIMIT_MSGQUEUE] = "MSGQUEUE", | |
336 | [RLIMIT_NICE] = "NICE", | |
337 | [RLIMIT_NOFILE] = "NOFILE", | |
338 | [RLIMIT_NPROC] = "NPROC", | |
339 | [RLIMIT_RSS] = "RSS", | |
340 | [RLIMIT_RTPRIO] = "RTPRIO", | |
341 | [RLIMIT_RTTIME] = "RTTIME", | |
342 | [RLIMIT_SIGPENDING] = "SIGPENDING", | |
343 | [RLIMIT_STACK] = "STACK", | |
344 | }; | |
345 | ||
346 | DEFINE_STRING_TABLE_LOOKUP(rlimit, int); | |
347 | ||
348 | int rlimit_from_string_harder(const char *s) { | |
349 | const char *suffix; | |
350 | ||
351 | /* The official prefix */ | |
352 | suffix = startswith(s, "RLIMIT_"); | |
353 | if (suffix) | |
354 | return rlimit_from_string(suffix); | |
355 | ||
356 | /* Our own unit file setting prefix */ | |
357 | suffix = startswith(s, "Limit"); | |
358 | if (suffix) | |
359 | return rlimit_from_string(suffix); | |
360 | ||
361 | return rlimit_from_string(s); | |
362 | } | |
363 | ||
364 | void rlimit_free_all(struct rlimit **rl) { | |
365 | free_many((void**) rl, _RLIMIT_MAX); | |
366 | } | |
367 | ||
368 | int rlimit_copy_all(struct rlimit* target[static _RLIMIT_MAX], struct rlimit* const source[static _RLIMIT_MAX]) { | |
369 | struct rlimit* copy[_RLIMIT_MAX] = {}; | |
370 | ||
371 | assert(target); | |
372 | assert(source); | |
373 | ||
374 | for (int i = 0; i < _RLIMIT_MAX; i++) { | |
375 | if (!source[i]) | |
376 | continue; | |
377 | ||
378 | copy[i] = newdup(struct rlimit, source[i], 1); | |
379 | if (!copy[i]) { | |
380 | rlimit_free_all(copy); | |
381 | return -ENOMEM; | |
382 | } | |
383 | } | |
384 | ||
385 | memcpy(target, copy, sizeof(struct rlimit*) * _RLIMIT_MAX); | |
386 | return 0; | |
387 | } | |
388 | ||
389 | int rlimit_nofile_bump(int limit) { | |
390 | int r; | |
391 | ||
392 | /* Bumps the (soft) RLIMIT_NOFILE resource limit as close as possible to the specified limit. If a negative | |
393 | * limit is specified, bumps it to the maximum the kernel and the hard resource limit allows. This call should | |
394 | * be used by all our programs that might need a lot of fds, and that know how to deal with high fd numbers | |
395 | * (i.e. do not use select() — which chokes on fds >= 1024) */ | |
396 | ||
397 | if (limit < 0) | |
398 | limit = read_nr_open(); | |
399 | ||
400 | if (limit < 3) | |
401 | limit = 3; | |
402 | ||
403 | r = setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(limit)); | |
404 | if (r < 0) | |
405 | return log_debug_errno(r, "Failed to set RLIMIT_NOFILE: %m"); | |
406 | ||
407 | return 0; | |
408 | } | |
409 | ||
410 | int rlimit_nofile_safe(void) { | |
411 | struct rlimit rl; | |
412 | ||
413 | /* Resets RLIMIT_NOFILE's soft limit FD_SETSIZE (i.e. 1024), for compatibility with software still using | |
414 | * select() */ | |
415 | ||
416 | if (getrlimit(RLIMIT_NOFILE, &rl) < 0) | |
417 | return log_debug_errno(errno, "Failed to query RLIMIT_NOFILE: %m"); | |
418 | ||
419 | if (rl.rlim_cur <= FD_SETSIZE) | |
420 | return 0; | |
421 | ||
422 | /* So we might have inherited a hard limit that's larger than the kernel's maximum limit as stored in | |
423 | * /proc/sys/fs/nr_open. If we pass this hard limit unmodified to setrlimit(), we'll get EPERM. To | |
424 | * make sure that doesn't happen, let's limit our hard limit to the value from nr_open. */ | |
425 | rl.rlim_max = MIN(rl.rlim_max, (rlim_t) read_nr_open()); | |
426 | rl.rlim_cur = MIN((rlim_t) FD_SETSIZE, rl.rlim_max); | |
427 | if (setrlimit(RLIMIT_NOFILE, &rl) < 0) | |
428 | return log_debug_errno(errno, "Failed to lower RLIMIT_NOFILE's soft limit to " RLIM_FMT ": %m", rl.rlim_cur); | |
429 | ||
430 | return 1; | |
431 | } | |
432 | ||
433 | int pid_getrlimit(pid_t pid, int resource, struct rlimit *ret) { | |
434 | ||
435 | static const char * const prefix_table[_RLIMIT_MAX] = { | |
436 | [RLIMIT_CPU] = "Max cpu time", | |
437 | [RLIMIT_FSIZE] = "Max file size", | |
438 | [RLIMIT_DATA] = "Max data size", | |
439 | [RLIMIT_STACK] = "Max stack size", | |
440 | [RLIMIT_CORE] = "Max core file size", | |
441 | [RLIMIT_RSS] = "Max resident set", | |
442 | [RLIMIT_NPROC] = "Max processes", | |
443 | [RLIMIT_NOFILE] = "Max open files", | |
444 | [RLIMIT_MEMLOCK] = "Max locked memory", | |
445 | [RLIMIT_AS] = "Max address space", | |
446 | [RLIMIT_LOCKS] = "Max file locks", | |
447 | [RLIMIT_SIGPENDING] = "Max pending signals", | |
448 | [RLIMIT_MSGQUEUE] = "Max msgqueue size", | |
449 | [RLIMIT_NICE] = "Max nice priority", | |
450 | [RLIMIT_RTPRIO] = "Max realtime priority", | |
451 | [RLIMIT_RTTIME] = "Max realtime timeout", | |
452 | }; | |
453 | ||
454 | int r; | |
455 | ||
456 | assert(resource >= 0); | |
457 | assert(resource < _RLIMIT_MAX); | |
458 | assert(pid >= 0); | |
459 | assert(ret); | |
460 | ||
461 | if (pid == 0 || pid == getpid_cached()) | |
462 | return RET_NERRNO(getrlimit(resource, ret)); | |
463 | ||
464 | r = RET_NERRNO(prlimit(pid, resource, /* new_limit= */ NULL, ret)); | |
465 | if (!ERRNO_IS_NEG_PRIVILEGE(r)) | |
466 | return r; | |
467 | ||
468 | /* We don't have access? Then try to go via /proc/$PID/limits. Weirdly that's world readable in | |
469 | * contrast to querying the data via prlimit() */ | |
470 | ||
471 | const char *p = procfs_file_alloca(pid, "limits"); | |
472 | _cleanup_free_ char *limits = NULL; | |
473 | ||
474 | r = read_full_virtual_file(p, &limits, NULL); | |
475 | if (r < 0) | |
476 | return -EPERM; /* propagate original permission error if we can't access the limits file */ | |
477 | ||
478 | _cleanup_strv_free_ char **l = NULL; | |
479 | l = strv_split(limits, "\n"); | |
480 | if (!l) | |
481 | return -ENOMEM; | |
482 | ||
483 | STRV_FOREACH(i, strv_skip(l, 1)) { | |
484 | _cleanup_free_ char *soft = NULL, *hard = NULL; | |
485 | uint64_t sv, hv; | |
486 | const char *e; | |
487 | ||
488 | e = startswith(*i, prefix_table[resource]); | |
489 | if (!e) | |
490 | continue; | |
491 | ||
492 | if (*e != ' ') | |
493 | continue; | |
494 | ||
495 | e += strspn(e, WHITESPACE); | |
496 | ||
497 | size_t n; | |
498 | n = strcspn(e, WHITESPACE); | |
499 | if (n == 0) | |
500 | continue; | |
501 | ||
502 | soft = strndup(e, n); | |
503 | if (!soft) | |
504 | return -ENOMEM; | |
505 | ||
506 | e += n; | |
507 | if (*e != ' ') | |
508 | continue; | |
509 | ||
510 | e += strspn(e, WHITESPACE); | |
511 | n = strcspn(e, WHITESPACE); | |
512 | if (n == 0) | |
513 | continue; | |
514 | ||
515 | hard = strndup(e, n); | |
516 | if (!hard) | |
517 | return -ENOMEM; | |
518 | ||
519 | if (streq(soft, "unlimited")) | |
520 | sv = RLIM_INFINITY; | |
521 | else { | |
522 | r = safe_atou64(soft, &sv); | |
523 | if (r < 0) | |
524 | return r; | |
525 | } | |
526 | ||
527 | if (streq(hard, "unlimited")) | |
528 | hv = RLIM_INFINITY; | |
529 | else { | |
530 | r = safe_atou64(hard, &hv); | |
531 | if (r < 0) | |
532 | return r; | |
533 | } | |
534 | ||
535 | *ret = (struct rlimit) { | |
536 | .rlim_cur = sv, | |
537 | .rlim_max = hv, | |
538 | }; | |
539 | ||
540 | return 0; | |
541 | } | |
542 | ||
543 | return -ENOTRECOVERABLE; | |
544 | } |