skip to content
==> 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}