[systemd-commits] 3 commits - configure.ac fixme .gitignore Makefile.am src/cgroup.c src/cgroup.h src/cgroup-util.c src/cgroup-util.h src/macro.h src/manager.c src/notify.c src/pam-module.c src/sd-daemon.c src/sd-daemon.h src/test-cgroup.c src/util.c src/util.h src/utmp-wtmp.c systemd.pc.in

Lennart Poettering lennart at kemper.freedesktop.org
Mon Jun 21 14:27:50 PDT 2010


 .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/macro.h       |    2 
 src/manager.c     |    4 
 src/notify.c      |   15 +
 src/pam-module.c  |  466 ++++++++++++++++++++++++++++++++++++
 src/sd-daemon.c   |   20 +
 src/sd-daemon.h   |  291 +++++++++++++---------
 src/test-cgroup.c |   84 ++++++
 src/util.c        |  169 ++++++++++++-
 src/util.h        |    8 
 src/utmp-wtmp.c   |    6 
 systemd.pc.in     |   11 
 19 files changed, 1816 insertions(+), 432 deletions(-)

New commits:
commit 8c6db8336536916d0476ff8233e0abf40a2f6aab
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jun 21 23:27:18 2010 +0200

    pam: implement systemd PAM module and generelize cgroup API for that a bit

diff --git a/.gitignore b/.gitignore
index 2316dd5..ed881d7 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 76662b1..ae83c4a 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, at rootlibexecdir\@,$(rootlibexecdir),g' \
 		-e 's, at SPECIAL_SYSLOG_SERVICE\@,$(SPECIAL_SYSLOG_SERVICE),g' \
 		-e 's, at SPECIAL_DBUS_SERVICE\@,$(SPECIAL_DBUS_SERVICE),g' \
 		-e 's, at SYSTEMCTL\@,$(rootbindir)/systemctl,g' \
+		-e 's, at pkgsysconfdir\@,$(pkgsysconfdir),g' \
+		-e 's, at pkgdatadir\@,$(pkgdatadir),g' \
+		-e 's, at systemunitdir\@,$(systemunitdir),g' \
+		-e 's, at PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \
+		-e 's, at PACKAGE_NAME\@,$(PACKAGE_NAME),g' \
+		-e 's, at PACKAGE_URL\@,$(PACKAGE_URL),g' \
+		-e 's, at 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 cdcb71c..cb41916 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 9152a25..3ca6a02 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 0000000..9337817
--- /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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libcgroup.h>
+
+#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 0000000..4ada71b
--- /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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+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 291db4e..27130a9 100644
--- a/src/cgroup.c
+++ b/src/cgroup.c
@@ -26,32 +26,12 @@
 #include <signal.h>
 #include <sys/mount.h>
 
+#include <libcgroup.h>
+
 #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 67c7cc3..11d2aba 100644
--- a/src/cgroup.h
+++ b/src/cgroup.h
@@ -22,8 +22,6 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <libcgroup.h>
-
 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 554dd8e..f1a79b5 100644
--- a/src/manager.c
+++ b/src/manager.c
@@ -32,7 +32,6 @@
 #include <sys/reboot.h>
 #include <sys/ioctl.h>
 #include <linux/kd.h>
-#include <libcgroup.h>
 #include <termios.h>
 #include <fcntl.h>
 #include <sys/types.h>
@@ -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 0000000..5157897
--- /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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <pwd.h>
+
+#include <security/pam_modules.h>
+#include <security/_pam_macros.h>
+#include <security/pam_modutil.h>
+#include <security/pam_ext.h>
+#include <security/pam_misc.h>
+
+#include <libcgroup.h>
+
+#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 0000000..389df6d
--- /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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <string.h>
+
+#include <libcgroup.h>
+
+#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 6fa9dec..11ab074 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 9af2ca8..864d98f 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 d7cda82..45da79c 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 0000000..3f8ed5c
--- /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@
commit 96551bae6107936a4576b9b4b391abbc9963bdfe
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jun 21 19:20:21 2010 +0200

    notify: add systemd-notify --booted

diff --git a/src/notify.c b/src/notify.c
index 864b7c2..7797810 100644
--- a/src/notify.c
+++ b/src/notify.c
@@ -35,6 +35,7 @@
 static bool arg_ready = false;
 static pid_t arg_pid = 0;
 static const char *arg_status = NULL;
