Systemd/src/login/logind-user.c
Lennart Poettering 4ad490007b core: general cgroup rework
Replace the very generic cgroup hookup with a much simpler one. With
this change only the high-level cgroup settings remain, the ability to
set arbitrary cgroup attributes is removed, so is support for adding
units to arbitrary cgroup controllers or setting arbitrary paths for
them (especially paths that are different for the various controllers).

This also introduces a new -.slice root slice, that is the parent of
system.slice and friends. This enables easy admin configuration of
root-level cgrouo properties.

This replaces DeviceDeny= by DevicePolicy=, and implicitly adds in
/dev/null, /dev/zero and friends if DeviceAllow= is used (unless this is
turned off by DevicePolicy=).
2013-06-27 04:17:34 +02:00

680 lines
18 KiB
C

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2011 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "logind-user.h"
#include "util.h"
#include "mkdir.h"
#include "cgroup-util.h"
#include "hashmap.h"
#include "strv.h"
#include "fileio.h"
#include "special.h"
User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) {
User *u;
assert(m);
assert(name);
u = new0(User, 1);
if (!u)
return NULL;
u->name = strdup(name);
if (!u->name)
goto fail;
if (asprintf(&u->state_file, "/run/systemd/users/%lu", (unsigned long) uid) < 0)
goto fail;
if (hashmap_put(m->users, ULONG_TO_PTR((unsigned long) uid), u) < 0)
goto fail;
u->manager = m;
u->uid = uid;
u->gid = gid;
return u;
fail:
free(u->state_file);
free(u->name);
free(u);
return NULL;
}
void user_free(User *u) {
assert(u);
if (u->in_gc_queue)
LIST_REMOVE(User, gc_queue, u->manager->user_gc_queue, u);
while (u->sessions)
session_free(u->sessions);
if (u->cgroup_path) {
hashmap_remove(u->manager->user_cgroups, u->cgroup_path);
free(u->cgroup_path);
}
free(u->service);
free(u->runtime_path);
hashmap_remove(u->manager->users, ULONG_TO_PTR((unsigned long) u->uid));
free(u->name);
free(u->state_file);
free(u->slice);
free(u);
}
int user_save(User *u) {
_cleanup_free_ char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(u);
assert(u->state_file);
if (!u->started)
return 0;
r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0);
if (r < 0)
goto finish;
r = fopen_temporary(u->state_file, &f, &temp_path);
if (r < 0)
goto finish;
fchmod(fileno(f), 0644);
fprintf(f,
"# This is private data. Do not parse.\n"
"NAME=%s\n"
"STATE=%s\n",
u->name,
user_state_to_string(user_get_state(u)));
if (u->cgroup_path)
fprintf(f, "CGROUP=%s\n", u->cgroup_path);
if (u->runtime_path)
fprintf(f, "RUNTIME=%s\n", u->runtime_path);
if (u->service)
fprintf(f, "SERVICE=%s\n", u->service);
if (u->slice)
fprintf(f, "SLICE=%s\n", u->slice);
if (u->display)
fprintf(f, "DISPLAY=%s\n", u->display->id);
if (dual_timestamp_is_set(&u->timestamp))
fprintf(f,
"REALTIME=%llu\n"
"MONOTONIC=%llu\n",
(unsigned long long) u->timestamp.realtime,
(unsigned long long) u->timestamp.monotonic);
if (u->sessions) {
Session *i;
bool first;
fputs("SESSIONS=", f);
first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (first)
first = false;
else
fputc(' ', f);
fputs(i->id, f);
}
fputs("\nSEATS=", f);
first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (!i->seat)
continue;
if (first)
first = false;
else
fputc(' ', f);
fputs(i->seat->id, f);
}
fputs("\nACTIVE_SESSIONS=", f);
first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (!session_is_active(i))
continue;
if (first)
first = false;
else
fputc(' ', f);
fputs(i->id, f);
}
fputs("\nONLINE_SESSIONS=", f);
first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (session_get_state(i) == SESSION_CLOSING)
continue;
if (first)
first = false;
else
fputc(' ', f);
fputs(i->id, f);
}
fputs("\nACTIVE_SEATS=", f);
first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (!session_is_active(i) || !i->seat)
continue;
if (first)
first = false;
else
fputc(' ', f);
fputs(i->seat->id, f);
}
fputs("\nONLINE_SEATS=", f);
first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (session_get_state(i) == SESSION_CLOSING || !i->seat)
continue;
if (first)
first = false;
else
fputc(' ', f);
fputs(i->seat->id, f);
}
fputc('\n', f);
}
fflush(f);
if (ferror(f) || rename(temp_path, u->state_file) < 0) {
r = -errno;
unlink(u->state_file);
unlink(temp_path);
}
finish:
if (r < 0)
log_error("Failed to save user data for %s: %s", u->name, strerror(-r));
return r;
}
int user_load(User *u) {
_cleanup_free_ char *display = NULL, *realtime = NULL, *monotonic = NULL;
Session *s = NULL;
int r;
assert(u);
r = parse_env_file(u->state_file, NEWLINE,
"CGROUP", &u->cgroup_path,
"RUNTIME", &u->runtime_path,
"SERVICE", &u->service,
"DISPLAY", &display,
"SLICE", &u->slice,
"REALTIME", &realtime,
"MONOTONIC", &monotonic,
NULL);
if (r < 0) {
if (r == -ENOENT)
return 0;
log_error("Failed to read %s: %s", u->state_file, strerror(-r));
return r;
}
if (display)
s = hashmap_get(u->manager->sessions, display);
if (s && s->display && display_is_local(s->display))
u->display = s;
if (realtime) {
unsigned long long l;
if (sscanf(realtime, "%llu", &l) > 0)
u->timestamp.realtime = l;
}
if (monotonic) {
unsigned long long l;
if (sscanf(monotonic, "%llu", &l) > 0)
u->timestamp.monotonic = l;
}
return r;
}
static int user_mkdir_runtime_path(User *u) {
char *p;
int r;
assert(u);
r = mkdir_safe_label("/run/user", 0755, 0, 0);
if (r < 0) {
log_error("Failed to create /run/user: %s", strerror(-r));
return r;
}
if (!u->runtime_path) {
if (asprintf(&p, "/run/user/%lu", (unsigned long) u->uid) < 0)
return log_oom();
} else
p = u->runtime_path;
r = mkdir_safe_label(p, 0700, u->uid, u->gid);
if (r < 0) {
log_error("Failed to create runtime directory %s: %s", p, strerror(-r));
free(p);
u->runtime_path = NULL;
return r;
}
u->runtime_path = p;
return 0;
}
static int user_create_cgroup(User *u) {
char **k;
int r;
assert(u);
if (!u->slice) {
u->slice = strdup(SPECIAL_USER_SLICE);
if (!u->slice)
return log_oom();
}
if (!u->cgroup_path) {
_cleanup_free_ char *name = NULL, *escaped = NULL, *slice = NULL;
if (asprintf(&name, "%lu.user", (unsigned long) u->uid) < 0)
return log_oom();
escaped = cg_escape(name);
if (!escaped)
return log_oom();
r = cg_slice_to_path(u->slice, &slice);
if (r < 0)
return r;
u->cgroup_path = strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL);
if (!u->cgroup_path)
return log_oom();
}
r = cg_create(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path);
if (r < 0) {
log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", u->cgroup_path, strerror(-r));
return r;
}
STRV_FOREACH(k, u->manager->controllers) {
if (strv_contains(u->manager->reset_controllers, *k))
continue;
r = cg_create(*k, u->cgroup_path);
if (r < 0)
log_warning("Failed to create cgroup %s:%s: %s", *k, u->cgroup_path, strerror(-r));
}
r = hashmap_put(u->manager->user_cgroups, u->cgroup_path, u);
if (r < 0)
log_warning("Failed to create mapping between cgroup and user");
return 0;
}
static int user_start_service(User *u) {
assert(u);
/* FIXME: Fill me in later ... */
return 0;
}
int user_start(User *u) {
int r;
assert(u);
if (u->started)
return 0;
log_debug("New user %s logged in.", u->name);
/* Make XDG_RUNTIME_DIR */
r = user_mkdir_runtime_path(u);
if (r < 0)
return r;
/* Create cgroup */
r = user_create_cgroup(u);
if (r < 0)
return r;
/* Spawn user systemd */
r = user_start_service(u);
if (r < 0)
return r;
if (!dual_timestamp_is_set(&u->timestamp))
dual_timestamp_get(&u->timestamp);
u->started = true;
/* Save new user data */
user_save(u);
user_send_signal(u, true);
return 0;
}
static int user_stop_service(User *u) {
assert(u);
if (!u->service)
return 0;
return 0;
}
static int user_shall_kill(User *u) {
assert(u);
if (!u->manager->kill_user_processes)
return false;
if (strv_contains(u->manager->kill_exclude_users, u->name))
return false;
if (strv_isempty(u->manager->kill_only_users))
return true;
return strv_contains(u->manager->kill_only_users, u->name);
}
static int user_terminate_cgroup(User *u) {
int r;
char **k;
assert(u);
if (!u->cgroup_path)
return 0;
cg_trim(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false);
if (user_shall_kill(u)) {
r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true);
if (r < 0)
log_error("Failed to kill user cgroup: %s", strerror(-r));
} else {
r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true);
if (r < 0)
log_error("Failed to check user cgroup: %s", strerror(-r));
else if (r > 0) {
r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path);
if (r < 0)
log_error("Failed to delete user cgroup: %s", strerror(-r));
} else
r = -EBUSY;
}
STRV_FOREACH(k, u->manager->controllers)
cg_trim(*k, u->cgroup_path, true);
hashmap_remove(u->manager->user_cgroups, u->cgroup_path);
free(u->cgroup_path);
u->cgroup_path = NULL;
return r;
}
static int user_remove_runtime_path(User *u) {
int r;
assert(u);
if (!u->runtime_path)
return 0;
r = rm_rf(u->runtime_path, false, true, false);
if (r < 0)
log_error("Failed to remove runtime directory %s: %s", u->runtime_path, strerror(-r));
free(u->runtime_path);
u->runtime_path = NULL;
return r;
}
int user_stop(User *u) {
Session *s;
int r = 0, k;
assert(u);
if (u->started)
log_debug("User %s logged out.", u->name);
LIST_FOREACH(sessions_by_user, s, u->sessions) {
k = session_stop(s);
if (k < 0)
r = k;
}
/* Kill systemd */
k = user_stop_service(u);
if (k < 0)
r = k;
/* Kill cgroup */
k = user_terminate_cgroup(u);
if (k < 0)
r = k;
/* Kill XDG_RUNTIME_DIR */
k = user_remove_runtime_path(u);
if (k < 0)
r = k;
unlink(u->state_file);
user_add_to_gc_queue(u);
if (u->started)
user_send_signal(u, false);
u->started = false;
return r;
}
int user_get_idle_hint(User *u, dual_timestamp *t) {
Session *s;
bool idle_hint = true;
dual_timestamp ts = { 0, 0 };
assert(u);
LIST_FOREACH(sessions_by_user, s, u->sessions) {
dual_timestamp k;
int ih;
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
if (!ih) {
if (!idle_hint) {
if (k.monotonic < ts.monotonic)
ts = k;
} else {
idle_hint = false;
ts = k;
}
} else if (idle_hint) {
if (k.monotonic > ts.monotonic)
ts = k;
}
}
if (t)
*t = ts;
return idle_hint;
}
static int user_check_linger_file(User *u) {
char *p;
int r;
if (asprintf(&p, "/var/lib/systemd/linger/%s", u->name) < 0)
return -ENOMEM;
r = access(p, F_OK) >= 0;
free(p);
return r;
}
int user_check_gc(User *u, bool drop_not_started) {
int r;
assert(u);
if (drop_not_started && !u->started)
return 0;
if (u->sessions)
return 1;
if (user_check_linger_file(u) > 0)
return 1;
if (u->cgroup_path) {
r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false);
if (r < 0)
return r;
if (r <= 0)
return 1;
}
return 0;
}
void user_add_to_gc_queue(User *u) {
assert(u);
if (u->in_gc_queue)
return;
LIST_PREPEND(User, gc_queue, u->manager->user_gc_queue, u);
u->in_gc_queue = true;
}
UserState user_get_state(User *u) {
Session *i;
bool all_closing = true;
assert(u);
LIST_FOREACH(sessions_by_user, i, u->sessions) {
if (session_is_active(i))
return USER_ACTIVE;
if (session_get_state(i) != SESSION_CLOSING)
all_closing = false;
}
if (u->sessions)
return all_closing ? USER_CLOSING : USER_ONLINE;
if (user_check_linger_file(u) > 0)
return USER_LINGERING;
return USER_CLOSING;
}
int user_kill(User *u, int signo) {
_cleanup_set_free_ Set *pid_set = NULL;
int r;
assert(u);
if (!u->cgroup_path)
return -ESRCH;
pid_set = set_new(trivial_hash_func, trivial_compare_func);
if (!pid_set)
return -ENOMEM;
r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set);
if (r < 0 && (r != -EAGAIN && r != -ESRCH && r != -ENOENT))
return r;
return 0;
}
static const char* const user_state_table[_USER_STATE_MAX] = {
[USER_OFFLINE] = "offline",
[USER_LINGERING] = "lingering",
[USER_ONLINE] = "online",
[USER_ACTIVE] = "active",
[USER_CLOSING] = "closing"
};
DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);