From 8c6db8336536916d0476ff8233e0abf40a2f6aab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 21 Jun 2010 23:27:18 +0200 Subject: [PATCH] pam: implement systemd PAM module and generelize cgroup API for that a bit --- .gitignore | 3 + Makefile.am | 65 ++++- configure.ac | 9 +- fixme | 20 +- src/cgroup-util.c | 694 ++++++++++++++++++++++++++++++++++++++++++++++ src/cgroup-util.h | 54 ++++ src/cgroup.c | 318 +++------------------ src/cgroup.h | 9 +- src/manager.c | 4 +- src/pam-module.c | 466 +++++++++++++++++++++++++++++++ src/test-cgroup.c | 84 ++++++ src/util.c | 169 ++++++++++- src/util.h | 8 +- src/utmp-wtmp.c | 6 +- systemd.pc.in | 11 + 15 files changed, 1609 insertions(+), 311 deletions(-) create mode 100644 src/cgroup-util.c create mode 100644 src/cgroup-util.h create mode 100644 src/pam-module.c create mode 100644 src/test-cgroup.c create mode 100644 systemd.pc.in diff --git a/.gitignore b/.gitignore index 2316dd509..ed881d785 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +systemd.pc +test-cgroup +.libs/ systemd-notify test-daemon systemd-install diff --git a/Makefile.am b/Makefile.am index 76662b182..ae83c4a44 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,8 @@ dbussessionservicedir=@dbussessionservicedir@ dbussystemservicedir=@dbussystemservicedir@ dbusinterfacedir=@dbusinterfacedir@ udevrulesdir=@udevrulesdir@ +pamlibdir=@pamlibdir@ +pkgconfigdatadir=$(datadir)/pkgconfig # Our own, non-special dirs pkgsysconfdir=$(sysconfdir)/systemd @@ -45,6 +47,7 @@ AM_CPPFLAGS = \ -DCGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \ -DSYSTEMD_BINARY_PATH=\"$(rootbindir)/systemd\" \ -DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \ + -DRUNTIME_DIR=\"$(localstatedir)/run\" \ -I $(top_srcdir)/src rootbin_PROGRAMS = \ @@ -70,7 +73,13 @@ noinst_PROGRAMS = \ test-job-type \ test-ns \ test-loopback \ - test-daemon + test-daemon \ + test-cgroup + +if HAVE_PAM +pamlib_LTLIBRARIES = \ + pam_systemd.la +endif dist_dbuspolicy_DATA = \ src/org.freedesktop.systemd1.conf @@ -158,7 +167,8 @@ EXTRA_DIST = \ units/session/exit.service.in \ LICENSE \ README \ - DISTRO_PORTING + DISTRO_PORTING \ + src/systemd.pc.in if TARGET_FEDORA dist_systemunit_DATA += \ @@ -201,6 +211,9 @@ dist_doc_DATA = \ src/sd-daemon.h \ src/sd-daemon.c +pkgconfigdata_DATA = \ + systemd.pc + noinst_LTLIBRARIES = \ libsystemd-basic.la \ libsystemd-core.la @@ -262,7 +275,8 @@ libsystemd_core_la_SOURCES = \ src/unit-name.c \ src/fdset.c \ src/namespace.c \ - src/tcpwrap.c + src/tcpwrap.c \ + src/cgroup-util.c libsystemd_core_la_CFLAGS = \ $(AM_CFLAGS) \ @@ -356,6 +370,18 @@ test_daemon_SOURCES = \ test_daemon_LDADD = \ libsystemd-basic.la +test_cgroup_SOURCES = \ + src/test-cgroup.c \ + src/cgroup-util.c + +test_cgroup_CFLAGS = \ + $(AM_CFLAGS) \ + $(CGROUP_CFLAGS) + +test_cgroup_LDADD = \ + libsystemd-basic.la \ + $(CGROUP_LIBS) + systemd_logger_SOURCES = \ src/logger.c \ src/sd-daemon.c \ @@ -442,12 +468,41 @@ systemadm_LDADD = \ $(DBUSGLIB_LIBS) \ $(GTK_LIBS) +pam_systemd_la_SOURCES = \ + src/pam-module.c \ + src/cgroup-util.c \ + src/sd-daemon.c + +pam_systemd_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(CGROUP_CFLAGS) \ + -fvisibility=hidden + +pam_systemd_la_LDFLAGS = \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -export-symbols-regex '^pam_sm_.*' + +pam_systemd_la_LIBADD = \ + libsystemd-basic.la \ + $(PAM_LIBS) \ + $(CGROUP_LIBS) + SED_PROCESS = \ $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ $(SED) -e 's,@rootlibexecdir\@,$(rootlibexecdir),g' \ -e 's,@SPECIAL_SYSLOG_SERVICE\@,$(SPECIAL_SYSLOG_SERVICE),g' \ -e 's,@SPECIAL_DBUS_SERVICE\@,$(SPECIAL_DBUS_SERVICE),g' \ -e 's,@SYSTEMCTL\@,$(rootbindir)/systemctl,g' \ + -e 's,@pkgsysconfdir\@,$(pkgsysconfdir),g' \ + -e 's,@pkgdatadir\@,$(pkgdatadir),g' \ + -e 's,@systemunitdir\@,$(systemunitdir),g' \ + -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \ + -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' \ + -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' \ + -e 's,@prefix\@,$(prefix),g' \ < $< > $@ units/%: units/%.in Makefile @@ -456,6 +511,9 @@ units/%: units/%.in Makefile man/%: man/%.in Makefile $(SED_PROCESS) +%.pc: %.pc.in Makefile + $(SED_PROCESS) + M4_PROCESS_SYSTEM = \ $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ $(M4) -P $(M4_DISTRO_FLAG) -DFOR_SYSTEM=1 < $< > $@ @@ -639,4 +697,5 @@ DISTCHECK_CONFIGURE_FLAGS = \ --with-dbussystemservicedir=$$dc_install_base/$(dbussystemservicedir) \ --with-dbusinterfacedir=$$dc_install_base/$(dbusinterfacedir) \ --with-udevrulesdir=$$dc_install_base/$(udevrulesdir) \ + --with-pamlibdir=$$dc_install_base/$(pamlibdir) \ --with-rootdir=$$dc_install_base/$(rootdir) diff --git a/configure.ac b/configure.ac index cdcb71cec..cb419169e 100644 --- a/configure.ac +++ b/configure.ac @@ -352,7 +352,12 @@ AC_ARG_WITH([dbusinterfacedir], AC_ARG_WITH([udevrulesdir], AS_HELP_STRING([--with-udevrulesdir=DIR], [Diectory for udev rules]), [], - [with_udevrulesdir=/lib/udev/rules.d]) + [with_udevrulesdir=`pkg-config --variable=udevdir udev`/rules.d]) + +AC_ARG_WITH([pamlibdir], + AS_HELP_STRING([--with-pamlibdir=DIR], [Diectory for PAM modules]), + [], + [with_pamlibdir=/lib/`$CC -print-multi-os-directory`/security]) AC_ARG_WITH([rootdir], AS_HELP_STRING([--with-rootdir=DIR], [Root directory for files necessary for boot]), @@ -364,6 +369,7 @@ AC_SUBST([dbussessionservicedir], [$with_dbussessionservicedir]) AC_SUBST([dbussystemservicedir], [$with_dbussystemservicedir]) AC_SUBST([dbusinterfacedir], [$with_dbusinterfacedir]) AC_SUBST([udevrulesdir], [$with_udevrulesdir]) +AC_SUBST([pamlibdir], [$with_pamlibdir]) AC_SUBST([rootdir], [$with_rootdir]) AC_CONFIG_FILES([Makefile]) @@ -383,6 +389,7 @@ echo " prefix: ${prefix} root dir: ${with_rootdir} udev rules dir: ${with_udevrulesdir} + pam modules dir: ${with_pamlibdir} dbus policy dir: ${with_dbuspolicydir} dbus session dir: ${with_dbussessionservicedir} dbus system dir: ${with_dbussystemservicedir} diff --git a/fixme b/fixme index 9152a2521..3ca6a02e0 100644 --- a/fixme +++ b/fixme @@ -1,29 +1,17 @@ -* calendar time support in timer +* calendar time support in timer, iCalendar semantics for the timer stuff (RFC2445) * complete dbus exposure -* make conf parser work more like .desktop parsers - * implicitly import "defaults" settings file into all types -* service startup should be delayed if the matching socket is being started - -* add #ifdefs for non-redhat builds in sysv parser - * add #ifdefs for non-sysv builds -* bootchart hookup - * reinvestigate random seed, hwclock * "disabled" load state? -* %m in printf() instead of strerror(); - * gc: don't reap broken services -* iCalendar semantics for the timer stuff (RFC2445) - * ability to kill services? i.e. in contrast to stopping them, go directly into killing mode? @@ -47,10 +35,6 @@ * follow property change dbus spec -* make systemd bus activatable (?) - -* pam module - * selinux External: @@ -68,3 +52,5 @@ Regularly: * check for strerror(r) instead of strerror(-r) * Use PR_SET_PROCTITLE_AREA if it becomes available in the kernel + +* %m in printf() instead of strerror(); diff --git a/src/cgroup-util.c b/src/cgroup-util.c new file mode 100644 index 000000000..9337817b6 --- /dev/null +++ b/src/cgroup-util.c @@ -0,0 +1,694 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "cgroup-util.h" +#include "log.h" +#include "set.h" +#include "macro.h" +#include "util.h" + +int cg_translate_error(int error, int _errno) { + + switch (error) { + + case ECGROUPNOTCOMPILED: + case ECGROUPNOTMOUNTED: + case ECGROUPNOTEXIST: + case ECGROUPNOTCREATED: + return -ENOENT; + + case ECGINVAL: + return -EINVAL; + + case ECGROUPNOTALLOWED: + return -EPERM; + + case ECGOTHER: + return -_errno; + } + + return -EIO; +} + +static struct cgroup* cg_new(const char *controller, const char *path) { + struct cgroup *cgroup; + + assert(path); + assert(controller); + + if (!(cgroup = cgroup_new_cgroup(path))) + return NULL; + + if (!cgroup_add_controller(cgroup, controller)) { + cgroup_free(&cgroup); + return NULL; + } + + return cgroup; +} + +int cg_kill(const char *controller, const char *path, int sig, bool ignore_self) { + bool killed = false, done = false; + Set *s; + pid_t my_pid; + int r, ret = 0; + + assert(controller); + assert(path); + assert(sig >= 0); + + /* This goes through the tasks list and kills them all. This + * is repeated until no further processes are added to the + * tasks list, to properly handle forking processes */ + + if (!(s = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + my_pid = getpid(); + + do { + void *iterator = NULL; + pid_t pid = 0; + + done = true; + + r = cgroup_get_task_begin(path, controller, &iterator, &pid); + while (r == 0) { + + if (pid == my_pid && ignore_self) + goto next; + + if (set_get(s, INT_TO_PTR(pid)) == INT_TO_PTR(pid)) + goto next; + + /* If we haven't killed this process yet, kill + * it */ + + if (kill(pid, sig) < 0 && errno != ESRCH) { + if (ret == 0) + ret = -errno; + } + + killed = true; + done = false; + + if ((r = set_put(s, INT_TO_PTR(pid))) < 0) + goto loop_exit; + + next: + r = cgroup_get_task_next(&iterator, &pid); + } + + if (r == 0 || r == ECGEOF) + r = 0; + else if (r == ECGOTHER && errno == ENOENT) + r = -ESRCH; + else + r = cg_translate_error(r, errno); + + loop_exit: + assert_se(cgroup_get_task_end(&iterator) == 0); + + /* To avoid racing against processes which fork + * quicker than we can kill them we repeat this until + * no new pids need to be killed. */ + + } while (!done && r >= 0); + + set_free(s); + + if (ret < 0) + return ret; + + if (r < 0) + return r; + + return !!killed; +} + +int cg_kill_recursive(const char *controller, const char *path, int sig, bool ignore_self) { + struct cgroup_file_info info; + int level = 0, r, ret = 0; + void *iterator = NULL; + bool killed = false; + + assert(path); + assert(controller); + assert(sig >= 0); + + zero(info); + + r = cgroup_walk_tree_begin(controller, path, 0, &iterator, &info, &level); + while (r == 0) { + int k; + char *p; + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (asprintf(&p, "%s/%s", path, info.path) < 0) { + ret = -ENOMEM; + break; + } + + k = cg_kill(controller, p, sig, ignore_self); + free(p); + + if (k < 0) { + if (ret == 0) + ret = k; + } else if (k > 0) + killed = true; + + next: + + r = cgroup_walk_tree_next(0, &iterator, &info, level); + } + + if (ret == 0) { + if (r == 0 || r == ECGEOF) + ret = !!killed; + else if (r == ECGOTHER && errno == ENOENT) + ret = -ESRCH; + else + ret = cg_translate_error(r, errno); + } + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return ret; +} + +int cg_kill_recursive_and_wait(const char *controller, const char *path) { + unsigned i; + + assert(path); + assert(controller); + + /* This safely kills all processes; first it sends a SIGTERM, + * then checks 8 times after 50ms whether the group is + * now empty, and finally kills everything that is left with + * SIGKILL */ + + for (i = 0; i < 10; i++) { +int sig, r; + + if (i <= 0) + sig = SIGTERM; + else if (i >= 9) + sig = SIGKILL; + else + sig = 0; + + if ((r = cg_kill_recursive(controller, path, sig, true)) <= 0) + return r; + + usleep(50 * USEC_PER_MSEC); + } + + return 0; +} + +int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self) { + bool migrated = false, done = false; + struct cgroup *dest; + int r, ret = 0; + pid_t my_pid; + + assert(controller); + assert(from); + assert(to); + + if (!(dest = cg_new(controller, to))) + return -ENOMEM; + + my_pid = getpid(); + + do { + void *iterator = NULL; + pid_t pid = 0; + + done = true; + + r = cgroup_get_task_begin(from, controller, &iterator, &pid); + while (r == 0) { + + if (pid == my_pid && ignore_self) + goto next; + + if ((r = cgroup_attach_task_pid(dest, pid)) != 0) { + if (ret == 0) + r = cg_translate_error(r, errno); + } + + migrated = true; + done = false; + + next: + + r = cgroup_get_task_next(&iterator, &pid); + } + + if (r == 0 || r == ECGEOF) + r = 0; + else if (r == ECGOTHER && errno == ENOENT) + r = -ESRCH; + else + r = cg_translate_error(r, errno); + + assert_se(cgroup_get_task_end(&iterator) == 0); + + } while (!done && r >= 0); + + cgroup_free(&dest); + + if (ret < 0) + return ret; + + if (r < 0) + return r; + + return !!migrated; +} + +int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self) { + struct cgroup_file_info info; + int level = 0, r, ret = 0; + void *iterator = NULL; + bool migrated = false; + + assert(controller); + assert(from); + assert(to); + + zero(info); + + r = cgroup_walk_tree_begin(controller, from, 0, &iterator, &info, &level); + while (r == 0) { + int k; + char *p; + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (asprintf(&p, "%s/%s", from, info.path) < 0) { + ret = -ENOMEM; + break; + } + + k = cg_migrate(controller, p, to, ignore_self); + free(p); + + if (k < 0) { + if (ret == 0) + ret = k; + } else if (k > 0) + migrated = true; + + next: + r = cgroup_walk_tree_next(0, &iterator, &info, level); + } + + if (ret == 0) { + if (r == 0 || r == ECGEOF) + r = !!migrated; + else if (r == ECGOTHER && errno == ENOENT) + r = -ESRCH; + else + r = cg_translate_error(r, errno); + } + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return ret; +} + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { + char *mp; + int r; + + assert(controller); + assert(path); + + if ((r = cgroup_get_subsys_mount_point(controller, &mp)) != 0) + return cg_translate_error(r, errno); + + if (suffix) + r = asprintf(fs, "%s/%s/%s", mp, path, suffix); + else + r = asprintf(fs, "%s/%s", mp, path); + + free(mp); + + return r < 0 ? -ENOMEM : 0; +} + +int cg_trim(const char *controller, const char *path, bool delete_root) { + char *fs; + int r; + + assert(controller); + assert(path); + + if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + return r; + + r = rm_rf(fs, true, delete_root); + free(fs); + + return r; +} + +int cg_delete(const char *controller, const char *path) { + struct cgroup *cg; + int r; + + assert(controller); + assert(path); + + if (!(cg = cg_new(controller, path))) + return -ENOMEM; + + if ((r = cgroup_delete_cgroup_ext(cg, CGFLAG_DELETE_RECURSIVE|CGFLAG_DELETE_IGNORE_MIGRATION)) != 0) { + r = cg_translate_error(r, errno); + goto finish; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_create(const char *controller, const char *path) { + struct cgroup *cg; + int r; + + assert(controller); + assert(path); + + if (!(cg = cg_new(controller, path))) + return -ENOMEM; + + if ((r = cgroup_create_cgroup(cg, 1)) != 0) { + r = cg_translate_error(r, errno); + goto finish; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_attach(const char *controller, const char *path, pid_t pid) { + struct cgroup *cg; + int r; + + assert(controller); + assert(path); + assert(pid >= 0); + + if (!(cg = cg_new(controller, path))) + return -ENOMEM; + + if (pid == 0) + pid = getpid(); + + if ((r = cgroup_attach_task_pid(cg, pid))) { + r = cg_translate_error(r, errno); + goto finish; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + struct cgroup *cg; + int r; + + assert(controller); + assert(path); + assert(pid >= 0); + + if (!(cg = cg_new(controller, path))) + return -ENOMEM; + + if ((r = cgroup_create_cgroup(cg, 1)) != 0) { + r = cg_translate_error(r, errno); + goto finish; + } + + if (pid == 0) + pid = getpid(); + + if ((r = cgroup_attach_task_pid(cg, pid))) { + r = cg_translate_error(r, errno); + goto finish; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) { + char *fs; + int r; + + assert(controller); + assert(path); + + if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + free(fs); + + return r; +} + +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) { + char *fs; + int r; + + assert(controller); + assert(path); + + if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + free(fs); + + return r; +} + +int cg_get_by_pid(const char *controller, pid_t pid, char **path) { + int r; + char *p = NULL; + + assert(controller); + assert(pid > 0); + assert(path); + + if ((r = cgroup_get_current_controller_path(pid, controller, &p)) != 0) + return cg_translate_error(r, errno); + + assert(p); + + *path = p; + return 0; +} + +int cg_install_release_agent(const char *controller, const char *agent) { + char *mp = NULL, *path = NULL, *contents = NULL, *line = NULL, *sc; + int r; + + assert(controller); + assert(agent); + + if ((r = cgroup_get_subsys_mount_point(controller, &mp)) != 0) + return cg_translate_error(r, errno); + + if (asprintf(&path, "%s/release_agent", mp) < 0) { + r = -ENOMEM; + goto finish; + } + + if ((r = read_one_line_file(path, &contents)) < 0) + goto finish; + + sc = strstrip(contents); + + if (sc[0] == 0) { + + if (asprintf(&line, "%s\n", agent) < 0) { + r = -ENOMEM; + goto finish; + } + + if ((r = write_one_line_file(path, line)) < 0) + goto finish; + + } else if (!streq(sc, agent)) { + r = -EEXIST; + goto finish; + } + + free(path); + path = NULL; + if (asprintf(&path, "%s/notify_on_release", mp) < 0) { + r = -ENOMEM; + goto finish; + } + + free(contents); + contents = NULL; + if ((r = read_one_line_file(path, &contents)) < 0) + goto finish; + + sc = strstrip(contents); + + if (streq(sc, "0")) { + if ((r = write_one_line_file(path, "1\n")) < 0) + goto finish; + } else if (!streq(sc, "1")) { + r = -EIO; + goto finish; + } + + r = 0; + +finish: + free(mp); + free(path); + free(contents); + free(line); + + return r; +} + +int cg_is_empty(const char *controller, const char *path, bool ignore_self) { + void *iterator = NULL; + pid_t pid = 0; + int r; + + assert(controller); + assert(path); + + r = cgroup_get_task_begin(path, controller, &iterator, &pid); + while (r == 0) { + + if (ignore_self&& pid == getpid()) + goto next; + + break; + + next: + r = cgroup_get_task_next(&iterator, &pid); + } + + + if (r == ECGEOF) + r = 1; + else if (r != 0) + r = cg_translate_error(r, errno); + else + r = 0; + + assert_se(cgroup_get_task_end(&iterator) == 0); + + return r; +} + +int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) { + struct cgroup_file_info info; + int level = 0, r, ret = 0; + void *iterator = NULL; + bool empty = true; + + assert(controller); + assert(path); + + zero(info); + + r = cgroup_walk_tree_begin(controller, path, 0, &iterator, &info, &level); + while (r == 0) { + int k; + char *p; + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (asprintf(&p, "%s/%s", path, info.path) < 0) { + ret = -ENOMEM; + break; + } + + k = cg_is_empty(controller, p, ignore_self); + free(p); + + if (k < 0) { + ret = k; + break; + } else if (k == 0) { + empty = false; + break; + } + + next: + r = cgroup_walk_tree_next(0, &iterator, &info, level); + } + + if (ret == 0) { + if (r == 0 || r == ECGEOF) + ret = !!empty; + else if (r == ECGOTHER && errno == ENOENT) + ret = -ESRCH; + else + ret = cg_translate_error(r, errno); + } + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return ret; +} diff --git a/src/cgroup-util.h b/src/cgroup-util.h new file mode 100644 index 000000000..4ada71bc1 --- /dev/null +++ b/src/cgroup-util.h @@ -0,0 +1,54 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foocgrouputilhfoo +#define foocgrouputilhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int cg_translate_error(int error, int _errno); + +int cg_kill(const char *controller, const char *path, int sig, bool ignore_self); +int cg_kill_recursive(const char *controller, const char *path, int sig, bool ignore_self); +int cg_kill_recursive_and_wait(const char *controller, const char *path); + +int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self); +int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self); + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_by_pid(const char *controller, pid_t pid, char **path); + +int cg_trim(const char *controller, const char *path, bool delete_root); +int cg_delete(const char *controller, const char *path); + +int cg_create(const char *controller, const char *path); +int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_create_and_attach(const char *controller, const char *path, pid_t pid); + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); + +int cg_install_release_agent(const char *controller, const char *agent); + +int cg_is_empty(const char *controller, const char *path, bool ignore_self); +int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self); + +#endif diff --git a/src/cgroup.c b/src/cgroup.c index 291db4e6c..27130a9c3 100644 --- a/src/cgroup.c +++ b/src/cgroup.c @@ -26,32 +26,12 @@ #include #include +#include + #include "cgroup.h" +#include "cgroup-util.h" #include "log.h" -static int translate_error(int error, int _errno) { - - switch (error) { - - case ECGROUPNOTCOMPILED: - case ECGROUPNOTMOUNTED: - case ECGROUPNOTEXIST: - case ECGROUPNOTCREATED: - return -ENOENT; - - case ECGINVAL: - return -EINVAL; - - case ECGROUPNOTALLOWED: - return -EPERM; - - case ECGOTHER: - return -_errno; - } - - return -EIO; -} - int cgroup_bonding_realize(CGroupBonding *b) { int r; @@ -59,39 +39,27 @@ int cgroup_bonding_realize(CGroupBonding *b) { assert(b->path); assert(b->controller); - if (b->cgroup) + if (b->realized) return 0; - if (!(b->cgroup = cgroup_new_cgroup(b->path))) - return -ENOMEM; + if ((r = cg_create(b->controller, b->path)) < 0) + return r; - if (!cgroup_add_controller(b->cgroup, b->controller)) { - r = -ENOMEM; - goto fail; - } + b->realized = true; - if ((r = cgroup_create_cgroup(b->cgroup, true)) != 0) { - r = translate_error(r, errno); - goto fail; - } + if (b->only_us && b->clean_up) + cg_trim(b->controller, b->path, false); return 0; - -fail: - cgroup_free(&b->cgroup); - b->cgroup = NULL; - return r; } int cgroup_bonding_realize_list(CGroupBonding *first) { CGroupBonding *b; + int r; - LIST_FOREACH(by_unit, b, first) { - int r; - + LIST_FOREACH(by_unit, b, first) if ((r = cgroup_bonding_realize(b)) < 0) return r; - } return 0; } @@ -113,11 +81,12 @@ void cgroup_bonding_free(CGroupBonding *b) { hashmap_remove(b->unit->meta.manager->cgroup_bondings, b->path); } - if (b->cgroup) { - if (b->only_us && b->clean_up && cgroup_bonding_is_empty(b) > 0) - cgroup_delete_cgroup_ext(b->cgroup, true); + if (b->realized && b->only_us && b->clean_up) { - cgroup_free(&b->cgroup); + if (cgroup_bonding_is_empty(b) > 0) + cg_delete(b->controller, b->path); + else + cg_trim(b->controller, b->path, false); } free(b->controller); @@ -138,109 +107,36 @@ int cgroup_bonding_install(CGroupBonding *b, pid_t pid) { assert(b); assert(pid >= 0); - if (pid == 0) - pid = getpid(); - - if (!b->cgroup) - return -ENOENT; - - if ((r = cgroup_attach_task_pid(b->cgroup, pid))) - return translate_error(r, errno); + if ((r = cg_create_and_attach(b->controller, b->path, pid)) < 0) + return r; + b->realized = true; return 0; } int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid) { CGroupBonding *b; + int r; - LIST_FOREACH(by_unit, b, first) { - int r; - + LIST_FOREACH(by_unit, b, first) if ((r = cgroup_bonding_install(b, pid)) < 0) return r; - } return 0; } int cgroup_bonding_kill(CGroupBonding *b, int sig) { int r; - Set *s; - bool done; - bool killed = false; assert(b); - assert(sig > 0); - - if (!b->only_us) - return -EAGAIN; - - if (!(s = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - do { - void *iterator; - pid_t pid; - - done = true; - - if ((r = cgroup_get_task_begin(b->path, b->controller, &iterator, &pid)) != 0) { - if (r == ECGEOF) { - r = 0; - goto kill_done; - } else { - if (r == ECGOTHER && errno == ENOENT) - r = ESRCH; - else - r = translate_error(r, errno); - break; - } - } - - for (;;) { - if (set_get(s, INT_TO_PTR(pid)) != INT_TO_PTR(pid)) { - - /* If we haven't killed this process - * yet, kill it */ - - if (kill(pid, sig) < 0 && errno != ESRCH) { - r = -errno; - break; - } - - killed = true; - done = false; - - if ((r = set_put(s, INT_TO_PTR(pid))) < 0) - break; - } - - if ((r = cgroup_get_task_next(&iterator, &pid)) != 0) { + assert(sig >= 0); - if (r == ECGEOF) - r = 0; - else - r = translate_error(r, errno); - - break; - } - } - - kill_done: - assert_se(cgroup_get_task_end(&iterator) == 0); - - /* To avoid racing against processes which fork - * quicker than we can kill them we repeat this until - * no new pids need to be killed. */ - - } while (!done && r >= 0); - - set_free(s); - - if (r < 0) + if ((r = cgroup_bonding_realize(b)) < 0) return r; - return killed ? 0 : -ESRCH; + assert(b->realized); + + return cg_kill_recursive(b->controller, b->path, sig, true); } int cgroup_bonding_kill_list(CGroupBonding *first, int sig) { @@ -249,7 +145,7 @@ int cgroup_bonding_kill_list(CGroupBonding *first, int sig) { LIST_FOREACH(by_unit, b, first) { if ((r = cgroup_bonding_kill(b, sig)) < 0) { - if (r == -EAGAIN || -ESRCH) + if (r == -EAGAIN || r == -ESRCH) continue; return r; @@ -264,33 +160,19 @@ int cgroup_bonding_kill_list(CGroupBonding *first, int sig) { /* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we * cannot know */ int cgroup_bonding_is_empty(CGroupBonding *b) { - void *iterator; - pid_t pid; int r; assert(b); - r = cgroup_get_task_begin(b->path, b->controller, &iterator, &pid); - - if (r == 0 || r == ECGEOF) - cgroup_get_task_end(&iterator); + if ((r = cg_is_empty_recursive(b->controller, b->path, true)) < 0) + return r; - /* Hmm, no PID in this group? Then it is definitely empty */ - if (r == ECGEOF) + /* If it is empty it is empty */ + if (r > 0) return 1; - /* Some error? Let's return it */ - if (r != 0) - return translate_error(r, errno); - - /* It's not empty, and we are the only user, then it is - * definitely not empty */ - if (b->only_us) - return 0; - - /* There are PIDs in the group but we aren't the only users, - * hence we cannot say */ - return -EAGAIN; + /* It's not only us using this cgroup, so we just don't know */ + return b->only_us ? 0 : -EAGAIN; } int cgroup_bonding_is_empty_list(CGroupBonding *first) { @@ -313,99 +195,6 @@ int cgroup_bonding_is_empty_list(CGroupBonding *first) { return -EAGAIN; } -static int install_release_agent(Manager *m, const char *mount_point) { - char *p, *c, *sc; - int r; - - assert(m); - assert(mount_point); - - if (asprintf(&p, "%s/release_agent", mount_point) < 0) - return -ENOMEM; - - if ((r = read_one_line_file(p, &c)) < 0) { - free(p); - return r; - } - - sc = strstrip(c); - - if (sc[0] == 0) { - if ((r = write_one_line_file(p, CGROUP_AGENT_PATH "\n" )) < 0) { - free(p); - free(c); - return r; - } - } else if (!streq(sc, CGROUP_AGENT_PATH)) { - free(p); - free(c); - return -EEXIST; - } - - free(c); - free(p); - - if (asprintf(&p, "%s/notify_on_release", mount_point) < 0) - return -ENOMEM; - - if ((r = read_one_line_file(p, &c)) < 0) { - free(p); - return r; - } - - sc = strstrip(c); - - if (streq(sc, "0")) { - if ((r = write_one_line_file(p, "1\n")) < 0) { - free(p); - free(c); - return r; - } - } else if (!streq(sc, "1")) { - free(p); - free(c); - return -EIO; - } - - free(p); - free(c); - - return 0; -} - -static int create_hierarchy_cgroup(Manager *m) { - struct cgroup *cg; - int r; - - assert(m); - - if (!(cg = cgroup_new_cgroup(m->cgroup_hierarchy))) - return -ENOMEM; - - if (!(cgroup_add_controller(cg, m->cgroup_controller))) { - r = -ENOMEM; - goto finish; - } - - if ((r = cgroup_create_cgroup(cg, true)) != 0) { - log_error("Failed to create cgroup hierarchy group: %s", cgroup_strerror(r)); - r = translate_error(r, errno); - goto finish; - } - - if ((r = cgroup_attach_task(cg)) != 0) { - log_error("Failed to add ourselves to hierarchy group: %s", cgroup_strerror(r)); - r = translate_error(r, errno); - goto finish; - } - - r = 0; - -finish: - cgroup_free(&cg); - return r; -} - int manager_setup_cgroup(Manager *m) { char *cp; int r; @@ -416,7 +205,7 @@ int manager_setup_cgroup(Manager *m) { if ((r = cgroup_init()) != 0) { log_error("Failed to initialize libcg: %s", cgroup_strerror(r)); - return translate_error(r, errno); + return cg_translate_error(r, errno); } free(m->cgroup_controller); @@ -426,12 +215,12 @@ int manager_setup_cgroup(Manager *m) { free(m->cgroup_mount_point); m->cgroup_mount_point = NULL; if ((r = cgroup_get_subsys_mount_point(m->cgroup_controller, &m->cgroup_mount_point))) - return translate_error(r, errno); + return cg_translate_error(r, errno); pid = getpid(); if ((r = cgroup_get_current_controller_path(pid, m->cgroup_controller, &cp))) - return translate_error(r, errno); + return cg_translate_error(r, errno); snprintf(suffix, sizeof(suffix), "/systemd-%u", (unsigned) pid); char_array_0(suffix); @@ -457,12 +246,12 @@ int manager_setup_cgroup(Manager *m) { m->cgroup_mount_point, m->cgroup_hierarchy); - if ((r = install_release_agent(m, m->cgroup_mount_point)) < 0) + if ((r = cg_install_release_agent(m->cgroup_controller, CGROUP_AGENT_PATH)) < 0) log_warning("Failed to install release agent, ignoring: %s", strerror(-r)); else log_debug("Installed release agent, or already installed."); - if ((r = create_hierarchy_cgroup(m)) < 0) + if ((r = cg_create_and_attach(m->cgroup_controller, m->cgroup_hierarchy, 0)) < 0) log_error("Failed to create root cgroup hierarchy: %s", strerror(-r)); else log_debug("Created root group."); @@ -470,33 +259,13 @@ int manager_setup_cgroup(Manager *m) { return r; } -int manager_shutdown_cgroup(Manager *m, bool delete) { - struct cgroup *cg; - int r; - +int manager_shutdown_cgroup(Manager *m) { assert(m); - if (!m->cgroup_hierarchy) + if (!m->cgroup_controller || !m->cgroup_hierarchy) return 0; - if (!(cg = cgroup_new_cgroup(m->cgroup_hierarchy))) - return -ENOMEM; - - if (!(cgroup_add_controller(cg, m->cgroup_controller))) { - r = -ENOMEM; - goto finish; - } - - /* Often enough we won't be able to delete the cgroup we - * ourselves are in, hence ignore all errors here */ - if (delete) - cgroup_delete_cgroup_ext(cg, CGFLAG_DELETE_IGNORE_MIGRATION|CGFLAG_DELETE_RECURSIVE); - r = 0; - -finish: - cgroup_free(&cg); - return r; - + return cg_delete(m->cgroup_controller, m->cgroup_hierarchy); } int cgroup_notify_empty(Manager *m, const char *group) { @@ -541,15 +310,12 @@ Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) { if (pid <= 1) return NULL; - if ((r = cgroup_get_current_controller_path(pid, m->cgroup_controller, &group))) + if ((r = cg_get_by_pid(m->cgroup_controller, pid, &group))) return NULL; l = hashmap_get(m->cgroup_bondings, group); free(group); - if (!l) - return NULL; - LIST_FOREACH(by_path, b, l) { if (!b->unit) diff --git a/src/cgroup.h b/src/cgroup.h index 67c7cc350..11d2aba8c 100644 --- a/src/cgroup.h +++ b/src/cgroup.h @@ -22,8 +22,6 @@ along with systemd; If not, see . ***/ -#include - typedef struct CGroupBonding CGroupBonding; #include "unit.h" @@ -35,8 +33,6 @@ struct CGroupBonding { Unit *unit; - struct cgroup *cgroup; - /* For the Unit::cgroup_bondings list */ LIST_FIELDS(CGroupBonding, by_unit); @@ -48,6 +44,9 @@ struct CGroupBonding { /* When our tasks are the only ones in this group */ bool only_us:1; + + /* This cgroup is realized */ + bool realized:1; }; int cgroup_bonding_realize(CGroupBonding *b); @@ -72,7 +71,7 @@ char *cgroup_bonding_to_string(CGroupBonding *b); #include "manager.h" int manager_setup_cgroup(Manager *m); -int manager_shutdown_cgroup(Manager *m, bool delete); +int manager_shutdown_cgroup(Manager *m); int cgroup_notify_empty(Manager *m, const char *group); diff --git a/src/manager.c b/src/manager.c index 554dd8e86..f1a79b5e8 100644 --- a/src/manager.c +++ b/src/manager.c @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -421,7 +420,8 @@ void manager_free(Manager *m) { /* If we reexecute ourselves, we keep the root cgroup * around */ - manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); + if (m->exit_code != MANAGER_REEXECUTE) + manager_shutdown_cgroup(m); bus_done(m); diff --git a/src/pam-module.c b/src/pam-module.c new file mode 100644 index 000000000..5157897a7 --- /dev/null +++ b/src/pam-module.c @@ -0,0 +1,466 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "util.h" +#include "cgroup-util.h" +#include "macro.h" +#include "sd-daemon.h" + +static int parse_argv(pam_handle_t *handle, + int argc, const char **argv, + bool *create_session, + bool *kill_session, + bool *kill_user) { + + unsigned i; + + assert(argc >= 0); + assert(argc == 0 || argv); + + for (i = 0; i < (unsigned) argc; i++) { + int k; + + if (startswith(argv[i], "create-session=")) { + if ((k = parse_boolean(argv[i] + 15)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to parse create-session= argument."); + return k; + } + + if (create_session) + *create_session = k; + } else if (startswith(argv[i], "kill-session=")) { + if ((k = parse_boolean(argv[i] + 13)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument."); + return k; + } + + if (kill_session) + *kill_session = k; + + } else if (startswith(argv[i], "kill-user=")) { + if ((k = parse_boolean(argv[i] + 10)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to parse kill-user= argument."); + return k; + } + + if (kill_user) + *kill_user = k; + } else { + pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]); + return -EINVAL; + } + } + + if (kill_session && *kill_session && kill_user) + *kill_user = true; + + return 0; +} + +static int open_file_and_lock(const char *fn) { + int fd; + + assert(fn); + + if ((fd = open(fn, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_CREAT, 0600)) < 0) + return -errno; + + if (flock(fd, LOCK_EX) < 0) + return -errno; + + return fd; +} + +static uint32_t combine32(unsigned long long u) { + uint32_t r = 0; + unsigned i; + + for (i = 0; i < sizeof(u)/4; i ++) + r ^= (uint32_t) ((u >> (i*32)) & 0xFFFFFFFFULL); + + return r; +} + +static char *generate_session_cookie(void) { + char *machine; + char *cookie; + unsigned long long r; + usec_t u; + int k; + + if (getmachineid_malloc(&machine) < 0) + if (!(machine = gethostname_malloc())) + return NULL; + + r = random_ull(); + u = now(CLOCK_REALTIME); + + k = asprintf(&cookie, "%s-%lu-%lu", + machine, + (unsigned long) combine32(r), + (unsigned long) combine32((unsigned long long) u)); + free(machine); + + return k < 0 ? NULL : cookie; +} + +static int get_user_data( + pam_handle_t *handle, + const char **ret_username, + struct passwd **ret_pw) { + + const char *username; + struct passwd *pw; + int r; + + assert(handle); + assert(ret_username); + assert(ret_pw); + + if ((r = pam_get_user(handle, &username, NULL)) != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to get user name."); + return r; + } + + if (!username || !*username) { + pam_syslog(handle, LOG_ERR, "User name not valid."); + return PAM_AUTH_ERR; + } + + if (!(pw = pam_modutil_getpwnam(handle, username))) { + pam_syslog(handle, LOG_ERR, "Failed to get user data."); + return PAM_USER_UNKNOWN; + } + + *ret_pw = pw; + *ret_username = username; + + return PAM_SUCCESS; +} + +static int create_user_group(pam_handle_t *handle, const char *group, struct passwd *pw, bool attach) { + int r; + + assert(handle); + assert(group); + + if (attach) + r = cg_create_and_attach("name=systemd", group, 0); + else + r = cg_create("name=systemd", group); + + if (r < 0) { + pam_syslog(handle, LOG_ERR, "Failed to create cgroup: %s", strerror(-r)); + return PAM_SESSION_ERR; + } + + if ((r = cg_set_task_access("name=systemd", group, 0755, pw->pw_uid, pw->pw_gid)) < 0 || + (r = cg_set_group_access("name=systemd", group, 0755, pw->pw_uid, pw->pw_gid)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to change access modes: %s", strerror(-r)); + return PAM_SESSION_ERR; + } + + return PAM_SUCCESS; +} + +_public_ PAM_EXTERN int pam_sm_open_session( + pam_handle_t *handle, + int flags, + int argc, const char **argv) { + + const char *username = NULL; + struct passwd *pw; + int r; + char *buf = NULL; + int lock_fd = -1; + bool create_session = true; + + assert(handle); + + pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); + + if (parse_argv(handle, argc, argv, &create_session, NULL, NULL) < 0) + return PAM_SESSION_ERR; + + /* Make this a NOP on non-systemd systems */ + if (sd_booted() <= 0) + return PAM_SUCCESS; + + if ((r = cgroup_init()) != 0) { + pam_syslog(handle, LOG_ERR, "libcgroup initialization failed: %s", cgroup_strerror(r)); + r = PAM_SESSION_ERR; + goto finish; + } + + if ((r = get_user_data(handle, &username, &pw)) != PAM_SUCCESS) + goto finish; + + if (safe_mkdir(RUNTIME_DIR "/user", 0755, 0, 0) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to create runtime directory: %m"); + r = PAM_SYSTEM_ERR; + goto finish; + } + + if ((lock_fd = open_file_and_lock(RUNTIME_DIR "/user/.pam-systemd-lock")) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to lock runtime directory: %m"); + r = PAM_SYSTEM_ERR; + goto finish; + } + + /* Create /var/run/$USER */ + free(buf); + if (asprintf(&buf, RUNTIME_DIR "/user/%s", username) < 0) { + r = PAM_BUF_ERR; + goto finish; + } + + if (safe_mkdir(buf, 0700, pw->pw_uid, pw->pw_gid) < 0) { + pam_syslog(handle, LOG_WARNING, "Failed to create runtime directory: %m"); + r = PAM_SYSTEM_ERR; + goto finish; + } else if ((r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", buf, 0)) != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set runtime dir."); + goto finish; + } + + free(buf); + buf = NULL; + + if (create_session) { + const char *cookie; + + /* Reuse or create XDG session ID */ + if (!(cookie = pam_getenv(handle, "XDG_SESSION_COOKIE"))) { + if (!(buf = generate_session_cookie())) { + r = PAM_BUF_ERR; + goto finish; + } + + if ((r = pam_misc_setenv(handle, "XDG_SESSION_COOKIE", buf, 0)) != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set cookie."); + goto finish; + } + + if (!(cookie = pam_getenv(handle, "XDG_SESSION_COOKIE"))) { + pam_syslog(handle, LOG_ERR, "Failed to get cookie."); + r = PAM_SESSION_ERR; + goto finish; + } + } + + r = asprintf(&buf, "/user/%s/%s", username, cookie); + } else + r = asprintf(&buf, "/user/%s/no-session", username); + + if (r < 0) { + r = PAM_BUF_ERR; + goto finish; + } + + if ((r = create_user_group(handle, buf, pw, true)) != PAM_SUCCESS) + goto finish; + + r = PAM_SUCCESS; + +finish: + free(buf); + + if (lock_fd >= 0) + close_nointr_nofail(lock_fd); + + return r; +} + +static int session_remains(pam_handle_t *handle, const char *user_path) { + struct cgroup_file_info info; + int level = 0, r; + void *iterator = NULL; + bool remains = false; + + zero(info); + + r = cgroup_walk_tree_begin("name=systemd", user_path, 0, &iterator, &info, &level); + while (r == 0) { + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (streq(info.path, "")) + goto next; + + if (streq(info.path, "no-session")) + goto next; + + remains = true; + break; + + next: + + r = cgroup_walk_tree_next(0, &iterator, &info, level); + } + + + if (remains) + r = 1; + else if (r == 0 || r == ECGEOF) + r = 0; + else + r = cg_translate_error(r, errno); + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return r; +} + +_public_ PAM_EXTERN int pam_sm_close_session( + pam_handle_t *handle, + int flags, + int argc, const char **argv) { + + const char *username = NULL; + bool kill_session = false; + bool kill_user = false; + int lock_fd = -1, r; + char *session_path = NULL, *nosession_path = NULL, *user_path = NULL; + const char *cookie; + struct passwd *pw; + + assert(handle); + + if (parse_argv(handle, argc, argv, NULL, &kill_session, &kill_user) < 0) + return PAM_SESSION_ERR; + + /* Make this a NOP on non-systemd systems */ + if (sd_booted() <= 0) + return PAM_SUCCESS; + + if ((r = get_user_data(handle, &username, &pw)) != PAM_SUCCESS) + goto finish; + + if ((lock_fd = open_file_and_lock(RUNTIME_DIR "/user/.pam-systemd-lock")) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to lock runtime directory: %m"); + r = PAM_SYSTEM_ERR; + goto finish; + } + + if (asprintf(&user_path, "/user/%s", username) < 0) { + r = PAM_BUF_ERR; + goto finish; + } + + if ((cookie = pam_getenv(handle, "XDG_SESSION_COOKIE"))) { + + if (asprintf(&session_path, "/user/%s/%s", username, cookie) < 0 || + asprintf(&nosession_path, "/user/%s/no-session", username) < 0) { + r = PAM_BUF_ERR; + goto finish; + } + + if (kill_session) { + pam_syslog(handle, LOG_INFO, "KILLING ENTER"); + + /* Kill processes in session cgroup */ + if ((r = cg_kill_recursive_and_wait("name=systemd", session_path)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to kill session cgroup: %s", strerror(-r)); + + pam_syslog(handle, LOG_INFO, "KILLING EXIT"); + + } else { + /* Migrate processes from session to + * no-session cgroup. First, try to create the + * no-session group in case it doesn't exist + * yet. */ + create_user_group(handle, nosession_path, pw, 0); + + if ((r = cg_migrate_recursive("name=systemd", session_path, nosession_path, false)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to migrate session cgroup: %s", strerror(-r)); + } + + /* Delete session cgroup */ + if (r < 0) + pam_syslog(handle, LOG_INFO, "Couldn't empty session cgroup, not deleting."); + else { + if ((r = cg_delete("name=systemd", session_path)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to delete session cgroup: %s", strerror(-r)); + } + } + + /* GC user tree */ + cg_trim("name=systemd", user_path, false); + + if ((r = session_remains(handle, user_path)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to determine whether a session remains: %s", strerror(-r)); + + /* Kill user processes not attached to any session */ + if (kill_user && r == 0) { + + /* Kill no-session cgroup */ + if ((r = cg_kill_recursive_and_wait("name=systemd", user_path)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to kill user cgroup: %s", strerror(-r)); + } else { + + if ((r = cg_is_empty_recursive("name=systemd", user_path, true)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to check user cgroup: %s", strerror(-r)); + + /* If we managed to kill somebody, don't cleanup the cgroup. */ + if (r == 0) + r = -EBUSY; + } + + if (r >= 0) { + const char *runtime_dir; + + /* Remove user cgroup */ + if ((r = cg_delete("name=systemd", user_path)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to delete user cgroup: %s", strerror(-r)); + + /* This will migrate us to the /user cgroup. */ + + if ((runtime_dir = pam_getenv(handle, "XDG_RUNTIME_DIR"))) + if ((r = rm_rf(runtime_dir, false, true)) < 0) + pam_syslog(handle, LOG_ERR, "Failed to remove runtime directory: %s", strerror(-r)); + } + + r = PAM_SUCCESS; + +finish: + if (lock_fd >= 0) + close_nointr_nofail(lock_fd); + + free(session_path); + free(nosession_path); + free(user_path); + + return r; +} diff --git a/src/test-cgroup.c b/src/test-cgroup.c new file mode 100644 index 000000000..389df6d39 --- /dev/null +++ b/src/test-cgroup.c @@ -0,0 +1,84 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include + +#include "cgroup-util.h" +#include "util.h" +#include "log.h" + +int main(int argc, char*argv[]) { + char *path; + + assert_se(cgroup_init() == 0); + + assert_se(cg_create("name=systemd", "/test-a") == 0); + assert_se(cg_create("name=systemd", "/test-a") == 0); + assert_se(cg_create("name=systemd", "/test-b") == 0); + assert_se(cg_create("name=systemd", "/test-b/test-c") == 0); + assert_se(cg_create_and_attach("name=systemd", "/test-b", 0) == 0); + + assert_se(cg_get_by_pid("name=systemd", getpid(), &path) == 0); + assert_se(streq(path, "/test-b")); + free(path); + + assert_se(cg_attach("name=systemd", "/test-a", 0) == 0); + + assert_se(cg_get_by_pid("name=systemd", getpid(), &path) == 0); + assert_se(path_equal(path, "/test-a")); + free(path); + + assert_se(cg_create_and_attach("name=systemd", "/test-b/test-d", 0) == 0); + + assert_se(cg_get_by_pid("name=systemd", getpid(), &path) == 0); + assert_se(path_equal(path, "/test-b/test-d")); + free(path); + + assert_se(cg_get_path("name=systemd", "/test-b/test-d", NULL, &path) == 0); + assert_se(path_equal(path, "/cgroup/systemd/test-b/test-d")); + free(path); + + assert_se(cg_is_empty("name=systemd", "/test-a", false) > 0); + assert_se(cg_is_empty("name=systemd", "/test-b", false) > 0); + assert_se(cg_is_empty_recursive("name=systemd", "/test-a", false) > 0); + assert_se(cg_is_empty_recursive("name=systemd", "/test-b", false) == 0); + + assert_se(cg_kill_recursive("name=systemd", "/test-a", 0, false) == 0); + assert_se(cg_kill_recursive("name=systemd", "/test-b", 0, false) > 0); + + assert_se(cg_migrate_recursive("name=systemd", "/test-b", "/test-a", false) == 0); + + assert_se(cg_is_empty_recursive("name=systemd", "/test-a", false) == 0); + assert_se(cg_is_empty_recursive("name=systemd", "/test-b", false) > 0); + + assert_se(cg_kill_recursive("name=systemd", "/test-a", 0, false) > 0); + assert_se(cg_kill_recursive("name=systemd", "/test-b", 0, false) == 0); + + cg_trim("name=systemd", "/", false); + + assert_se(cg_delete("name=systemd", "/test-b") < 0); + assert_se(cg_delete("name=systemd", "/test-a") == 0); + + return 0; +} diff --git a/src/util.c b/src/util.c index 6fa9dec3a..11ab07456 100644 --- a/src/util.c +++ b/src/util.c @@ -846,6 +846,28 @@ char *file_in_same_dir(const char *path, const char *filename) { return r; } +int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) { + struct stat st; + + if (mkdir(path, mode) >= 0) + if (chmod_and_chown(path, mode, uid, gid) < 0) + return -errno; + + if (lstat(path, &st) < 0) + return -errno; + + if ((st.st_mode & 0777) != mode || + st.st_uid != uid || + st.st_gid != gid || + !S_ISDIR(st.st_mode)) { + errno = EEXIST; + return -errno; + } + + return 0; +} + + int mkdir_parents(const char *path, mode_t mode) { const char *p, *e; @@ -2325,6 +2347,18 @@ char* gethostname_malloc(void) { return strdup(u.sysname); } +int getmachineid_malloc(char **b) { + int r; + + assert(b); + + if ((r = read_one_line_file("/var/lib/dbus/machine-id", b)) < 0) + return r; + + strstrip(*b); + return 0; +} + char* getlogname_malloc(void) { uid_t uid; long bufsize; @@ -2361,11 +2395,13 @@ char* getlogname_malloc(void) { return name; } -char *getttyname_malloc(void) { - char path[PATH_MAX], *p; +int getttyname_malloc(char **r) { + char path[PATH_MAX], *p, *c; + + assert(r); if (ttyname_r(STDIN_FILENO, path, sizeof(path)) < 0) - return strdup("unknown"); + return -errno; char_array_0(path); @@ -2373,7 +2409,132 @@ char *getttyname_malloc(void) { if (startswith(path, "/dev/")) p += 5; - return strdup(p); + if (!(c = strdup(p))) + return -ENOMEM; + + *r = c; + return 0; +} + +static int rm_rf_children(int fd, bool only_dirs) { + DIR *d; + int ret = 0; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on */ + + if (!(d = fdopendir(fd))) { + close_nointr_nofail(fd); + return -errno; + } + + for (;;) { + struct dirent buf, *de; + bool is_dir; + int r; + + if ((r = readdir_r(d, &buf, &de)) != 0) { + if (ret == 0) + ret = -r; + break; + } + + if (!de) + break; + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN) { + struct stat st; + + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0) + ret = -errno; + continue; + } + + is_dir = S_ISDIR(st.st_mode); + } else + is_dir = de->d_type == DT_DIR; + + if (is_dir) { + int subdir_fd; + + if ((subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) { + if (ret == 0) + ret = -errno; + continue; + } + + if ((r = rm_rf_children(subdir_fd, only_dirs)) < 0) { + if (ret == 0) + ret = r; + } + + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0) + ret = -errno; + } + } else if (!only_dirs) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0) + ret = -errno; + } + } + } + + closedir(d); + + return ret; +} + +int rm_rf(const char *path, bool only_dirs, bool delete_root) { + int fd; + int r; + + assert(path); + + if ((fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) { + + if (errno != ENOTDIR) + return -errno; + + if (delete_root && !only_dirs) + if (unlink(path) < 0) + return -errno; + + return 0; + } + + r = rm_rf_children(fd, only_dirs); + + if (delete_root) + if (rmdir(path) < 0) { + if (r == 0) + r = -errno; + } + + return r; +} + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + assert(path); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (chmod(path, mode) < 0) + return -errno; + + if (chown(path, uid, gid) < 0) + return -errno; + + return 0; } static const char *const ioprio_class_table[] = { diff --git a/src/util.h b/src/util.h index 9af2ca8ae..864d98fa6 100644 --- a/src/util.h +++ b/src/util.h @@ -160,6 +160,7 @@ char *delete_chars(char *s, const char *bad); char *truncate_nl(char *s); char *file_in_same_dir(const char *path, const char *filename); +int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid); int mkdir_parents(const char *path, mode_t mode); int mkdir_p(const char *path, mode_t mode); @@ -263,7 +264,12 @@ void sigset_add_many(sigset_t *ss, ...); char* gethostname_malloc(void); char* getlogname_malloc(void); -char *getttyname_malloc(void); +int getttyname_malloc(char **r); +int getmachineid_malloc(char **r); + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); + +int rm_rf(const char *path, bool only_dirs, bool delete_root); const char *ioprio_class_to_string(int i); int ioprio_class_from_string(const char *s); diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c index d7cda82d5..45da79c68 100644 --- a/src/utmp-wtmp.c +++ b/src/utmp-wtmp.c @@ -296,12 +296,14 @@ int utmp_wall(const char *message) { time_t t; if (!(hn = gethostname_malloc()) || - !(un = getlogname_malloc()) || - !(tty = getttyname_malloc())) { + !(un = getlogname_malloc())) { r = -ENOMEM; goto finish; } + if ((r = getttyname_malloc(&tty)) < 0) + goto finish; + time(&t); assert_se(ctime_r(&t, date)); delete_chars(date, "\n\r"); diff --git a/systemd.pc.in b/systemd.pc.in new file mode 100644 index 000000000..3f8ed5c3e --- /dev/null +++ b/systemd.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=${prefix} +systemdsystemunitdir=@systemunitdir@ +systemdsessionunitdir=@pkgdatadir@/session +systemdsystemconfdir=@pkgsysconfdir@/system +systemdsessionconfdir=@pkgsysconfdir@/session + +Name: systemd +Description: systemd System and Session Manager +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ -- 2.30.2