chiark / gitweb /
util: rework system_tasks_max() to make use of procfs_tasks_max()
[elogind.git] / src / basic / util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2010 Lennart Poettering
6
7   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <alloca.h>
22 //#include <errno.h>
23 //#include <fcntl.h>
24 #include <sched.h>
25 //#include <signal.h>
26 //#include <stdarg.h>
27 //#include <stdio.h>
28 #include <stdlib.h>
29 //#include <string.h>
30 //#include <sys/mman.h>
31 #include <sys/prctl.h>
32 #include <sys/statfs.h>
33 #include <sys/sysmacros.h>
34 //#include <sys/types.h>
35 //#include <unistd.h>
36
37 #include "alloc-util.h"
38 //#include "btrfs-util.h"
39 #include "build.h"
40 #include "cgroup-util.h"
41 //#include "def.h"
42 //#include "device-nodes.h"
43 #include "dirent-util.h"
44 #include "fd-util.h"
45 #include "fileio.h"
46 //#include "format-util.h"
47 #include "hashmap.h"
48 #include "hostname-util.h"
49 //#include "log.h"
50 #include "macro.h"
51 //#include "missing.h"
52 #include "parse-util.h"
53 //#include "path-util.h"
54 #include "process-util.h"
55 //#include "procfs-util.h"
56 #include "set.h"
57 #include "signal-util.h"
58 #include "stat-util.h"
59 #include "string-util.h"
60 #include "strv.h"
61 #include "time-util.h"
62 #include "umask-util.h"
63 #include "user-util.h"
64 #include "util.h"
65 //#include "virt.h"
66
67 int saved_argc = 0;
68 char **saved_argv = NULL;
69 static int saved_in_initrd = -1;
70
71 size_t page_size(void) {
72         static thread_local size_t pgsz = 0;
73         long r;
74
75         if (_likely_(pgsz > 0))
76                 return pgsz;
77
78         r = sysconf(_SC_PAGESIZE);
79         assert(r > 0);
80
81         pgsz = (size_t) r;
82         return pgsz;
83 }
84
85 #if 0 /// UNNEEDED by elogind
86 bool plymouth_running(void) {
87         return access("/run/plymouth/pid", F_OK) >= 0;
88 }
89 #endif // 0
90
91 bool display_is_local(const char *display) {
92         assert(display);
93
94         return
95                 display[0] == ':' &&
96                 display[1] >= '0' &&
97                 display[1] <= '9';
98 }
99
100 int socket_from_display(const char *display, char **path) {
101         size_t k;
102         char *f, *c;
103
104         assert(display);
105         assert(path);
106
107         if (!display_is_local(display))
108                 return -EINVAL;
109
110         k = strspn(display+1, "0123456789");
111
112         f = new(char, STRLEN("/tmp/.X11-unix/X") + k + 1);
113         if (!f)
114                 return -ENOMEM;
115
116         c = stpcpy(f, "/tmp/.X11-unix/X");
117         memcpy(c, display+1, k);
118         c[k] = 0;
119
120         *path = f;
121
122         return 0;
123 }
124
125 #if 0 /// UNNEEDED by elogind
126 bool kexec_loaded(void) {
127        _cleanup_free_ char *s = NULL;
128
129        if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0)
130                return false;
131
132        return s[0] == '1';
133 }
134
135 int prot_from_flags(int flags) {
136
137         switch (flags & O_ACCMODE) {
138
139         case O_RDONLY:
140                 return PROT_READ;
141
142         case O_WRONLY:
143                 return PROT_WRITE;
144
145         case O_RDWR:
146                 return PROT_READ|PROT_WRITE;
147
148         default:
149                 return -EINVAL;
150         }
151 }
152 #endif // 0
153
154 bool in_initrd(void) {
155         struct statfs s;
156
157         if (saved_in_initrd >= 0)
158                 return saved_in_initrd;
159
160         /* We make two checks here:
161          *
162          * 1. the flag file /etc/initrd-release must exist
163          * 2. the root file system must be a memory file system
164          *
165          * The second check is extra paranoia, since misdetecting an
166          * initrd can have bad consequences due the initrd
167          * emptying when transititioning to the main systemd.
168          */
169
170         saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
171                           statfs("/", &s) >= 0 &&
172                           is_temporary_fs(&s);
173
174         return saved_in_initrd;
175 }
176
177 void in_initrd_force(bool value) {
178         saved_in_initrd = value;
179 }
180
181 #if 0 /// UNNEEDED by elogind
182 /* hey glibc, APIs with callbacks without a user pointer are so useless */
183 void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
184                  int (*compar) (const void *, const void *, void *), void *arg) {
185         size_t l, u, idx;
186         const void *p;
187         int comparison;
188
189         l = 0;
190         u = nmemb;
191         while (l < u) {
192                 idx = (l + u) / 2;
193                 p = (const char *) base + idx * size;
194                 comparison = compar(key, p, arg);
195                 if (comparison < 0)
196                         u = idx;
197                 else if (comparison > 0)
198                         l = idx + 1;
199                 else
200                         return (void *)p;
201         }
202         return NULL;
203 }
204
205 int on_ac_power(void) {
206         bool found_offline = false, found_online = false;
207         _cleanup_closedir_ DIR *d = NULL;
208         struct dirent *de;
209
210         d = opendir("/sys/class/power_supply");
211         if (!d)
212                 return errno == ENOENT ? true : -errno;
213
214         FOREACH_DIRENT(de, d, return -errno) {
215                 _cleanup_close_ int fd = -1, device = -1;
216                 char contents[6];
217                 ssize_t n;
218
219                 device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY);
220                 if (device < 0) {
221                         if (IN_SET(errno, ENOENT, ENOTDIR))
222                                 continue;
223
224                         return -errno;
225                 }
226
227                 fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY);
228                 if (fd < 0) {
229                         if (errno == ENOENT)
230                                 continue;
231
232                         return -errno;
233                 }
234
235                 n = read(fd, contents, sizeof(contents));
236                 if (n < 0)
237                         return -errno;
238
239                 if (n != 6 || memcmp(contents, "Mains\n", 6))
240                         continue;
241
242                 safe_close(fd);
243                 fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY);
244                 if (fd < 0) {
245                         if (errno == ENOENT)
246                                 continue;
247
248                         return -errno;
249                 }
250
251                 n = read(fd, contents, sizeof(contents));
252                 if (n < 0)
253                         return -errno;
254
255                 if (n != 2 || contents[1] != '\n')
256                         return -EIO;
257
258                 if (contents[0] == '1') {
259                         found_online = true;
260                         break;
261                 } else if (contents[0] == '0')
262                         found_offline = true;
263                 else
264                         return -EIO;
265         }
266
267         return found_online || !found_offline;
268 }
269
270 #endif // 0
271 int container_get_leader(const char *machine, pid_t *pid) {
272         _cleanup_free_ char *s = NULL, *class = NULL;
273         const char *p;
274         pid_t leader;
275         int r;
276
277         assert(machine);
278         assert(pid);
279
280         if (!machine_name_is_valid(machine))
281                 return -EINVAL;
282
283         p = strjoina("/run/systemd/machines/", machine);
284         r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
285         if (r == -ENOENT)
286                 return -EHOSTDOWN;
287         if (r < 0)
288                 return r;
289         if (!s)
290                 return -EIO;
291
292         if (!streq_ptr(class, "container"))
293                 return -EIO;
294
295         r = parse_pid(s, &leader);
296         if (r < 0)
297                 return r;
298         if (leader <= 1)
299                 return -EIO;
300
301         *pid = leader;
302         return 0;
303 }
304
305 int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) {
306         _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1;
307         int rfd = -1;
308
309         assert(pid >= 0);
310
311         if (mntns_fd) {
312                 const char *mntns;
313
314                 mntns = procfs_file_alloca(pid, "ns/mnt");
315                 mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
316                 if (mntnsfd < 0)
317                         return -errno;
318         }
319
320         if (pidns_fd) {
321                 const char *pidns;
322
323                 pidns = procfs_file_alloca(pid, "ns/pid");
324                 pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
325                 if (pidnsfd < 0)
326                         return -errno;
327         }
328
329         if (netns_fd) {
330                 const char *netns;
331
332                 netns = procfs_file_alloca(pid, "ns/net");
333                 netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
334                 if (netnsfd < 0)
335                         return -errno;
336         }
337
338         if (userns_fd) {
339                 const char *userns;
340
341                 userns = procfs_file_alloca(pid, "ns/user");
342                 usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
343                 if (usernsfd < 0 && errno != ENOENT)
344                         return -errno;
345         }
346
347         if (root_fd) {
348                 const char *root;
349
350                 root = procfs_file_alloca(pid, "root");
351                 rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
352                 if (rfd < 0)
353                         return -errno;
354         }
355
356         if (pidns_fd)
357                 *pidns_fd = pidnsfd;
358
359         if (mntns_fd)
360                 *mntns_fd = mntnsfd;
361
362         if (netns_fd)
363                 *netns_fd = netnsfd;
364
365         if (userns_fd)
366                 *userns_fd = usernsfd;
367
368         if (root_fd)
369                 *root_fd = rfd;
370
371         pidnsfd = mntnsfd = netnsfd = usernsfd = -1;
372
373         return 0;
374 }
375
376 int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
377         if (userns_fd >= 0) {
378                 /* Can't setns to your own userns, since then you could
379                  * escalate from non-root to root in your own namespace, so
380                  * check if namespaces equal before attempting to enter. */
381                 _cleanup_free_ char *userns_fd_path = NULL;
382                 int r;
383                 if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0)
384                         return -ENOMEM;
385
386                 r = files_same(userns_fd_path, "/proc/self/ns/user", 0);
387                 if (r < 0)
388                         return r;
389                 if (r)
390                         userns_fd = -1;
391         }
392
393         if (pidns_fd >= 0)
394                 if (setns(pidns_fd, CLONE_NEWPID) < 0)
395                         return -errno;
396
397         if (mntns_fd >= 0)
398                 if (setns(mntns_fd, CLONE_NEWNS) < 0)
399                         return -errno;
400
401         if (netns_fd >= 0)
402                 if (setns(netns_fd, CLONE_NEWNET) < 0)
403                         return -errno;
404
405         if (userns_fd >= 0)
406                 if (setns(userns_fd, CLONE_NEWUSER) < 0)
407                         return -errno;
408
409         if (root_fd >= 0) {
410                 if (fchdir(root_fd) < 0)
411                         return -errno;
412
413                 if (chroot(".") < 0)
414                         return -errno;
415         }
416
417         return reset_uid_gid();
418 }
419
420 uint64_t physical_memory(void) {
421         _cleanup_free_ char *root = NULL, *value = NULL;
422         uint64_t mem, lim;
423         size_t ps;
424         long sc;
425
426         /* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
427          * memory.
428          *
429          * In order to support containers nicely that have a configured memory limit we'll take the minimum of the
430          * physically reported amount of memory and the limit configured for the root cgroup, if there is any. */
431
432         sc = sysconf(_SC_PHYS_PAGES);
433         assert(sc > 0);
434
435         ps = page_size();
436         mem = (uint64_t) sc * (uint64_t) ps;
437
438         if (cg_get_root_path(&root) < 0)
439                 return mem;
440
441         if (cg_get_attribute("memory", root, "memory.limit_in_bytes", &value))
442                 return mem;
443
444         if (safe_atou64(value, &lim) < 0)
445                 return mem;
446
447         /* Make sure the limit is a multiple of our own page size */
448         lim /= ps;
449         lim *= ps;
450
451         return MIN(mem, lim);
452 }
453
454 uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
455         uint64_t p, m, ps, r;
456
457         assert(max > 0);
458
459         /* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success
460          * the result is a multiple of the page size (rounds down). */
461
462         ps = page_size();
463         assert(ps > 0);
464
465         p = physical_memory() / ps;
466         assert(p > 0);
467
468         m = p * v;
469         if (m / p != v)
470                 return UINT64_MAX;
471
472         m /= max;
473
474         r = m * ps;
475         if (r / ps != m)
476                 return UINT64_MAX;
477
478         return r;
479 }
480
481 uint64_t system_tasks_max(void) {
482
483 #else
484         uint64_t a = TASKS_MAX, b = TASKS_MAX;
485         _cleanup_free_ char *root = NULL;
486
487         /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
488          * limit:
489          *
490          * a) the maximum tasks value the kernel allows on this architecture
491          * b) the cgroups pids_max attribute for the system
492          * c) the kernel's configured maximum PID value
493          *
494          * And then pick the smallest of the three */
495
496         (void) procfs_tasks_get_limit(&a);
497
498         if (cg_get_root_path(&root) >= 0) {
499                 _cleanup_free_ char *value = NULL;
500
501                 if (cg_get_attribute("pids", root, "pids.max", &value) >= 0)
502                         (void) safe_atou64(value, &b);
503         }
504
505         return MIN3(TASKS_MAX,
506                     a <= 0 ? TASKS_MAX : a,
507                     b <= 0 ? TASKS_MAX : b);
508 }
509
510 uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
511         uint64_t t, m;
512
513         assert(max > 0);
514
515         /* Multiply the system's task value by the fraction v/max. Hence, if max==100 this calculates percentages
516          * relative to the system's maximum number of tasks. Returns UINT64_MAX on overflow. */
517
518         t = system_tasks_max();
519         assert(t > 0);
520
521         m = t * v;
522         if (m / t != v) /* overflow? */
523                 return UINT64_MAX;
524
525         return m / max;
526 }
527
528 #if 0 /// UNNEEDED by elogind
529 int update_reboot_parameter_and_warn(const char *param) {
530         int r;
531
532         if (isempty(param)) {
533                 if (unlink("/run/systemd/reboot-param") < 0) {
534                         if (errno == ENOENT)
535                                 return 0;
536
537                         return log_warning_errno(errno, "Failed to unlink reboot parameter file: %m");
538                 }
539
540                 return 0;
541         }
542
543         RUN_WITH_UMASK(0022) {
544                 r = write_string_file("/run/systemd/reboot-param", param, WRITE_STRING_FILE_CREATE);
545                 if (r < 0)
546                         return log_warning_errno(r, "Failed to write reboot parameter file: %m");
547         }
548
549         return 0;
550 }
551 #endif // 0
552
553 int version(void) {
554         puts(PACKAGE_STRING "\n"
555              SYSTEMD_FEATURES);
556         return 0;
557 }
558
559 #if 0 /// UNNEEDED by elogind
560 #endif // 0
561 /* This is a direct translation of str_verscmp from boot.c */
562 static bool is_digit(int c) {
563         return c >= '0' && c <= '9';
564 }
565
566 static int c_order(int c) {
567         if (c == 0 || is_digit(c))
568                 return 0;
569
570         if ((c >= 'a') && (c <= 'z'))
571                 return c;
572
573         return c + 0x10000;
574 }
575
576 int str_verscmp(const char *s1, const char *s2) {
577         const char *os1, *os2;
578
579         assert(s1);
580         assert(s2);
581
582         os1 = s1;
583         os2 = s2;
584
585         while (*s1 || *s2) {
586                 int first;
587
588                 while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
589                         int order;
590
591                         order = c_order(*s1) - c_order(*s2);
592                         if (order != 0)
593                                 return order;
594                         s1++;
595                         s2++;
596                 }
597
598                 while (*s1 == '0')
599                         s1++;
600                 while (*s2 == '0')
601                         s2++;
602
603                 first = 0;
604                 while (is_digit(*s1) && is_digit(*s2)) {
605                         if (first == 0)
606                                 first = *s1 - *s2;
607                         s1++;
608                         s2++;
609                 }
610
611                 if (is_digit(*s1))
612                         return 1;
613                 if (is_digit(*s2))
614                         return -1;
615
616                 if (first != 0)
617                         return first;
618         }
619
620         return strcmp(os1, os2);
621 }
622
623 /* Turn off core dumps but only if we're running outside of a container. */
624 void disable_coredumps(void) {
625         int r;
626
627         if (detect_container() > 0)
628                 return;
629
630         r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0);
631         if (r < 0)
632                 log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m");
633 }