1 /* 2 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License version 2.1 6 * as published by the Free Software Foundation 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * reads unified cgroup config as proposed in 14 * https://github.com/opencontainers/runtime-spec/pull/1040 15 * attempt conversion from cgroup1 -> cgroup2 16 * https://github.com/containers/crun/blob/0.14.1/crun.1.md#cgroup-v2 17 * 18 * ToDo: 19 * - convert cgroup1 net_prio and net_cls to eBPF program 20 * - rdma (anyone?) intelrdt (anyone?) 21 */ 22 23 #define _GNU_SOURCE 24 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <limits.h> 28 #include <poll.h> 29 #include <stdlib.h> 30 #include <stdio.h> 31 #include <string.h> 32 #include <sys/stat.h> 33 #include <sys/mman.h> 34 #include <time.h> 35 #include <unistd.h> 36 #include <libgen.h> 37 #include <inttypes.h> 38 39 #include <libubox/avl.h> 40 #include <libubox/avl-cmp.h> 41 #include <libubox/blobmsg.h> 42 #include <libubox/list.h> 43 #include <libubox/utils.h> 44 45 #include "log.h" 46 #include "cgroups.h" 47 #include "cgroups-bpf.h" 48 49 #define CGROUP_ROOT "/sys/fs/cgroup/" 50 #define CGROUP_IO_WEIGHT_MAX 10000 51 #define CGROUP_DRAIN_TIMEOUT_MSEC 1000 52 53 struct cgval { 54 struct avl_node avl; 55 char *val; 56 }; 57 58 struct avl_tree cgvals; 59 static char *cgroup_path; 60 static bool initialized; 61 static bool destroyed; 62 63 void cgroups_prepare(void) { 64 initialized = false; 65 destroyed = false; 66 } 67 68 void cgroups_init(const char *p) { 69 avl_init(&cgvals, avl_strcmp, false, NULL); 70 cgroup_path = strdup(p); 71 initialized = true; 72 } 73 74 static void cgroups_set(const char *key, const char *val) 75 { 76 struct cgval *valp; 77 78 valp = avl_find_element(&cgvals, key, valp, avl); 79 if (!valp) { 80 valp = malloc(sizeof(struct cgval)); 81 if (!valp) 82 exit(ENOMEM); 83 84 valp->avl.key = strdup(key); 85 avl_insert(&cgvals, &valp->avl); 86 } else { 87 DEBUG("overwriting previous cgroup2 assignment %s=\"%s\"!\n", key, valp->val); 88 free(valp->val); 89 } 90 91 valp->val = strdup(val); 92 } 93 94 void cgroups_free(void) 95 { 96 struct cgval *valp, *tmp; 97 98 if (initialized) { 99 avl_remove_all_elements(&cgvals, valp, avl, tmp) { 100 free((void *)(valp->avl.key)); 101 free(valp->val); 102 free(valp); 103 } 104 free(cgroup_path); 105 cgroup_path = NULL; 106 initialized = false; 107 } 108 } 109 110 static int cgroups_write_attr(const char *attr, const char *val, size_t vlen) 111 { 112 char *ent; 113 int fd, ret = 0; 114 size_t len; 115 116 if (!cgroup_path) 117 return -ENODEV; 118 119 len = strlen(cgroup_path) + 1 + strlen(attr) + 1; 120 ent = malloc(len); 121 if (!ent) 122 return -ENOMEM; 123 124 snprintf(ent, len, "%s/%s", cgroup_path, attr); 125 fd = open(ent, O_WRONLY); 126 if (fd < 0) { 127 ret = -errno; 128 free(ent); 129 return ret; 130 } 131 132 if (write(fd, val, vlen) < 0) 133 ret = -errno; 134 135 close(fd); 136 free(ent); 137 return ret; 138 } 139 140 int cgroups_kill_all(void) 141 { 142 return cgroups_write_attr("cgroup.kill", "1", 1); 143 } 144 145 int cgroups_set_frozen(bool frozen) 146 { 147 return cgroups_write_attr("cgroup.freeze", frozen ? "1" : "", 1); 148 } 149 150 int cgroups_reclaim(int64_t bytes, int32_t swappiness) 151 { 152 char val[64]; 153 int len, ret; 154 int attempt; 155 156 if (bytes < 0) 157 return -EINVAL; 158 159 if (swappiness >= 0) 160 len = snprintf(val, sizeof(val), "%" PRId64 " swappiness=%" PRId32, 161 bytes, swappiness); 162 else 163 len = snprintf(val, sizeof(val), "%" PRId64, bytes); 164 if (len < 0 || len >= (int)sizeof(val)) 165 return -EINVAL; 166 167 for (attempt = 0; attempt < 2; ++attempt) { 168 ret = cgroups_write_attr("memory.reclaim", val, len); 169 if (ret != -EINTR) 170 break; 171 } 172 if (ret == -ENOENT) 173 ret = -ENODEV; 174 return ret; 175 } 176 177 int cgroups_attach_pid(pid_t pid) 178 { 179 char *ent; 180 int fd, ret = 0; 181 size_t len; 182 183 if (!cgroup_path) 184 return -ENODEV; 185 186 len = strlen(cgroup_path) + strlen("/cgroup.procs") + 1; 187 ent = malloc(len); 188 if (!ent) 189 return -ENOMEM; 190 191 snprintf(ent, len, "%s/cgroup.procs", cgroup_path); 192 fd = open(ent, O_WRONLY); 193 if (fd < 0) { 194 ret = -errno; 195 free(ent); 196 return ret; 197 } 198 199 if (dprintf(fd, "%d", pid) < 0) 200 ret = -errno; 201 202 close(fd); 203 free(ent); 204 return ret; 205 } 206 207 static void cgroups_compute_subtree_control(char *out, size_t outlen) 208 { 209 struct cgval *valp; 210 bool cpuset = false, 211 cpu = false, 212 hugetlb = false, 213 io = false, 214 memory = false, 215 pids = false, 216 rdma = false; 217 char *p; 218 219 out[0] = '\0'; 220 221 avl_for_each_element(&cgvals, valp, avl) { 222 const char *ent = (const char *)valp->avl.key; 223 224 if (!strncmp("cpuset.", ent, 7)) 225 cpuset = true; 226 else if (!strncmp("cpu.", ent, 4)) 227 cpu = true; 228 else if (!strncmp("hugetlb.", ent, 8)) 229 hugetlb = true; 230 else if (!strncmp("io.", ent, 3)) 231 io = true; 232 else if (!strncmp("memory.", ent, 7)) 233 memory = true; 234 else if (!strncmp("pids.", ent, 5)) 235 pids = true; 236 else if (!strncmp("rdma.", ent, 5)) 237 rdma = true; 238 } 239 240 if (cpuset) 241 strncat(out, "+cpuset ", outlen - strlen(out) - 1); 242 243 if (cpu) 244 strncat(out, "+cpu ", outlen - strlen(out) - 1); 245 246 if (hugetlb) 247 strncat(out, "+hugetlb ", outlen - strlen(out) - 1); 248 249 if (io) 250 strncat(out, "+io ", outlen - strlen(out) - 1); 251 252 if (memory) 253 strncat(out, "+memory ", outlen - strlen(out) - 1); 254 255 if (pids) 256 strncat(out, "+pids ", outlen - strlen(out) - 1); 257 258 if (rdma) 259 strncat(out, "+rdma ", outlen - strlen(out) - 1); 260 261 p = strchr(out, '\0'); 262 if (p > out && p[-1] == ' ') 263 p[-1] = '\0'; 264 } 265 266 static bool cgroups_populated(int fd) 267 { 268 char buf[128]; 269 ssize_t len; 270 char *val; 271 272 if (lseek(fd, 0, SEEK_SET) < 0) 273 return false; 274 275 len = read(fd, buf, sizeof(buf) - 1); 276 if (len <= 0) 277 return false; 278 279 buf[len] = '\0'; 280 val = strstr(buf, "populated "); 281 if (!val) 282 return false; 283 284 return val[strlen("populated ")] == '1'; 285 } 286 287 static void cgroups_wait_empty(void) 288 { 289 struct timespec deadline, now; 290 struct pollfd pfd; 291 int remaining; 292 size_t len; 293 char *ent; 294 int fd; 295 296 len = strlen(cgroup_path) + strlen("/cgroup.events") + 1; 297 ent = malloc(len); 298 if (!ent) 299 return; 300 301 snprintf(ent, len, "%s/cgroup.events", cgroup_path); 302 fd = open(ent, O_RDONLY); 303 free(ent); 304 if (fd < 0) 305 return; 306 307 clock_gettime(CLOCK_MONOTONIC, &deadline); 308 deadline.tv_sec += CGROUP_DRAIN_TIMEOUT_MSEC / 1000; 309 310 pfd.fd = fd; 311 pfd.events = POLLPRI; 312 313 while (cgroups_populated(fd)) { 314 clock_gettime(CLOCK_MONOTONIC, &now); 315 remaining = (deadline.tv_sec - now.tv_sec) * 1000 + 316 (deadline.tv_nsec - now.tv_nsec) / 1000000; 317 if (remaining <= 0) 318 break; 319 320 if (poll(&pfd, 1, remaining) < 0 && errno != EINTR) 321 break; 322 } 323 324 close(fd); 325 } 326 327 void cgroups_destroy(void) 328 { 329 char *sep; 330 331 destroyed = true; 332 333 if (!cgroup_path) 334 return; 335 336 cgroups_kill_all(); 337 cgroups_wait_empty(); 338 339 if (rmdir(cgroup_path) && errno == EBUSY) 340 ERROR("failed to remove busy cgroup %s\n", cgroup_path); 341 342 sep = strrchr(cgroup_path, '/'); 343 if (sep && sep != cgroup_path) { 344 *sep = '\0'; 345 (void)rmdir(cgroup_path); 346 *sep = '/'; 347 } 348 } 349 350 bool cgroups_destroyed(void) 351 { 352 return destroyed; 353 } 354 355 void cgroups_create(void) 356 { 357 char subtree_control[64] = { 0 }; 358 char *cdir, *ent; 359 size_t maxlen; 360 int fd; 361 362 if (!cgroup_path) 363 return; 364 365 DEBUG("creating cgroup %s\n", cgroup_path); 366 mkdir_p(cgroup_path, 0700); 367 368 cgroups_compute_subtree_control(subtree_control, sizeof(subtree_control)); 369 if (!subtree_control[0]) { 370 DEBUG("no cgroup controllers requested, skipping subtree_control walk\n"); 371 return; 372 } 373 374 maxlen = strlen(cgroup_path) + strlen("/cgroup.subtree_control") + 1; 375 ent = malloc(maxlen); 376 if (!ent) 377 exit(ENOMEM); 378 379 DEBUG("recursively applying cgroup.subtree_control = \"%s\"\n", subtree_control); 380 cdir = &cgroup_path[strlen(CGROUP_ROOT) - 2]; 381 while ((cdir = strchr(cdir + 1, '/'))) { 382 *cdir = '\0'; 383 snprintf(ent, maxlen, "%s/cgroup.subtree_control", cgroup_path); 384 DEBUG(" * %s\n", ent); 385 if ((fd = open(ent, O_WRONLY)) < 0) { 386 ERROR("can't open %s: %m\n", ent); 387 *cdir = '/'; 388 continue; 389 } 390 if (write(fd, subtree_control, strlen(subtree_control)) == -1) 391 ERROR("can't write to %s: %m\n", ent); 392 close(fd); 393 *cdir = '/'; 394 } 395 free(ent); 396 } 397 398 /* a swap limit narrows nothing on a kernel built without swap */ 399 static bool cgroups_attr_vacuous(const char *key) 400 { 401 if (strcmp(key, "memory.swap.max")) 402 return false; 403 404 return access("/proc/swaps", F_OK) != 0; 405 } 406 407 int cgroups_configure(void) 408 { 409 struct cgval *valp; 410 size_t maxlen = 0; 411 int fd, dirfd; 412 int ret = 0; 413 char *ent; 414 415 if (!cgroup_path) 416 return 0; 417 418 avl_for_each_element(&cgvals, valp, avl) { 419 size_t klen = strlen((char *)valp->avl.key); 420 421 if (klen > maxlen) 422 maxlen = klen; 423 } 424 maxlen += strlen(cgroup_path) + 2; 425 426 ent = malloc(maxlen); 427 if (!ent) 428 exit(ENOMEM); 429 430 avl_for_each_element(&cgvals, valp, avl) { 431 DEBUG("applying cgroup2 %s=\"%s\"\n", (char *)valp->avl.key, valp->val); 432 snprintf(ent, maxlen, "%s/%s", cgroup_path, (char *)valp->avl.key); 433 fd = open(ent, O_WRONLY); 434 if (fd < 0) { 435 if (cgroups_attr_vacuous((char *)valp->avl.key)) { 436 INFO("%s is unavailable here, \"%s\" narrows nothing\n", 437 ent, valp->val); 438 continue; 439 } 440 ERROR("can't open %s: %m\n", ent); 441 jail_cgroup_refused((char *)valp->avl.key); 442 ret = ENOTSUP; 443 break; 444 } 445 if (dprintf(fd, "%s", valp->val) < 0) { 446 ERROR("can't write to %s: %m\n", ent); 447 close(fd); 448 if (cgroups_attr_vacuous((char *)valp->avl.key)) 449 continue; 450 451 jail_cgroup_refused((char *)valp->avl.key); 452 ret = ENOTSUP; 453 break; 454 } 455 close(fd); 456 } 457 free(ent); 458 459 if (ret) 460 return ret; 461 462 dirfd = open(cgroup_path, O_DIRECTORY); 463 if (dirfd < 0) { 464 ERROR("can't open %s: %m\n", cgroup_path); 465 } else { 466 attach_cgroups_ebpf(dirfd); 467 close(dirfd); 468 } 469 470 return 0; 471 } 472 473 int cgroups_apply(pid_t pid) 474 { 475 int ret; 476 477 cgroups_create(); 478 479 ret = cgroups_configure(); 480 if (ret) 481 return ret; 482 483 cgroups_attach_pid(pid); 484 485 return 0; 486 } 487 488 enum { 489 OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAJOR, 490 OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MINOR, 491 OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_WEIGHT, 492 OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_LEAFWEIGHT, 493 __OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAX, 494 }; 495 496 static const struct blobmsg_policy oci_linux_cgroups_blockio_weightdevice_policy[] = { 497 [OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAJOR] = { "major", BLOBMSG_CAST_INT64 }, 498 [OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MINOR] = { "minor", BLOBMSG_CAST_INT64 }, 499 [OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_WEIGHT] = { "weight", BLOBMSG_TYPE_INT32 }, 500 [OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_LEAFWEIGHT] = { "leafWeight", BLOBMSG_TYPE_INT32 }, 501 }; 502 503 enum { 504 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR, 505 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR, 506 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE, 507 __OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAX, 508 }; 509 510 static const struct blobmsg_policy oci_linux_cgroups_blockio_throttledevice_policy[] = { 511 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR] = { "major", BLOBMSG_CAST_INT64 }, 512 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR] = { "minor", BLOBMSG_CAST_INT64 }, 513 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE] = { "rate", BLOBMSG_CAST_INT64 }, 514 }; 515 516 enum { 517 OCI_LINUX_CGROUPS_BLOCKIO_WEIGHT, 518 OCI_LINUX_CGROUPS_BLOCKIO_LEAFWEIGHT, 519 OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE, 520 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEREADBPSDEVICE, 521 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEWRITEBPSDEVICE, 522 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEREADIOPSDEVICE, 523 OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEWRITEIOPSDEVICE, 524 __OCI_LINUX_CGROUPS_BLOCKIO_MAX, 525 }; 526 527 static const struct blobmsg_policy oci_linux_cgroups_blockio_policy[] = { 528 [OCI_LINUX_CGROUPS_BLOCKIO_WEIGHT] = { "weight", BLOBMSG_TYPE_INT32 }, 529 [OCI_LINUX_CGROUPS_BLOCKIO_LEAFWEIGHT] = { "leafWeight", BLOBMSG_TYPE_INT32 }, 530 [OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE] = { "weightDevice", BLOBMSG_TYPE_ARRAY }, 531 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEREADBPSDEVICE] = { "throttleReadBpsDevice", BLOBMSG_TYPE_ARRAY }, 532 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEWRITEBPSDEVICE] = { "throttleWriteBpsDevice", BLOBMSG_TYPE_ARRAY }, 533 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEREADIOPSDEVICE] = { "throttleReadIOPSDevice", BLOBMSG_TYPE_ARRAY }, 534 [OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEWRITEIOPSDEVICE] = { "throttleWriteIOPSDevice", BLOBMSG_TYPE_ARRAY }, 535 }; 536 537 struct posix_dev { 538 uint64_t major; 539 uint64_t minor; 540 }; 541 542 struct iomax_line { 543 struct avl_node avl; 544 struct posix_dev dev; 545 uint64_t rbps; 546 uint64_t wbps; 547 uint64_t riops; 548 uint64_t wiops; 549 }; 550 551 static int avl_devcmp(const void *k1, const void *k2, void *ptr) 552 { 553 struct posix_dev *d1 = (struct posix_dev *)k1, *d2 = (struct posix_dev *)k2; 554 555 if (d1->major < d2->major) 556 return -1; 557 558 if (d1->major > d2->major) 559 return 1; 560 561 if (d1->minor < d2->minor) 562 return -1; 563 564 if (d1->minor > d2->minor) 565 return 1; 566 567 return 0; 568 } 569 570 static struct iomax_line *get_iomax_line(struct avl_tree *iomax, uint64_t major, uint64_t minor) 571 { 572 struct iomax_line *l; 573 struct posix_dev d; 574 d.major = major; 575 d.minor = minor; 576 l = avl_find_element(iomax, &d, l, avl); 577 if (!l) { 578 l = malloc(sizeof(struct iomax_line)); 579 if (!l) 580 exit(ENOMEM); 581 582 l->dev.major = d.major; 583 l->dev.minor = d.minor; 584 l->avl.key = &l->dev; 585 l->rbps = -1; 586 l->wbps = -1; 587 l->riops = -1; 588 l->wiops = -1; 589 avl_insert(iomax, &l->avl); 590 } 591 592 return l; 593 } 594 595 static int parseOCIlinuxcgroups_legacy_blockio(struct blob_attr *msg) 596 { 597 struct blob_attr *tb[__OCI_LINUX_CGROUPS_BLOCKIO_MAX], 598 *tbwd[__OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAX], 599 *tbtd[__OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAX], 600 *cur; 601 int rem; 602 int weight = -1; 603 size_t numweightstrs = 0, numiomaxstrs = 0, strtotlen = 1; 604 char **weightstrs = NULL, **iomaxstrs = NULL, **curstr; 605 char *weightstr, *iomaxstr; 606 struct avl_tree iomax; 607 struct iomax_line *curiomax, *tmp; 608 609 blobmsg_parse(oci_linux_cgroups_blockio_policy, __OCI_LINUX_CGROUPS_BLOCKIO_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); 610 611 if (tb[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHT]) { 612 weight = blobmsg_get_u32(tb[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHT]); 613 ++numweightstrs; 614 } 615 616 if (weight > CGROUP_IO_WEIGHT_MAX) 617 return ERANGE; 618 619 if (tb[OCI_LINUX_CGROUPS_BLOCKIO_LEAFWEIGHT]) 620 return jail_unsupported(tb[OCI_LINUX_CGROUPS_BLOCKIO_LEAFWEIGHT]); 621 622 blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE], rem) 623 ++numweightstrs; 624 625 weightstrs = calloc(numweightstrs + 1, sizeof(char *)); 626 if (!weightstrs) 627 exit(ENOMEM); 628 629 numweightstrs = 0; 630 631 if (weight > -1) 632 if (asprintf(&weightstrs[numweightstrs++], "default %d", weight) < 0) 633 return ENOMEM; 634 635 blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE], rem) { 636 uint64_t major, minor; 637 int devweight = weight; 638 639 blobmsg_parse(oci_linux_cgroups_blockio_weightdevice_policy, __OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAX, tbwd, blobmsg_data(cur), blobmsg_len(cur)); 640 if (!tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAJOR] || 641 !tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MINOR]) 642 return ENODATA; 643 644 if (!tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_WEIGHT] && 645 !tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_LEAFWEIGHT]) 646 return ENODATA; 647 648 if (tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_WEIGHT]) 649 devweight = blobmsg_get_u32(tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_WEIGHT]); 650 651 if (devweight > CGROUP_IO_WEIGHT_MAX) 652 return ERANGE; 653 654 if (tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_LEAFWEIGHT]) 655 return jail_unsupported( 656 tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_LEAFWEIGHT]); 657 658 major = blobmsg_cast_u64(tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAJOR]); 659 minor = blobmsg_cast_u64(tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MINOR]); 660 661 if (asprintf(&weightstrs[numweightstrs++], "%" PRIu64 ":%" PRIu64 " %u", major, minor, devweight) < 0) 662 return ENOMEM; 663 } 664 665 if (numweightstrs) { 666 curstr = weightstrs; 667 while (*curstr) 668 strtotlen += strlen(*(curstr++)) + 1; 669 670 weightstr = calloc(strtotlen, sizeof(char)); 671 if (!weightstr) 672 exit(ENOMEM); 673 674 curstr = weightstrs; 675 while (*curstr) { 676 strcat(weightstr, *curstr); 677 strcat(weightstr, "\n"); 678 free(*(curstr++)); 679 } 680 681 cgroups_set("io.bfq.weight", weightstr); 682 free(weightstr); 683 }; 684 685 free(weightstrs); 686 687 avl_init(&iomax, avl_devcmp, false, NULL); 688 689 blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEREADBPSDEVICE], rem) { 690 struct iomax_line *l; 691 692 blobmsg_parse(oci_linux_cgroups_blockio_throttledevice_policy, __OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAX, tbtd, blobmsg_data(cur), blobmsg_len(cur)); 693 694 if (!tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR] || 695 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR] || 696 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]) 697 return ENODATA; 698 699 l = get_iomax_line(&iomax, 700 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR]), 701 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR])); 702 703 l->rbps = blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]); 704 } 705 706 blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEWRITEBPSDEVICE], rem) { 707 struct iomax_line *l; 708 709 blobmsg_parse(oci_linux_cgroups_blockio_throttledevice_policy, __OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAX, tbtd, blobmsg_data(cur), blobmsg_len(cur)); 710 711 if (!tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR] || 712 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR] || 713 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]) 714 return ENODATA; 715 716 l = get_iomax_line(&iomax, 717 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR]), 718 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR])); 719 720 l->wbps = blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]); 721 } 722 723 blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEREADIOPSDEVICE], rem) { 724 struct iomax_line *l; 725 726 blobmsg_parse(oci_linux_cgroups_blockio_throttledevice_policy, __OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAX, tbtd, blobmsg_data(cur), blobmsg_len(cur)); 727 728 if (!tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR] || 729 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR] || 730 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]) 731 return ENODATA; 732 733 l = get_iomax_line(&iomax, 734 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR]), 735 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR])); 736 737 l->riops = blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]); 738 } 739 740 blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEWRITEIOPSDEVICE], rem) { 741 struct iomax_line *l; 742 743 blobmsg_parse(oci_linux_cgroups_blockio_throttledevice_policy, __OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAX, tbtd, blobmsg_data(cur), blobmsg_len(cur)); 744 745 if (!tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR] || 746 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR] || 747 !tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]) 748 return ENODATA; 749 750 l = get_iomax_line(&iomax, 751 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MAJOR]), 752 blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_MINOR])); 753 754 l->wiops = blobmsg_cast_u64(tbtd[OCI_LINUX_CGROUPS_BLOCKIO_THROTTLEDEVICE_RATE]); 755 } 756 757 avl_for_each_element(&iomax, curiomax, avl) 758 ++numiomaxstrs; 759 760 if (!numiomaxstrs) 761 return 0; 762 763 iomaxstrs = calloc(numiomaxstrs + 1, sizeof(char *)); 764 if (!iomaxstrs) 765 exit(ENOMEM); 766 767 numiomaxstrs = 0; 768 769 avl_for_each_element(&iomax, curiomax, avl) { 770 char iomaxlstr[160]; 771 char lstr[32]; 772 773 sprintf(iomaxlstr, "%" PRIu64 ":%" PRIu64 " ", curiomax->dev.major, curiomax->dev.minor); 774 775 if (curiomax->rbps != -1) { 776 sprintf(lstr, "rbps=%" PRIu64 " ", curiomax->rbps); 777 strcat(iomaxlstr, lstr); 778 } 779 if (curiomax->wbps != -1) { 780 sprintf(lstr, "wbps=%" PRIu64 " ", curiomax->wbps); 781 strcat(iomaxlstr, lstr); 782 } 783 if (curiomax->riops != -1) { 784 sprintf(lstr, "riops=%" PRIu64 " ", curiomax->riops); 785 strcat(iomaxlstr, lstr); 786 } 787 if (curiomax->wiops != -1) { 788 sprintf(lstr, "wiops=%" PRIu64 " ", curiomax->wiops); 789 strcat(iomaxlstr, lstr); 790 } 791 792 iomaxstrs[numiomaxstrs++] = strdup(iomaxlstr); 793 } 794 795 avl_for_each_element_safe(&iomax, curiomax, avl, tmp) { 796 avl_delete(&iomax, &curiomax->avl); 797 free(curiomax); 798 } 799 800 strtotlen = 1; /* 1 accounts for \0 at end of string */ 801 if (numiomaxstrs) { 802 curstr = iomaxstrs; 803 while (*curstr) 804 strtotlen += strlen(*(curstr++)) + 1; /* +1 accounts for \n at end of line */ 805 806 iomaxstr = calloc(strtotlen, sizeof(char)); 807 if (!iomaxstr) 808 exit(ENOMEM); 809 810 curstr = iomaxstrs; 811 812 while (*curstr) { 813 strcat(iomaxstr, *curstr); 814 strcat(iomaxstr, "\n"); 815 free(*(curstr++)); 816 } 817 818 cgroups_set("io.max", iomaxstr); 819 free(iomaxstr); 820 }; 821 822 free(iomaxstrs); 823 824 return 0; 825 } 826 827 828 enum { 829 OCI_LINUX_CGROUPS_CPU_SHARES, 830 OCI_LINUX_CGROUPS_CPU_PERIOD, 831 OCI_LINUX_CGROUPS_CPU_QUOTA, 832 OCI_LINUX_CGROUPS_CPU_BURST, 833 OCI_LINUX_CGROUPS_CPU_IDLE, 834 OCI_LINUX_CGROUPS_CPU_REALTIMERUNTIME, 835 OCI_LINUX_CGROUPS_CPU_REALTIMEPERIOD, 836 OCI_LINUX_CGROUPS_CPU_CPUS, 837 OCI_LINUX_CGROUPS_CPU_MEMS, 838 __OCI_LINUX_CGROUPS_CPU_MAX, 839 }; 840 841 static const struct blobmsg_policy oci_linux_cgroups_cpu_policy[] = { 842 [OCI_LINUX_CGROUPS_CPU_SHARES] = { "shares", BLOBMSG_CAST_INT64 }, 843 [OCI_LINUX_CGROUPS_CPU_PERIOD] = { "period", BLOBMSG_CAST_INT64 }, 844 [OCI_LINUX_CGROUPS_CPU_QUOTA] = { "quota", BLOBMSG_CAST_INT64 }, /* signed int64! */ 845 [OCI_LINUX_CGROUPS_CPU_BURST] = { "burst", BLOBMSG_CAST_INT64 }, 846 [OCI_LINUX_CGROUPS_CPU_IDLE] = { "idle", BLOBMSG_CAST_INT64 }, 847 [OCI_LINUX_CGROUPS_CPU_REALTIMEPERIOD] = { "realtimePeriod", BLOBMSG_CAST_INT64 }, 848 [OCI_LINUX_CGROUPS_CPU_REALTIMERUNTIME] = { "realtimeRuntime", BLOBMSG_CAST_INT64 }, 849 [OCI_LINUX_CGROUPS_CPU_CPUS] = { "cpus", BLOBMSG_TYPE_STRING }, 850 [OCI_LINUX_CGROUPS_CPU_MEMS] = { "mems", BLOBMSG_TYPE_STRING }, 851 }; 852 853 static int parseOCIlinuxcgroups_legacy_cpu(struct blob_attr *msg) 854 { 855 struct blob_attr *tb[__OCI_LINUX_CGROUPS_CPU_MAX]; 856 uint64_t shares, period = 0; 857 int64_t quota = -2; /* unset */ 858 char tmp[32] = { 0 }; 859 860 blobmsg_parse(oci_linux_cgroups_cpu_policy, __OCI_LINUX_CGROUPS_CPU_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); 861 862 /* no equivalent in cgroup2 */ 863 if (tb[OCI_LINUX_CGROUPS_CPU_REALTIMEPERIOD]) 864 return jail_unsupported(tb[OCI_LINUX_CGROUPS_CPU_REALTIMEPERIOD]); 865 866 if (tb[OCI_LINUX_CGROUPS_CPU_REALTIMERUNTIME]) 867 return jail_unsupported(tb[OCI_LINUX_CGROUPS_CPU_REALTIMERUNTIME]); 868 869 if (tb[OCI_LINUX_CGROUPS_CPU_SHARES]) { 870 shares = blobmsg_cast_u64(tb[OCI_LINUX_CGROUPS_CPU_SHARES]); 871 if ((shares < 2) || (shares > 262144)) 872 return ERANGE; 873 874 snprintf(tmp, sizeof(tmp), "%" PRIu64, (((uint64_t)1) + ((shares - 2) * 9999) / 262142)); 875 cgroups_set("cpu.weight", tmp); 876 tmp[0] = '\0'; 877 } 878 879 if (tb[OCI_LINUX_CGROUPS_CPU_QUOTA]) 880 quota = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_CPU_QUOTA]); 881 882 if (tb[OCI_LINUX_CGROUPS_CPU_PERIOD]) 883 period = blobmsg_cast_u64(tb[OCI_LINUX_CGROUPS_CPU_PERIOD]); 884 885 if (period) { 886 if (quota >= 0) 887 snprintf(tmp, sizeof(tmp), "%" PRId64 " %" PRIu64 , quota, period); 888 else 889 snprintf(tmp, sizeof(tmp), "max %" PRIu64, period); /* assume default */ 890 } else if (quota >= 0) { 891 snprintf(tmp, sizeof(tmp), "%" PRId64, quota); 892 } else if (quota == -1) { 893 strcpy(tmp, "max"); 894 } 895 896 if (tmp[0]) 897 cgroups_set("cpu.max", tmp); 898 899 if (tb[OCI_LINUX_CGROUPS_CPU_BURST]) { 900 snprintf(tmp, sizeof(tmp), "%" PRIu64, 901 blobmsg_cast_u64(tb[OCI_LINUX_CGROUPS_CPU_BURST])); 902 cgroups_set("cpu.max.burst", tmp); 903 } 904 905 if (tb[OCI_LINUX_CGROUPS_CPU_IDLE]) { 906 snprintf(tmp, sizeof(tmp), "%" PRId64, 907 blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_CPU_IDLE])); 908 cgroups_set("cpu.idle", tmp); 909 } 910 911 if (tb[OCI_LINUX_CGROUPS_CPU_CPUS]) 912 cgroups_set("cpuset.cpus", blobmsg_get_string(tb[OCI_LINUX_CGROUPS_CPU_CPUS])); 913 914 if (tb[OCI_LINUX_CGROUPS_CPU_MEMS]) 915 cgroups_set("cpuset.mems", blobmsg_get_string(tb[OCI_LINUX_CGROUPS_CPU_MEMS])); 916 917 return 0; 918 } 919 920 921 enum { 922 OCI_LINUX_CGROUPS_MEMORY_LIMIT, 923 OCI_LINUX_CGROUPS_MEMORY_RESERVATION, 924 OCI_LINUX_CGROUPS_MEMORY_SWAP, 925 OCI_LINUX_CGROUPS_MEMORY_KERNEL, 926 OCI_LINUX_CGROUPS_MEMORY_KERNELTCP, 927 OCI_LINUX_CGROUPS_MEMORY_SWAPPINESS, 928 OCI_LINUX_CGROUPS_MEMORY_DISABLEOOMKILLER, 929 OCI_LINUX_CGROUPS_MEMORY_USEHIERARCHY, 930 OCI_LINUX_CGROUPS_MEMORY_CHECKBEFOREUPDATE, 931 __OCI_LINUX_CGROUPS_MEMORY_MAX, 932 }; 933 934 static const struct blobmsg_policy oci_linux_cgroups_memory_policy[] = { 935 [OCI_LINUX_CGROUPS_MEMORY_LIMIT] = { "limit", BLOBMSG_CAST_INT64 }, /* signed int64! */ 936 [OCI_LINUX_CGROUPS_MEMORY_RESERVATION] = { "reservation", BLOBMSG_CAST_INT64 }, /* signed int64! */ 937 [OCI_LINUX_CGROUPS_MEMORY_SWAP] = { "swap", BLOBMSG_CAST_INT64 }, /* signed int64! */ 938 [OCI_LINUX_CGROUPS_MEMORY_KERNEL] = { "kernel", BLOBMSG_CAST_INT64 }, /* signed int64! ignored */ 939 [OCI_LINUX_CGROUPS_MEMORY_KERNELTCP] = { "kernelTCP", BLOBMSG_CAST_INT64 }, /* signed int64! ignored */ 940 [OCI_LINUX_CGROUPS_MEMORY_SWAPPINESS] = { "swappiness", BLOBMSG_CAST_INT64 }, 941 [OCI_LINUX_CGROUPS_MEMORY_DISABLEOOMKILLER] = { "disableOOMKiller", BLOBMSG_TYPE_BOOL }, 942 [OCI_LINUX_CGROUPS_MEMORY_USEHIERARCHY] = { "useHierarchy", BLOBMSG_TYPE_BOOL }, 943 [OCI_LINUX_CGROUPS_MEMORY_CHECKBEFOREUPDATE] = { "checkBeforeUpdate", BLOBMSG_TYPE_BOOL }, 944 }; 945 946 static int64_t read_int64_file(const char *path) 947 { 948 char buf[32]; 949 char *end; 950 int64_t v; 951 int fd; 952 ssize_t n; 953 954 fd = open(path, O_RDONLY | O_CLOEXEC); 955 if (fd < 0) 956 return -1; 957 do { 958 n = read(fd, buf, sizeof(buf) - 1); 959 } while (n < 0 && errno == EINTR); 960 close(fd); 961 if (n <= 0) 962 return -1; 963 buf[n] = '\0'; 964 v = strtoll(buf, &end, 10); 965 if (end == buf || (*end != '\0' && *end != '\n')) 966 return -1; 967 return v; 968 } 969 970 int64_t cgroups_read_int64(const char *attr) 971 { 972 char path[PATH_MAX]; 973 974 if (!cgroup_path) 975 return -1; 976 977 snprintf(path, sizeof(path), "%s/%s", cgroup_path, attr); 978 return read_int64_file(path); 979 } 980 981 int cgroups_open_attr(const char *attr) 982 { 983 char path[PATH_MAX]; 984 985 if (!cgroup_path) 986 return -1; 987 988 snprintf(path, sizeof(path), "%s/%s", cgroup_path, attr); 989 return open(path, O_RDONLY | O_CLOEXEC); 990 } 991 992 int cgroups_open_dir(void) 993 { 994 if (!cgroup_path) 995 return -1; 996 return open(cgroup_path, O_PATH | O_DIRECTORY | O_CLOEXEC); 997 } 998 999 void cgroups_set_memory_limit(int64_t bytes) 1000 { 1001 char tmp[32]; 1002 1003 snprintf(tmp, sizeof(tmp), "%" PRId64, bytes); 1004 cgroups_set("memory.max", tmp); 1005 } 1006 1007 static int parseOCIlinuxcgroups_legacy_memory(struct blob_attr *msg, bool is_update) 1008 { 1009 struct blob_attr *tb[__OCI_LINUX_CGROUPS_MEMORY_MAX]; 1010 char tmp[32] = { 0 }; 1011 int64_t limit = -1, swap, reservation; 1012 1013 blobmsg_parse(oci_linux_cgroups_memory_policy, __OCI_LINUX_CGROUPS_MEMORY_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); 1014 1015 /* 1016 * not all properties of the OCI memory section can be mapped to cgroup2 1017 * kernel memory accounting is always enabled and included in the set 1018 * memory limit, hence these options can be ignored 1019 * disableOOMKiller could be emulated using oom_score_adj + seccomp eBPF 1020 * preventing self-upgrade (but allow downgrade) 1021 * 1022 * see also https://github.com/opencontainers/runtime-spec/issues/1005 1023 */ 1024 if (tb[OCI_LINUX_CGROUPS_MEMORY_SWAPPINESS]) 1025 return jail_unsupported(tb[OCI_LINUX_CGROUPS_MEMORY_SWAPPINESS]); 1026 1027 if (tb[OCI_LINUX_CGROUPS_MEMORY_DISABLEOOMKILLER]) 1028 return jail_unsupported(tb[OCI_LINUX_CGROUPS_MEMORY_DISABLEOOMKILLER]); 1029 1030 if (tb[OCI_LINUX_CGROUPS_MEMORY_USEHIERARCHY]) 1031 return jail_unsupported(tb[OCI_LINUX_CGROUPS_MEMORY_USEHIERARCHY]); 1032 1033 if (is_update && tb[OCI_LINUX_CGROUPS_MEMORY_CHECKBEFOREUPDATE] && 1034 blobmsg_get_bool(tb[OCI_LINUX_CGROUPS_MEMORY_CHECKBEFOREUPDATE])) { 1035 char path[PATH_MAX]; 1036 int64_t current; 1037 1038 snprintf(path, sizeof(path), "%s/memory.current", cgroup_path); 1039 current = read_int64_file(path); 1040 if (current < 0) { 1041 ERROR("memory.checkBeforeUpdate: cannot read %s: %m\n", path); 1042 return EIO; 1043 } 1044 1045 if (tb[OCI_LINUX_CGROUPS_MEMORY_LIMIT]) { 1046 int64_t new_limit = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_MEMORY_LIMIT]); 1047 if (new_limit != -1 && new_limit < current) { 1048 ERROR("memory.checkBeforeUpdate: new limit %" PRId64 1049 " < current usage %" PRId64 "\n", new_limit, current); 1050 return EBUSY; 1051 } 1052 } 1053 if (tb[OCI_LINUX_CGROUPS_MEMORY_RESERVATION]) { 1054 int64_t new_res = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_MEMORY_RESERVATION]); 1055 if (new_res != -1 && new_res < current) { 1056 ERROR("memory.checkBeforeUpdate: new reservation %" PRId64 1057 " < current usage %" PRId64 "\n", new_res, current); 1058 return EBUSY; 1059 } 1060 } 1061 } 1062 1063 if (tb[OCI_LINUX_CGROUPS_MEMORY_LIMIT]) { 1064 limit = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_MEMORY_LIMIT]); 1065 if (limit == -1) 1066 strcpy(tmp, "max"); 1067 else 1068 snprintf(tmp, sizeof(tmp), "%" PRId64, limit); 1069 1070 cgroups_set("memory.max", tmp); 1071 } 1072 1073 if (tb[OCI_LINUX_CGROUPS_MEMORY_RESERVATION]) { 1074 reservation = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_MEMORY_RESERVATION]); 1075 1076 if (reservation == -1) 1077 strcpy(tmp, "max"); 1078 else 1079 snprintf(tmp, sizeof(tmp), "%" PRId64, reservation); 1080 1081 cgroups_set("memory.low", tmp); 1082 } 1083 1084 /* OCI 'swap' acounts for memory+swap */ 1085 if (tb[OCI_LINUX_CGROUPS_MEMORY_SWAP]) { 1086 swap = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_MEMORY_SWAP]); 1087 1088 if (swap == -1) 1089 strcpy(tmp, "max"); 1090 else if (limit == -1 || (limit < swap)) 1091 snprintf(tmp, sizeof(tmp), "%" PRId64, swap); 1092 else 1093 snprintf(tmp, sizeof(tmp), "%" PRId64, limit - swap); 1094 1095 cgroups_set("memory.swap.max", tmp); 1096 } 1097 1098 return 0; 1099 } 1100 1101 1102 enum { 1103 OCI_LINUX_CGROUPS_PIDS_LIMIT, 1104 __OCI_LINUX_CGROUPS_PIDS_MAX, 1105 }; 1106 1107 static const struct blobmsg_policy oci_linux_cgroups_pids_policy[] = { 1108 [OCI_LINUX_CGROUPS_PIDS_LIMIT] = { "limit", BLOBMSG_CAST_INT64 }, 1109 }; 1110 1111 static int parseOCIlinuxcgroups_legacy_pids(struct blob_attr *msg) 1112 { 1113 struct blob_attr *tb[__OCI_LINUX_CGROUPS_MEMORY_MAX]; 1114 char tmp[32] = { 0 }; 1115 int64_t limit; 1116 1117 blobmsg_parse(oci_linux_cgroups_pids_policy, __OCI_LINUX_CGROUPS_PIDS_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); 1118 1119 if (!tb[OCI_LINUX_CGROUPS_PIDS_LIMIT]) 1120 return 0; 1121 1122 limit = blobmsg_cast_s64(tb[OCI_LINUX_CGROUPS_PIDS_LIMIT]); 1123 if (limit < 0) 1124 strcpy(tmp, "max"); 1125 else 1126 snprintf(tmp, sizeof(tmp), "%" PRId64, limit); 1127 1128 cgroups_set("pids.max", tmp); 1129 1130 return 0; 1131 } 1132 1133 static int parseOCIlinuxcgroups_unified(struct blob_attr *msg) 1134 { 1135 struct blob_attr *cur; 1136 int rem; 1137 1138 blobmsg_for_each_attr(cur, msg, rem) { 1139 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) 1140 return EINVAL; 1141 1142 /* restrict keys */ 1143 if (strchr(blobmsg_name(cur), '/') || 1144 !strcmp(blobmsg_name(cur), "cgroup.subtree_control") || 1145 !strcmp(blobmsg_name(cur), "cgroup.procs") || 1146 !strcmp(blobmsg_name(cur), "cgroup.threads") || 1147 !strcmp(blobmsg_name(cur), "cgroup.freeze")) 1148 return EINVAL; 1149 1150 cgroups_set(blobmsg_name(cur), blobmsg_get_string(cur)); 1151 } 1152 1153 return 0; 1154 } 1155 1156 enum { 1157 OCI_LINUX_CGROUPS_BLOCKIO, 1158 OCI_LINUX_CGROUPS_CPU, 1159 OCI_LINUX_CGROUPS_DEVICES, 1160 OCI_LINUX_CGROUPS_HUGEPAGELIMITS, 1161 OCI_LINUX_CGROUPS_INTELRDT, 1162 OCI_LINUX_CGROUPS_MEMORY, 1163 OCI_LINUX_CGROUPS_NETWORK, 1164 OCI_LINUX_CGROUPS_PIDS, 1165 OCI_LINUX_CGROUPS_RDMA, 1166 OCI_LINUX_CGROUPS_UNIFIED, 1167 __OCI_LINUX_CGROUPS_MAX, 1168 }; 1169 1170 static const struct blobmsg_policy oci_linux_cgroups_policy[] = { 1171 [OCI_LINUX_CGROUPS_BLOCKIO] = { "blockIO", BLOBMSG_TYPE_TABLE }, 1172 [OCI_LINUX_CGROUPS_CPU] = { "cpu", BLOBMSG_TYPE_TABLE }, 1173 [OCI_LINUX_CGROUPS_DEVICES] = { "devices", BLOBMSG_TYPE_ARRAY }, 1174 [OCI_LINUX_CGROUPS_HUGEPAGELIMITS] = { "hugepageLimits", BLOBMSG_TYPE_ARRAY }, 1175 [OCI_LINUX_CGROUPS_INTELRDT] = { "intelRdt", BLOBMSG_TYPE_TABLE }, 1176 [OCI_LINUX_CGROUPS_MEMORY] = { "memory", BLOBMSG_TYPE_TABLE }, 1177 [OCI_LINUX_CGROUPS_NETWORK] = { "network", BLOBMSG_TYPE_TABLE }, 1178 [OCI_LINUX_CGROUPS_PIDS] = { "pids", BLOBMSG_TYPE_TABLE }, 1179 [OCI_LINUX_CGROUPS_RDMA] = { "rdma", BLOBMSG_TYPE_TABLE }, 1180 [OCI_LINUX_CGROUPS_UNIFIED] = { "unified", BLOBMSG_TYPE_TABLE }, 1181 }; 1182 1183 int parseOCIlinuxcgroups(struct blob_attr *msg, bool is_update) 1184 { 1185 struct blob_attr *tb[__OCI_LINUX_CGROUPS_MAX]; 1186 int ret; 1187 1188 blobmsg_parse(oci_linux_cgroups_policy, __OCI_LINUX_CGROUPS_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); 1189 1190 if (tb[OCI_LINUX_CGROUPS_HUGEPAGELIMITS]) 1191 return jail_unsupported(tb[OCI_LINUX_CGROUPS_HUGEPAGELIMITS]); 1192 1193 if (tb[OCI_LINUX_CGROUPS_INTELRDT]) 1194 return jail_unsupported(tb[OCI_LINUX_CGROUPS_INTELRDT]); 1195 1196 if (tb[OCI_LINUX_CGROUPS_NETWORK]) 1197 return jail_unsupported(tb[OCI_LINUX_CGROUPS_NETWORK]); 1198 1199 if (tb[OCI_LINUX_CGROUPS_RDMA]) 1200 return jail_unsupported(tb[OCI_LINUX_CGROUPS_RDMA]); 1201 1202 if (tb[OCI_LINUX_CGROUPS_BLOCKIO]) { 1203 ret = parseOCIlinuxcgroups_legacy_blockio(tb[OCI_LINUX_CGROUPS_BLOCKIO]); 1204 if (ret) 1205 return ret; 1206 } 1207 1208 if (tb[OCI_LINUX_CGROUPS_CPU]) { 1209 ret = parseOCIlinuxcgroups_legacy_cpu(tb[OCI_LINUX_CGROUPS_CPU]); 1210 if (ret) 1211 return ret; 1212 } 1213 1214 if (tb[OCI_LINUX_CGROUPS_DEVICES]) { 1215 ret = parseOCIlinuxcgroups_devices(tb[OCI_LINUX_CGROUPS_DEVICES]); 1216 if (ret) 1217 return ret; 1218 } 1219 1220 if (tb[OCI_LINUX_CGROUPS_MEMORY]) { 1221 ret = parseOCIlinuxcgroups_legacy_memory(tb[OCI_LINUX_CGROUPS_MEMORY], is_update); 1222 if (ret) 1223 return ret; 1224 } 1225 1226 if (tb[OCI_LINUX_CGROUPS_PIDS]) { 1227 ret = parseOCIlinuxcgroups_legacy_pids(tb[OCI_LINUX_CGROUPS_PIDS]); 1228 if (ret) 1229 return ret; 1230 } 1231 1232 if (tb[OCI_LINUX_CGROUPS_UNIFIED]) { 1233 ret = parseOCIlinuxcgroups_unified(tb[OCI_LINUX_CGROUPS_UNIFIED]); 1234 if (ret) 1235 return ret; 1236 } 1237 1238 return 0; 1239 } 1240
This page was automatically generated by LXR 0.3.1. • OpenWrt