==> src/limits.c <==1/* native.rs's Limits, limits_available and check_cgroup. */
2#include "mesh/native_limits.h"
3#include "mesh/log.h"
4#include "mesh/text.h"
5
6#include "mesh/abi/control_block.h"
7
8#include <errno.h>
9#include <fcntl.h>
10#include <pthread.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sys/wait.h>
15#include <time.h>
16#include <unistd.h>
17
18mesh_native_limits mesh_native_limits_of(const mesh_manifest *m) {
19 return (mesh_native_limits){m->cpu_pct, m->mem, m->nice, m->background};
20}
21
22bool mesh_native_limits_scoped(const mesh_native_limits *l) { return l->cpu_pct > 0 || l->mem > 0; }
23
24bool mesh_native_limits_eq(const mesh_native_limits *a, const mesh_native_limits *b) {
25 return a->cpu_pct == b->cpu_pct && a->mem == b->mem && a->nice == b->nice && a->background == b->background;
26}
27
28uint32_t mesh_native_limits_bits(const mesh_native_limits *l) {
29 return (l->cpu_pct > 0 ? MESH_LIMIT_CPU : 0u) | (l->mem > 0 ? MESH_LIMIT_MEM : 0u) | (l->nice > 0 ? MESH_LIMIT_NICE : 0u) |
30 (l->background ? MESH_LIMIT_BG : 0u);
31}
32
33char *mesh_native_limits_debug(const mesh_native_limits *l) {
34 return mesh_fmt("Limits { cpu_pct: %u, mem: %llu, nice: %u, background: %s }", (unsigned)l->cpu_pct, (unsigned long long)l->mem,
35 (unsigned)l->nice, l->background ? "true" : "false");
36}
37
38static pthread_mutex_t probe_mu = PTHREAD_MUTEX_INITIALIZER;
39static bool probed;
40static char *probe_err; /* NULL: works */
41
42/* Runs argv, capturing stderr; its wait status, or -1 when it could not start (errno). */
43static int run_capture(char *const argv[], mesh_text *err_out) {
44 int fds[2];
45 if (pipe(fds) != 0) return -1;
46 pid_t pid = fork();
47 if (pid < 0) {
48 close(fds[0]);
49 close(fds[1]);
50 return -1;
51 }
52 if (pid == 0) {
53 dup2(fds[1], 2);
54 close(fds[0]);
55 close(fds[1]);
56 int devnull = open("/dev/null", 0);
57 if (devnull >= 0) dup2(devnull, 0);
58 execvp(argv[0], argv);
59 _exit(127);
60 }
61 close(fds[1]);
62 char buf[512];
63 ssize_t n;
64 while ((n = read(fds[0], buf, sizeof buf)) > 0) mesh_text_putn(err_out, buf, (size_t)n);
65 close(fds[0]);
66 int st;
67 waitpid(pid, &st, 0);
68 return st;
69}
70
71bool mesh_native_limits_available(const mesh_native_limits *l, char **err) {
72 if (!mesh_native_limits_scoped(l)) return true;
73#if defined(__APPLE__)
74 const char *what = l->cpu_pct > 0 && l->mem > 0 ? "CPU or memory" : l->cpu_pct > 0 ? "CPU" : "memory";
75 return mesh_errf(err,
76 "load: limits: macOS cannot cap a process's %s (no cgroups; setrlimit RLIMIT_AS/DATA/RSS is refused or not enforced): "
77 "remove `cpu`/`mem_mb` from the manifest (`nice`, `background` work)",
78 what);
79#else
80 pthread_mutex_lock(&probe_mu);
81 if (!probed) {
82 probed = true;
83 struct timespec t0, t1;
84 clock_gettime(CLOCK_MONOTONIC, &t0);
85 char *argv[] = {"systemd-run", "--user", "--scope", "-q", "-p", "CPUQuota=100%", "-p", "MemoryMax=64M", "-p", "MemorySwapMax=0", "--", "true", NULL};
86 mesh_text e = {0};
87 int st = run_capture(argv, &e);
88 clock_gettime(CLOCK_MONOTONIC, &t1);
89 if (st >= 0 && WIFEXITED(st) && WEXITSTATUS(st) == 0) {
90 mesh_info("native: limits: systemd-run --user --scope works (%.0f ms)",
91 (double)(t1.tv_sec - t0.tv_sec) * 1e3 + (double)(t1.tv_nsec - t0.tv_nsec) / 1e6);
92 } else if (st >= 0 && !(WIFEXITED(st) && WEXITSTATUS(st) == 127)) {
93 const char *s = mesh_text_str(&e);
94 size_t n = e.len;
95 mesh_trim(&s, &n);
96 char *d = WIFEXITED(st) ? mesh_fmt("exit status: %d", WEXITSTATUS(st)) : mesh_fmt("signal: %d", WTERMSIG(st));
97 probe_err = mesh_fmt("load: limits: `systemd-run --user --scope` failed (%s): %.*s", d, (int)n, s);
98 free(d);
99 } else {
100 probe_err = mesh_fmt("load: limits: cannot run systemd-run: No such file or directory (os error 2)");
101 }
102 mesh_text_free(&e);
103 }
104 bool ok = probe_err == NULL;
105 if (!ok) mesh_errf(err, "%s", probe_err);
106 pthread_mutex_unlock(&probe_mu);
107 return ok;
108#endif
109}
110
111static char *read_trimmed(const char *path) {
112 FILE *f = fopen(path, "r");
113 if (!f) return NULL;
114 char buf[512];
115 size_t n = fread(buf, 1, sizeof buf - 1, f);
116 fclose(f);
117 buf[n] = 0;
118 const char *s = buf;
119 mesh_trim(&s, &n);
120 return mesh_strndup(s, n);
121}
122
123bool mesh_native_check_cgroup(pid_t pid, const mesh_native_limits *l, char **err) {
124 char path[64];
125 snprintf(path, sizeof path, "/proc/%d/cgroup", (int)pid);
126 FILE *f = fopen(path, "r");
127 if (!f) {
128 int e = errno;
129 return mesh_errf(err, "limits: /proc/%d/cgroup: %s (os error %d)", (int)pid, strerror(e), e);
130 }
131 char line[1024], *cg = NULL;
132 while (!cg && fgets(line, sizeof line, f))
133 if (strncmp(line, "0::", 3) == 0) {
134 const char *s = line + 3;
135 size_t n = strlen(s);
136 mesh_trim(&s, &n);
137 cg = mesh_strndup(s, n);
138 }
139 fclose(f);
140 if (!cg) return mesh_errf(err, "limits: no cgroup v2 entry");
141 size_t cn = strlen(cg);
142 if (cn < 6 || strcmp(cg + cn - 6, ".scope") != 0) {
143 mesh_errf(err, "limits: worker %d runs in %s, not a systemd scope", (int)pid, cg);
144 free(cg);
145 return false;
146 }
147 bool ok = true;
148 if (l->cpu_pct > 0) {
149 char *p = mesh_fmt("/sys/fs/cgroup%s/cpu.max", cg), *v = read_trimmed(p);
150 free(p);
151 unsigned long long q = 0, per = 0;
152 bool good = v && sscanf(v, "%llu %llu", &q, &per) == 2 && per > 0 && q * 100 / per == l->cpu_pct;
153 if (!v) ok = mesh_errf(err, "limits: %s/cpu.max: cannot read", cg);
154 else if (!good) {
155 char *q2 = mesh_debug_str(v);
156 ok = mesh_errf(err, "limits: %s: cpu.max is %s, wanted %u%% of a core", cg, q2, (unsigned)l->cpu_pct);
157 free(q2);
158 }
159 free(v);
160 }
161 if (ok && l->mem > 0) {
162 char *p1 = mesh_fmt("/sys/fs/cgroup%s/memory.max", cg), *p2 = mesh_fmt("/sys/fs/cgroup%s/memory.swap.max", cg);
163 char *m = read_trimmed(p1), *s = read_trimmed(p2);
164 free(p1);
165 free(p2);
166 char want[32];
167 snprintf(want, sizeof want, "%llu", (unsigned long long)l->mem);
168 if (!m || !s) ok = mesh_errf(err, "limits: %s/memory.max: cannot read", cg);
169 else if (strcmp(m, want) != 0 || strcmp(s, "0") != 0)
170 ok = mesh_errf(err, "limits: %s: memory.max %s, memory.swap.max %s; wanted %llu and 0", cg, m, s, (unsigned long long)l->mem);
171 free(m);
172 free(s);
173 }
174 free(cg);
175 return ok;
176}