]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/rlimit-util.c
util: introduce memcmp_safe()
[thirdparty/systemd.git] / src / basic / rlimit-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
78f22b97 2
11c3a366
TA
3#include <errno.h>
4#include <sys/resource.h>
5
d0a7c5f6
LP
6#include "alloc-util.h"
7#include "extract-word.h"
f97b34a6 8#include "format-util.h"
93cc7779 9#include "macro.h"
78f22b97
LP
10#include "missing.h"
11#include "rlimit-util.h"
8b43440b 12#include "string-table.h"
d0a7c5f6 13#include "time-util.h"
78f22b97
LP
14
15int setrlimit_closest(int resource, const struct rlimit *rlim) {
16 struct rlimit highest, fixed;
17
18 assert(rlim);
19
20 if (setrlimit(resource, rlim) >= 0)
21 return 0;
22
23 if (errno != EPERM)
24 return -errno;
25
26 /* So we failed to set the desired setrlimit, then let's try
27 * to get as close as we can */
c4ad3f43
LP
28 if (getrlimit(resource, &highest) < 0)
29 return -errno;
78f22b97 30
114c55f2
LP
31 /* If the hard limit is unbounded anyway, then the EPERM had other reasons, let's propagate the original EPERM
32 * then */
33 if (highest.rlim_max == RLIM_INFINITY)
34 return -EPERM;
35
78f22b97
LP
36 fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max);
37 fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max);
38
39 if (setrlimit(resource, &fixed) < 0)
40 return -errno;
41
42 return 0;
43}
44
34a5df58
LP
45int setrlimit_closest_all(const struct rlimit *const *rlim, int *which_failed) {
46 int i, r;
47
48 assert(rlim);
49
50 /* On failure returns the limit's index that failed in *which_failed, but only if non-NULL */
51
52 for (i = 0; i < _RLIMIT_MAX; i++) {
53 if (!rlim[i])
54 continue;
55
56 r = setrlimit_closest(i, rlim[i]);
57 if (r < 0) {
58 if (which_failed)
59 *which_failed = i;
60
61 return r;
62 }
63 }
64
65 if (which_failed)
66 *which_failed = -1;
67
68 return 0;
69}
70
d0a7c5f6
LP
71static int rlimit_parse_u64(const char *val, rlim_t *ret) {
72 uint64_t u;
73 int r;
74
75 assert(val);
76 assert(ret);
77
78 if (streq(val, "infinity")) {
79 *ret = RLIM_INFINITY;
80 return 0;
81 }
82
83 /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */
84 assert_cc(sizeof(rlim_t) == sizeof(uint64_t));
85
86 r = safe_atou64(val, &u);
87 if (r < 0)
88 return r;
89 if (u >= (uint64_t) RLIM_INFINITY)
90 return -ERANGE;
91
92 *ret = (rlim_t) u;
93 return 0;
94}
95
96static int rlimit_parse_size(const char *val, rlim_t *ret) {
97 uint64_t u;
98 int r;
99
100 assert(val);
101 assert(ret);
102
103 if (streq(val, "infinity")) {
104 *ret = RLIM_INFINITY;
105 return 0;
106 }
107
108 r = parse_size(val, 1024, &u);
109 if (r < 0)
110 return r;
111 if (u >= (uint64_t) RLIM_INFINITY)
112 return -ERANGE;
113
114 *ret = (rlim_t) u;
115 return 0;
116}
117
118static int rlimit_parse_sec(const char *val, rlim_t *ret) {
119 uint64_t u;
120 usec_t t;
121 int r;
122
123 assert(val);
124 assert(ret);
125
126 if (streq(val, "infinity")) {
127 *ret = RLIM_INFINITY;
128 return 0;
129 }
130
131 r = parse_sec(val, &t);
132 if (r < 0)
133 return r;
134 if (t == USEC_INFINITY) {
135 *ret = RLIM_INFINITY;
136 return 0;
137 }
138
139 u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC);
140 if (u >= (uint64_t) RLIM_INFINITY)
141 return -ERANGE;
142
143 *ret = (rlim_t) u;
144 return 0;
145}
146
147static int rlimit_parse_usec(const char *val, rlim_t *ret) {
148 usec_t t;
149 int r;
150
151 assert(val);
152 assert(ret);
153
154 if (streq(val, "infinity")) {
155 *ret = RLIM_INFINITY;
156 return 0;
157 }
158
159 r = parse_time(val, &t, 1);
160 if (r < 0)
161 return r;
162 if (t == USEC_INFINITY) {
163 *ret = RLIM_INFINITY;
164 return 0;
165 }
166
167 *ret = (rlim_t) t;
168 return 0;
169}
170
29857001
LP
171static int rlimit_parse_nice(const char *val, rlim_t *ret) {
172 uint64_t rl;
173 int r;
174
175 /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the
176 * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is
177 * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight
178 * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we
179 * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0.
180 *
181 * Yeah, Linux is quality engineering sometimes... */
182
183 if (val[0] == '+') {
184
185 /* Prefixed with "+": Parse as positive user-friendly nice value */
186 r = safe_atou64(val + 1, &rl);
187 if (r < 0)
188 return r;
189
190 if (rl >= PRIO_MAX)
191 return -ERANGE;
192
193 rl = 20 - rl;
194
195 } else if (val[0] == '-') {
196
197 /* Prefixed with "-": Parse as negative user-friendly nice value */
198 r = safe_atou64(val + 1, &rl);
199 if (r < 0)
200 return r;
201
202 if (rl > (uint64_t) (-PRIO_MIN))
203 return -ERANGE;
204
205 rl = 20 + rl;
206 } else {
207
208 /* Not prefixed: parse as raw resource limit value */
209 r = safe_atou64(val, &rl);
210 if (r < 0)
211 return r;
212
213 if (rl > (uint64_t) (20 - PRIO_MIN))
214 return -ERANGE;
215 }
216
217 *ret = (rlim_t) rl;
218 return 0;
219}
220
d0a7c5f6
LP
221static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = {
222 [RLIMIT_CPU] = rlimit_parse_sec,
223 [RLIMIT_FSIZE] = rlimit_parse_size,
224 [RLIMIT_DATA] = rlimit_parse_size,
225 [RLIMIT_STACK] = rlimit_parse_size,
226 [RLIMIT_CORE] = rlimit_parse_size,
227 [RLIMIT_RSS] = rlimit_parse_size,
228 [RLIMIT_NOFILE] = rlimit_parse_u64,
229 [RLIMIT_AS] = rlimit_parse_size,
230 [RLIMIT_NPROC] = rlimit_parse_u64,
231 [RLIMIT_MEMLOCK] = rlimit_parse_size,
232 [RLIMIT_LOCKS] = rlimit_parse_u64,
233 [RLIMIT_SIGPENDING] = rlimit_parse_u64,
234 [RLIMIT_MSGQUEUE] = rlimit_parse_size,
29857001 235 [RLIMIT_NICE] = rlimit_parse_nice,
d0a7c5f6
LP
236 [RLIMIT_RTPRIO] = rlimit_parse_u64,
237 [RLIMIT_RTTIME] = rlimit_parse_usec,
238};
239
240int rlimit_parse_one(int resource, const char *val, rlim_t *ret) {
241 assert(val);
242 assert(ret);
243
244 if (resource < 0)
245 return -EINVAL;
246 if (resource >= _RLIMIT_MAX)
247 return -EINVAL;
248
249 return rlimit_parse_table[resource](val, ret);
250}
251
252int rlimit_parse(int resource, const char *val, struct rlimit *ret) {
253 _cleanup_free_ char *hard = NULL, *soft = NULL;
254 rlim_t hl, sl;
255 int r;
256
257 assert(val);
258 assert(ret);
259
260 r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
261 if (r < 0)
262 return r;
263 if (r == 0)
264 return -EINVAL;
265
266 r = rlimit_parse_one(resource, soft, &sl);
267 if (r < 0)
268 return r;
269
270 r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
271 if (r < 0)
272 return r;
273 if (!isempty(val))
274 return -EINVAL;
275 if (r == 0)
276 hl = sl;
277 else {
278 r = rlimit_parse_one(resource, hard, &hl);
279 if (r < 0)
280 return r;
281 if (sl > hl)
282 return -EILSEQ;
283 }
284
285 *ret = (struct rlimit) {
286 .rlim_cur = sl,
287 .rlim_max = hl,
288 };
289
290 return 0;
291}
292
99d4f5e5
LP
293int rlimit_format(const struct rlimit *rl, char **ret) {
294 char *s = NULL;
295
296 assert(rl);
297 assert(ret);
298
299 if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY)
300 s = strdup("infinity");
301 else if (rl->rlim_cur >= RLIM_INFINITY)
302 (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max);
303 else if (rl->rlim_max >= RLIM_INFINITY)
304 (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur);
305 else if (rl->rlim_cur == rl->rlim_max)
306 (void) asprintf(&s, RLIM_FMT, rl->rlim_cur);
307 else
308 (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max);
309
310 if (!s)
311 return -ENOMEM;
312
313 *ret = s;
314 return 0;
315}
316
78f22b97 317static const char* const rlimit_table[_RLIMIT_MAX] = {
6550c24c
LP
318 [RLIMIT_AS] = "AS",
319 [RLIMIT_CORE] = "CORE",
320 [RLIMIT_CPU] = "CPU",
321 [RLIMIT_DATA] = "DATA",
322 [RLIMIT_FSIZE] = "FSIZE",
323 [RLIMIT_LOCKS] = "LOCKS",
324 [RLIMIT_MEMLOCK] = "MEMLOCK",
325 [RLIMIT_MSGQUEUE] = "MSGQUEUE",
326 [RLIMIT_NICE] = "NICE",
327 [RLIMIT_NOFILE] = "NOFILE",
328 [RLIMIT_NPROC] = "NPROC",
329 [RLIMIT_RSS] = "RSS",
330 [RLIMIT_RTPRIO] = "RTPRIO",
331 [RLIMIT_RTTIME] = "RTTIME",
332 [RLIMIT_SIGPENDING] = "SIGPENDING",
333 [RLIMIT_STACK] = "STACK",
78f22b97
LP
334};
335
336DEFINE_STRING_TABLE_LOOKUP(rlimit, int);
6550c24c
LP
337
338int rlimit_from_string_harder(const char *s) {
339 const char *suffix;
340
341 /* The official prefix */
342 suffix = startswith(s, "RLIMIT_");
343 if (suffix)
344 return rlimit_from_string(suffix);
345
346 /* Our own unit file setting prefix */
347 suffix = startswith(s, "Limit");
348 if (suffix)
349 return rlimit_from_string(suffix);
350
351 return rlimit_from_string(s);
352}
31ce987c
LP
353
354void rlimit_free_all(struct rlimit **rl) {
355 int i;
356
357 if (!rl)
358 return;
359
360 for (i = 0; i < _RLIMIT_MAX; i++)
361 rl[i] = mfree(rl[i]);
362}