Copyright 2011 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
+ 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
- General Public License for more details.
+ Lesser General Public License for more details.
- You should have received a copy of the GNU General Public License
+ 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 "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;
return NULL;
u->name = strdup(name);
- if (!u->name) {
- free(u);
- return NULL;
- }
+ if (!u->name)
+ goto fail;
- if (asprintf(&u->state_file, "/run/systemd/users/%lu", (unsigned long) uid) < 0) {
- free(u->name);
- free(u);
- return NULL;
- }
+ 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) {
- free(u->state_file);
- free(u->name);
- free(u);
- return NULL;
- }
+ 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) {
while (u->sessions)
session_free(u->sessions);
- free(u->cgroup_path);
+ if (u->cgroup_path) {
+ hashmap_remove(u->manager->user_cgroups, u->cgroup_path);
+ free(u->cgroup_path);
+ }
free(u->service);
free(u->runtime_path);
free(u->name);
free(u->state_file);
+ free(u->slice);
free(u);
}
int user_save(User *u) {
- FILE *f;
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
int r;
- char *temp_path;
assert(u);
assert(u->state_file);
if (!u->started)
return 0;
- r = safe_mkdir("/run/systemd/users", 0755, 0, 0);
+ r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0);
if (r < 0)
goto finish;
user_state_to_string(user_get_state(u)));
if (u->cgroup_path)
- fprintf(f,
- "CGROUP=%s\n",
- u->cgroup_path);
+ fprintf(f, "CGROUP=%s\n", u->cgroup_path);
if (u->runtime_path)
- fprintf(f,
- "RUNTIME=%s\n",
- u->runtime_path);
+ fprintf(f, "RUNTIME=%s\n", u->runtime_path);
if (u->service)
- fprintf(f,
- "SERVICE=%s\n",
- 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,
- "DISPLAY=%s\n",
- u->display->id);
+ "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) {
- fprintf(f,
- "%s%c",
- i->id,
- i->sessions_by_user_next ? ' ' : '\n');
+ if (!session_is_active(i))
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->id, f);
}
- fputs("SEATS=", f);
+ fputs("\nONLINE_SESSIONS=", f);
+ first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
- if (i->seat)
- fprintf(f,
- "%s%c",
- i->seat->id,
- i->sessions_by_user_next ? ' ' : '\n');
+ if (session_get_state(i) == SESSION_CLOSING)
+ continue;
+
+ if (first)
+ first = false;
+ else
+ fputc(' ', f);
+
+ fputs(i->id, f);
}
- fputs("ACTIVE_SESSIONS=", f);
- LIST_FOREACH(sessions_by_user, i, u->sessions)
- if (session_is_active(i))
- fprintf(f,
- "%lu%c",
- (unsigned long) i->user->uid,
- i->sessions_by_user_next ? ' ' : '\n');
+ 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("ACTIVE_SEATS=", f);
+ fputs("\nONLINE_SEATS=", f);
+ first = true;
LIST_FOREACH(sessions_by_user, i, u->sessions) {
- if (session_is_active(i) && i->seat)
- fprintf(f,
- "%s%c",
- i->seat->id,
- i->sessions_by_user_next ? ' ' : '\n');
+ 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);
unlink(temp_path);
}
- fclose(f);
- free(temp_path);
-
finish:
if (r < 0)
log_error("Failed to save user data for %s: %s", u->name, strerror(-r));
}
int user_load(User *u) {
- int r;
- char *display = NULL;
+ _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,
+ "CGROUP", &u->cgroup_path,
+ "RUNTIME", &u->runtime_path,
+ "SERVICE", &u->service,
+ "DISPLAY", &display,
+ "SLICE", &u->slice,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
NULL);
if (r < 0) {
- free(display);
-
if (r == -ENOENT)
return 0;
return r;
}
- if (display) {
+ if (display)
s = hashmap_get(u->manager->sessions, display);
- free(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;
}
assert(u);
- r = safe_mkdir("/run/user", 0755, 0, 0);
+ 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) {
- p = strappend("/run/user/", u->name);
-
- if (!p) {
- log_error("Out of memory");
- return -ENOMEM;
- }
+ if (asprintf(&p, "/run/user/%lu", (unsigned long) u->uid) < 0)
+ return log_oom();
} else
p = u->runtime_path;
- r = safe_mkdir(p, 0700, u->uid, u->gid);
+ 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);
static int user_create_cgroup(User *u) {
char **k;
- char *p;
int r;
assert(u);
+ if (!u->slice) {
+ u->slice = strdup(SPECIAL_USER_SLICE);
+ if (!u->slice)
+ return log_oom();
+ }
+
if (!u->cgroup_path) {
- if (asprintf(&p, "%s/%s", u->manager->cgroup_path, u->name) < 0) {
- log_error("Out of memory");
- return -ENOMEM;
- }
- } else
- p = 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, p);
+ r = cg_create(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL);
if (r < 0) {
- log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
- free(p);
- u->cgroup_path = NULL;
+ log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", u->cgroup_path, strerror(-r));
return r;
}
- u->cgroup_path = p;
-
STRV_FOREACH(k, u->manager->controllers) {
if (strv_contains(u->manager->reset_controllers, *k))
continue;
- r = cg_create(*k, p);
+ r = cg_create(*k, u->cgroup_path, NULL);
if (r < 0)
- log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r));
+ 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;
}
if (r < 0)
return r;
- dual_timestamp_get(&u->timestamp);
+ if (!dual_timestamp_is_set(&u->timestamp))
+ dual_timestamp_get(&u->timestamp);
u->started = true;
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 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;
- char *p;
assert(u);
if (u->sessions)
return 1;
- if (asprintf(&p, "/var/lib/systemd/linger/%s", u->name) < 0)
- return -ENOMEM;
-
- r = access(p, F_OK) >= 0;
- free(p);
-
- if (r > 0)
+ if (user_check_linger_file(u) > 0)
return 1;
if (u->cgroup_path) {
UserState user_get_state(User *u) {
Session *i;
+ bool all_closing = true;
assert(u);
- if (!u->sessions)
- return USER_LINGERING;
- LIST_FOREACH(sessions_by_user, i, u->sessions)
+ 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_ONLINE;
+ return USER_CLOSING;
}
int user_kill(User *u, int signo) {
- int r = 0, q;
- Set *pid_set = NULL;
+ _cleanup_set_free_ Set *pid_set = NULL;
+ int r;
assert(u);
if (!pid_set)
return -ENOMEM;
- q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set);
- if (q < 0)
- if (q != -EAGAIN && q != -ESRCH && q != -ENOENT)
- r = q;
-
- if (pid_set)
- set_free(pid_set);
+ 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 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_ACTIVE] = "active",
+ [USER_CLOSING] = "closing"
};
DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);