+static bool arg_booted = false;
 
 static int help(void) {
 
@@ -43,7 +44,8 @@ static int help(void) {
                "  -h --help         Show this help\n"
                "     --ready        Inform the init system about service start-up completion\n"
                "     --pid[=PID]    Set main pid of daemon\n"
-               "     --status=TEXT  Set status text\n",
+               "     --status=TEXT  Set status text\n"
+               "     --booted       Returns 0 if the system was booted up with systemd, non-zero otherwise\n",
                program_invocation_short_name);
 
         return 0;
@@ -54,7 +56,8 @@ static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_READY = 0x100,
                 ARG_PID,
-                ARG_STATUS
+                ARG_STATUS,
+                ARG_BOOTED
         };
 
         static const struct option options[] = {
@@ -62,6 +65,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "ready",     no_argument,       NULL, ARG_READY   },
                 { "pid",       optional_argument, NULL, ARG_PID     },
                 { "status",    required_argument, NULL, ARG_STATUS  },
+                { "booted",    no_argument,       NULL, ARG_BOOTED  },
                 { NULL,        0,                 NULL, 0           }
         };
 
@@ -98,6 +102,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_status = optarg;
                         break;
 
+                case ARG_BOOTED:
+                        arg_booted = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -123,6 +131,9 @@ int main(int argc, char* argv[]) {
                 goto finish;
         }
 
+        if (arg_booted)
+                return sd_booted() <= 0;
+
         if (arg_ready)
                 our_env[i++] = (char*) "READY=1";
 
commit 40473a70cf5ac6743d9e4e70f3229203a278ff9c
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Jun 21 19:17:47 2010 +0200

    sd-daemon: introduce sd_booted() and set ELF visibility to hidden for all symbols

diff --git a/src/macro.h b/src/macro.h
index 763a4de..d00e70b 100644
--- a/src/macro.h
+++ b/src/macro.h
@@ -38,6 +38,8 @@
 #define _weak_ __attribute__ ((weak))
 #define _likely_(x) (__builtin_expect(!!(x),1))
 #define _unlikely_(x) (__builtin_expect(!!(x),0))
