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

This commit is contained in:
Lennart Poettering 2010-06-21 23:27:18 +02:00
parent 96551bae61
commit 8c6db83365
15 changed files with 1609 additions and 311 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
systemd.pc
test-cgroup
.libs/
systemd-notify
test-daemon
systemd-install

View File

@ -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)

View File

@ -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}

20
fixme
View File

@ -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();

694
src/cgroup-util.c Normal file
View File

@ -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;
}

54
src/cgroup-util.h Normal file
View File

@ -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

View File

@ -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);
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) {
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 = cg_is_empty_recursive(b->controller, b->path, true)) < 0)
return r;
if (r == 0 || r == ECGEOF)
cgroup_get_task_end(&iterator);
/* 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)

View File

@ -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);

View File

@ -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);

466
src/pam-module.c Normal file
View File

@ -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;
}

84
src/test-cgroup.c Normal file
View File

@ -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;
}

View File

@ -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[] = {

View File

@ -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);

View File

@ -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");

11
systemd.pc.in Normal file
View File

@ -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@