From f51fde4d46181495c4cde70db1411cf0c5bc18ef Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Tue, 5 Feb 2019 12:48:18 +0100 Subject: [PATCH] Initial support for cgroups Similar to how systemd creates cgroups for all services, for purposes of tracking, Finit now does the same. We also mount all available cgroups in the /sys/fs/cgroup namespace. This patch adds support for grouping processes in logical cgroups, like systemd, and an `initctl ps` command to list the hieararchy. Signed-off-by: Joachim Nilsson --- src/Makefile.am | 3 +- src/cgreaper.sh | 11 ++++ src/cgroup.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cgroup.h | 32 +++++++++++ src/finit.c | 6 ++ src/getty.c | 2 + src/initctl.c | 68 +++++++++++++++++++++++ src/service.c | 3 + 8 files changed, 267 insertions(+), 1 deletion(-) create mode 100755 src/cgreaper.sh create mode 100644 src/cgroup.c create mode 100644 src/cgroup.h diff --git a/src/Makefile.am b/src/Makefile.am index a7f2a868..fa2b9cfe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,6 +10,7 @@ endif bin_PROGRAMS = logit sbin_PROGRAMS = finit initctl reboot +pkglibexec_SCRIPTS = cgreaper.sh if WATCHDOGD pkglibexec_PROGRAMS = watchdogd endif @@ -17,7 +18,7 @@ endif logit_SOURCES = logit.c logit_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99 -finit_SOURCES = api.c \ +finit_SOURCES = api.c cgroup.c cgroup.h \ cond.c cond-w.c cond.h \ telinit.c \ conf.c conf.h \ diff --git a/src/cgreaper.sh b/src/cgreaper.sh new file mode 100755 index 00000000..cc4f687b --- /dev/null +++ b/src/cgreaper.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Called by kernel when last child in cgroup exists + +BASE=/sys/fs/cgroup/finit +PROC=`basename $1` +DIR="$BASE$1" + +logit -p daemon.info -t finit "$PROC stopped, cleaning up control group $DIR" +rmdir $DIR + +exit 0 diff --git a/src/cgroup.c b/src/cgroup.c new file mode 100644 index 00000000..43a5ac25 --- /dev/null +++ b/src/cgroup.c @@ -0,0 +1,143 @@ +/* Finit control group support functions + * + * Copyright (c) 2019 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" + +static int cg_init = 0; + +/* + * Called by Finit at early boot to mount initial cgroups + */ +void cgroup_init(void) +{ + FILE *fp; + char buf[80]; + int opts = MS_NODEV | MS_NOEXEC | MS_NOSUID; + + fp = fopen("/proc/cgroups", "r"); + if (!fp) { + _d("No cgroup support"); + return; + } + + if (mount("none", "/sys/fs/cgroup", "tmpfs", opts, NULL)) { + _d("Failed mounting cgroups"); + goto fail; + } + + /* Skip first line, header */ + fgets(buf, sizeof(buf), fp); + while (fgets(buf, sizeof(buf), fp)) { + char *cgroup; + char rc[80]; + + cgroup = strtok(buf, "\t "); + if (!cgroup) + continue; + + snprintf(rc, sizeof(rc), "/sys/fs/cgroup/%s", cgroup); + mkdir(rc, 0755); + if (mount("cgroup", rc, "cgroup", opts, cgroup)) + _d("Failed mounting %s cgroup on %s", cgroup, rc); + } + + /* Default cgroups for process monitoring */ + mkdir("/sys/fs/cgroup/finit", 0755); + if (mount("none", "/sys/fs/cgroup/finit", "cgroup", opts, "none,name=finit")) { + _pe("Failed mounting Finit cgroup hierarchy"); + goto fail; + } + + /* Set up reaper and enable callbacks */ + echo("/sys/fs/cgroup/finit/release_agent", 0, "/libexec/finit/cgreaper.sh"); + echo("/sys/fs/cgroup/finit/notify_on_release", 0, "1"); + + /* Default groups, PID 1, services, and user/login processes */ + mkdir("/sys/fs/cgroup/finit/init", 0755); + mkdir("/sys/fs/cgroup/finit/system", 0755); + mkdir("/sys/fs/cgroup/finit/user", 0755); + + /* Move ourselves to init */ + echo("/sys/fs/cgroup/finit/init/cgroup.procs", 0, "1"); + + /* We have signal, main screen turn on! */ + cg_init = 1; +fail: + fclose(fp); +} + +static int move_pid(char *group, char *name, int pid) +{ + char path[256]; + + snprintf(path, sizeof(path), "/sys/fs/cgroup/%s/%s", group, name); + if (mkdir(path, 0755) && errno != EEXIST) + return 1; + + strlcat(path, "/cgroup.procs", sizeof(path)); + + return echo(path, 0, "%d", pid); +} + +int cgroup_user(char *name) +{ + if (!cg_init) + return 0; + + return move_pid("finit/user", name, getpid()); +} + +int cgroup_service(char *cmd, int pid) +{ + char *nm; + + if (!cg_init) + return 0; + + if (pid <= 0) { + errno = EINVAL; + return 1; + } + + nm = strrchr(cmd, '/'); + if (nm) + nm++; + else + nm = cmd; + + return move_pid("finit/system", nm, pid); +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/src/cgroup.h b/src/cgroup.h new file mode 100644 index 00000000..8e507cc6 --- /dev/null +++ b/src/cgroup.h @@ -0,0 +1,32 @@ +/* Finit control group support functions + * + * Copyright (c) 2019 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_CGROUP_H_ +#define FINIT_CGROUP_H_ + +void cgroup_init (void); + +int cgroup_user (char *name); +int cgroup_service (char *cmd, int pid); + +#endif /* FINIT_CGROUP_H_ */ diff --git a/src/finit.c b/src/finit.c index 86538bb6..9e34b09f 100644 --- a/src/finit.c +++ b/src/finit.c @@ -36,6 +36,7 @@ #include #include "finit.h" +#include "cgroup.h" #include "cond.h" #include "conf.h" #include "helpers.h" @@ -352,6 +353,11 @@ int main(int argc, char* argv[]) if (fisdir("/proc/bus/usb")) mount("none", "/proc/bus/usb", "usbfs", 0, NULL); + /* + * Initialize default control groups, if available + */ + cgroup_init(); + /* * Parse kernel command line (debug, rescue, splash, etc.) * Also calls log_init() to set correct log level diff --git a/src/getty.c b/src/getty.c index 58b95a1f..c1cabc40 100644 --- a/src/getty.c +++ b/src/getty.c @@ -32,6 +32,7 @@ #include #include "finit.h" +#include "cgroup.h" #include "helpers.h" #include "sig.h" #include "utmp-api.h" @@ -192,6 +193,7 @@ static int do_login(char *name) { struct stat st; + cgroup_user(name); execl(_PATH_LOGIN, _PATH_LOGIN, name, NULL); /* diff --git a/src/initctl.c b/src/initctl.c index 33a59c16..913bce16 100644 --- a/src/initctl.c +++ b/src/initctl.c @@ -509,6 +509,70 @@ static int show_status(char *arg) return 0; } +static int dump_cgroup(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf) +{ + FILE *fp; + char *group; + char path[256]; + char buf[256]; + int user = 0; + int num = 0; + + if (tflag == FTW_F) + return 0; + + snprintf(path, sizeof(path), "%s/cgroup.procs", fpath); + fp = fopen(path, "r"); + if (!fp) + return 0; + + if (strstr(fpath, "finit/user")) + user = 1; + + group = strrchr(fpath, '/'); + if (!group) + return 0; + group++; + + while (fgets(buf, sizeof(buf), fp)) { + FILE *cfp; + int pid; + + if (num == 0) + printf("%c :- %s/", user ? ' ' : '|', group); + num++; + + pid = atoi(chomp(buf)); + snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); + cfp = fopen(path, "r"); + if (!cfp) + continue; + fgets(buf, sizeof(buf), cfp); + fclose(cfp); + + printf("\n%c :- %d %s", user ? ' ' : '|', pid, buf); + } + fclose(fp); + + if (num) + printf("\r%c\n", user ? ' ' : '|'); + + return 0; +} + +static int show_cgroup(char *arg) +{ + puts("finit/"); + puts("|- init/"); + nftw("/sys/fs/cgroup/finit/init", dump_cgroup, 20, 0); + puts("|- system/"); + nftw("/sys/fs/cgroup/finit/system", dump_cgroup, 20, 0); + puts("`- user/"); + nftw("/sys/fs/cgroup/finit/user", dump_cgroup, 20, 0); + + return 0; +} + static int usage(int rc) { fprintf(stderr, @@ -543,6 +607,8 @@ static int usage(int rc) " status [:ID] Show service status, by job# or name\n" " status | show Show status of services, default command\n" "\n" + " ps List processes based on cgroups\n" + "\n" " runlevel [0-9] Show or set runlevel: 0 halt, 6 reboot\n" " reboot Reboot system\n" " halt Halt system\n" @@ -583,6 +649,8 @@ int main(int argc, char *argv[]) { "status", show_status }, { "show", show_status }, /* Convenience alias */ + { "ps", show_cgroup }, + { "runlevel", do_runlevel }, { "reboot", do_reboot }, { "halt", do_halt }, diff --git a/src/service.c b/src/service.c index f85a20ce..d4d8a257 100644 --- a/src/service.c +++ b/src/service.c @@ -32,6 +32,7 @@ #include #include +#include "cgroup.h" #include "conf.h" #include "cond.h" #include "finit.h" @@ -185,6 +186,8 @@ static int service_start(svc_t *svc) sigprocmask(SIG_BLOCK, &nmask, &omask); pid = fork(); + cgroup_service(svc->cmd, pid); + if (pid == 0) { int status; char *home = NULL;