+#define _public_ __attribute__ ((visibility("default")))
+#define _hidden_ __attribute__ ((visibility("hidden")))
 
 /* Rounds up */
 static inline size_t ALIGN(size_t l) {
diff --git a/src/sd-daemon.c b/src/sd-daemon.c
index e6b9a6f..cb568b5 100644
--- a/src/sd-daemon.c
+++ b/src/sd-daemon.c
@@ -426,3 +426,23 @@ int sd_notifyf(int unset_environment, const char *format, ...) {
         return r;
 #endif
 }
+
+int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+
+        struct stat a, b;
+
+        /* We simply test whether the systemd cgroup hierarchy is
+         * mounted */
+
+        if (lstat("/cgroup", &a) < 0)
+                return 0;
+
+        if (lstat("/cgroup/systemd", &b) < 0)
+                return 0;
+
+        return a.st_dev != b.st_dev;
+#endif
+}
diff --git a/src/sd-daemon.h b/src/sd-daemon.h
index 20c260c..dc18f42 100644
--- a/src/sd-daemon.h
+++ b/src/sd-daemon.h
@@ -34,17 +34,43 @@
 extern "C" {
 #endif
 
-/* Reference implementation of a few systemd related interfaces for
- * writing daemons. These interfaces are trivial to implement. To
- * simplify porting we provide this reference
- * implementation. Applications are welcome to reimplement the
- * algorithms described here, if they do not want to include these two
- * source files. */
-
-#ifdef __GNUC__
+/*
+  Reference implementation of a few systemd related interfaces for
+  writing daemons. These interfaces are trivial to implement. To
+  simplify porting we provide this reference implementation.
+  Applications are welcome to reimplement the algorithms described
+  here if they do not want to include these two source files.
+
+  The following functionality is provided:
+
+  - Support for logging with log levels on stderr
+  - File descriptor passing for socket-based activation
+  - Daemon startup and status notification
+  - Detection of systemd boots
+
+  You may compile this with -DDISABLE_SYSTEMD to disable systemd
+  support. This make all those calls NOPs that are directly related to
+  systemd (i.e. only sd_is_xxx() will stay useful).
+
+  Since this is drop-in code we don't want any of our symbols to be
+  exported in any case. Hence we declare hidden visibility for all of
+  them.
+
+  You may find an up-to-date version of these source files online:
+
+  http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h
+  http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c
+
+  This should compile on non-Linux systems, too, but with the
+  exception of the sd_is_xxx() calls all functions will become NOPs.
+*/
+
+#if __GNUC__ >= 4
 #define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#define _sd_hidden_ __attribute__ ((visibility("hidden")))
 #else
 #define _sd_printf_attr_(a,b)
+#define _sd_hidden_
 #endif
 
 /*
@@ -54,7 +80,6 @@ extern "C" {
 
   This is similar to printk() usage in the kernel.
 */
-
 #define SD_EMERG   "<0>"  /* system is unusable */
 #define SD_ALERT   "<1>"  /* action must be taken immediately */
 #define SD_CRIT    "<2>"  /* critical conditions */
@@ -67,116 +92,144 @@ extern "C" {
 /* The first passed file descriptor is fd 3 */
 #define SD_LISTEN_FDS_START 3
 
-/* Returns how many file descriptors have been passed, or a negative
- * errno code on failure. Optionally, removes the $LISTEN_FDS and
- * $LISTEN_PID file descriptors from the environment (recommended, but
- * problematic in threaded environments). If r is the return value of
- * this function you'll find the file descriptors passed as fds
- * SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
- * errno style error code on failure. This function call ensures that
- * the FD_CLOEXEC flag is set for the passed file descriptors, to make
- * sure they are not passed on to child processes. If FD_CLOEXEC shall
- * not be set, the caller needs to unset it after this call for all file
- * descriptors that are used.*/
-int sd_listen_fds(int unset_environment);
-
-/* Helper call for identifying a passed file descriptor. Returns 1 if
- * the file descriptor is a FIFO in the file system stored under the
- * specified path, 0 otherwise. If path is NULL a path name check will
- * not be done and the call only verifies if the file descriptor
- * refers to a FIFO. Returns a negative errno style error code on
- * failure. */
-int sd_is_fifo(int fd, const char *path);
-
-/* Helper call for identifying a passed file descriptor. Returns 1 if
- * the file descriptor is a socket of the specified family (AF_INET,
- * ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
- * family is 0 a socket family check will not be done. If type is 0 a
- * socket type check will not be done and the call only verifies if
- * the file descriptor refers to a socket. If listening is > 0 it is
- * verified that the socket is in listening mode. (i.e. listen() has
- * been called) If listening is == 0 it is verified that the socket is
- * not in listening mode. If listening is < 0 no listening mode check
- * is done. Returns a negative errno style error code on failure. */
-int sd_is_socket(int fd, int family, int type, int listening);
-
-/* Helper call for identifying a passed file descriptor. Returns 1 if
- * the file descriptor is an Internet socket, of the specified family
- * (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
- * SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
- * check is not done. If type is 0 a socket type check will not be
- * done. If port is 0 a socket port check will not be done. The
- * listening flag is used the same way as in sd_is_socket(). Returns a
- * negative errno style error code on failure. */
-int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port);
-
-/* Helper call for identifying a passed file descriptor. Returns 1 if
- * the file descriptor is an AF_UNIX socket of the specified type
- * (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
- * a socket type check will not be done. If path is NULL a socket path
- * check will not be done. For normal AF_UNIX sockets set length to
- * 0. For abstract namespace sockets set length to the length of the
- * socket name (including the initial 0 byte), and pass the full
- * socket path in path (including the initial 0 byte). The listening
- * flag is used the same way as in sd_is_socket(). Returns a negative
- * errno style error code on failure. */
-int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
-
-/* Informs systemd about changed daemon state. This takes a numeber of
- * newline seperated environment-style variable assignments in a
- * string. The following strings are known:
- *
- *    READY=1      Tells systemd that daemon startup is finished (only
- *                 relevant for services of Type=notify). The passed
- *                 argument is a boolean "1" or "0". Since there is
- *                 little value in signalling non-readiness the only
- *                 value daemons should send is "READY=1".
- *
- *    STATUS=...   Passes a single-line status string back to systemd
- *                 that describes the daemon state. This is free-from
- *                 and can be used for various purposes: general state
- *                 feedback, fsck-like programs could pass completion
- *                 percentages and failing programs could pass a human
- *                 readable error message. Example: "STATUS=Completed
- *                 66% of file system check..."
- *
- *    ERRNO=...    If a daemon fails, the errno-style error code,
- *                 formatted as string. Example: "ERRNO=2" for ENOENT.
- *
- *    BUSERROR=... If a daemon fails, the D-Bus error-style error
- *                 code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
- *
- *    MAINPID=...  The main pid of a daemon, in case systemd did not
- *                 fork off the process itself. Example: "MAINPID=4711"
- *
- * Daemons can choose to send additional variables.
- *
- * Returns a negative errno-style error code on failure. Returns > 0
- * if systemd could be notified, 0 if it couldn't possibly because
- * systemd is not running.
- *
- * See sd_notifyf() for more complete examples.
- */
-int sd_notify(int unset_environment, const char *state);
-
-/* Similar to sd_send_state() but takes a format string.
- *
- * Example 1: A daemon could send the following after initialization:
- *
- * sd_notifyf(0, "READY=1\n"
- *               "STATUS=Processing requests...\n"
- *               "MAINPID=%lu",
- *               (unsigned long) getpid());
- *
- * Example 2: A daemon could send the following shortly before
- * exiting, on failure:
- *
- * sd_notifyf(0, "STATUS=Failed to start up: %s\n"
- *               "ERRNO=%i",
- *               strerror(errno),
- *               errno);
- */
-int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3);
+/*
+  Returns how many file descriptors have been passed, or a negative
+  errno code on failure. Optionally, removes the $LISTEN_FDS and
+  $LISTEN_PID file descriptors from the environment (recommended, but
+  problematic in threaded environments). If r is the return value of
+  this function you'll find the file descriptors passed as fds
+  SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+  errno style error code on failure. This function call ensures that
+  the FD_CLOEXEC flag is set for the passed file descriptors, to make
+  sure they are not passed on to child processes. If FD_CLOEXEC shall
+  not be set, the caller needs to unset it after this call for all file
+  descriptors that are used.
+*/
+int sd_listen_fds(int unset_environment) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a FIFO in the file system stored under the
+  specified path, 0 otherwise. If path is NULL a path name check will
+  not be done and the call only verifies if the file descriptor
+  refers to a FIFO. Returns a negative errno style error code on
+  failure.
+*/
+int sd_is_fifo(int fd, const char *path) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a socket of the specified family (AF_INET,
+  ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+  family is 0 a socket family check will not be done. If type is 0 a
+  socket type check will not be done and the call only verifies if
+  the file descriptor refers to a socket. If listening is > 0 it is
+  verified that the socket is in listening mode. (i.e. listen() has
+  been called) If listening is == 0 it is verified that the socket is
+  not in listening mode. If listening is < 0 no listening mode check
+  is done. Returns a negative errno style error code on failure.
+*/
+int sd_is_socket(int fd, int family, int type, int listening) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an Internet socket, of the specified family
+  (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+  SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+  check is not done. If type is 0 a socket type check will not be
+  done. If port is 0 a socket port check will not be done. The
+  listening flag is used the same way as in sd_is_socket(). Returns a
+  negative errno style error code on failure.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) _sd_hidden_;
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an AF_UNIX socket of the specified type
+  (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+  a socket type check will not be done. If path is NULL a socket path
+  check will not be done. For normal AF_UNIX sockets set length to
+  0. For abstract namespace sockets set length to the length of the
+  socket name (including the initial 0 byte), and pass the full
+  socket path in path (including the initial 0 byte). The listening
+  flag is used the same way as in sd_is_socket(). Returns a negative
+  errno style error code on failure.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) _sd_hidden_;
+
+/*
+  Informs systemd about changed daemon state. This takes a numeber of
+  newline seperated environment-style variable assignments in a
+  string. The following strings are known:
+
+     READY=1      Tells systemd that daemon startup is finished (only
+                  relevant for services of Type=notify). The passed
+                  argument is a boolean "1" or "0". Since there is
+                  little value in signalling non-readiness the only
+                  value daemons should send is "READY=1".
+
+     STATUS=...   Passes a single-line status string back to systemd
+                  that describes the daemon state. This is free-from
+                  and can be used for various purposes: general state
+                  feedback, fsck-like programs could pass completion
+                  percentages and failing programs could pass a human
+                  readable error message. Example: "STATUS=Completed
+                  66% of file system check..."
+
+     ERRNO=...    If a daemon fails, the errno-style error code,
+                  formatted as string. Example: "ERRNO=2" for ENOENT.
+
+     BUSERROR=... If a daemon fails, the D-Bus error-style error
+                  code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+     MAINPID=...  The main pid of a daemon, in case systemd did not
+                  fork off the process itself. Example: "MAINPID=4711"
+
+  Daemons can choose to send additional variables.
+
+  Returns a negative errno-style error code on failure. Returns > 0
+  if systemd could be notified, 0 if it couldn't possibly because
+  systemd is not running.
+
+  Example: When a daemon finished starting up, it could issue this
+  call to notify systemd about it:
+
+     sd_notify(0, "READY=1");
+
+  See sd_notifyf() for more complete examples.
+*/
+int sd_notify(int unset_environment, const char *state) _sd_hidden_;
+
+/*
+  Similar to sd_notify() but takes a format string.
+
+  Example 1: A daemon could send the following after initialization:
+
+     sd_notifyf(0, "READY=1\n"
+                   "STATUS=Processing requests...\n"
+                   "MAINPID=%lu",
+                   (unsigned long) getpid());
+
+  Example 2: A daemon could send the following shortly before
+  exiting, on failure:
+
+     sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+                   "ERRNO=%i",
+                   strerror(errno),
+                   errno);
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_;
+
+/*
+  Returns > 0 if the system was booted with systemd. Returns < 0 on
+  error. Returns 0 if the system was not booted with systemd. Note
+  that all of the functions above handle non-systemd boots just
+  fine. You should NOT protect them with a call to this function. Also
+  note that this function checks whether the system, not the user
+  session is controlled by systemd. However the functions above work
+  for both session and system services.
+*/
+int sd_booted(void) _sd_hidden_;
 
 #ifdef __cplusplus
 }


More information about the systemd-commits mailing list