#include <errno.h>
#include <string.h>
#include <unistd.h>
+#include <sys/epoll.h>
-#include "logind.h"
+#include "logind-session.h"
#include "strv.h"
#include "util.h"
#include "cgroup-util.h"
+#define IDLE_THRESHOLD_USEC (5*USEC_PER_MINUTE)
+
Session* session_new(Manager *m, User *u, const char *id) {
Session *s;
assert(m);
assert(id);
- s = new(Session, 1);
+ s = new0(Session, 1);
if (!s)
return NULL;
- s->state_file = strappend("/run/systemd/session/", id);
+ s->state_file = strappend("/run/systemd/sessions/", id);
if (!s->state_file) {
free(s);
return NULL;
s->pipe_fd = -1;
s->user = u;
- dual_timestamp_get(&s->timestamp);
+ LIST_PREPEND(Session, sessions_by_user, u->sessions, s);
return s;
}
void session_free(Session *s) {
assert(s);
+ if (s->in_gc_queue)
+ LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s);
+
if (s->user) {
LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s);
s->user->display = NULL;
}
- if (s->seat)
+ if (s->seat) {
+ if (s->seat->active == s)
+ s->seat->active = NULL;
+
LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s);
+ }
+
+ if (s->cgroup_path)
+ hashmap_remove(s->manager->cgroups, s->cgroup_path);
free(s->cgroup_path);
strv_free(s->controllers);
free(s->tty);
free(s->display);
free(s->remote_host);
+ free(s->remote_user);
+ free(s->service);
hashmap_remove(s->manager->sessions, s->id);
+ session_unset_pipe_fd(s);
+
free(s->state_file);
free(s);
}
int session_save(Session *s) {
FILE *f;
int r = 0;
+ char *temp_path;
assert(s);
- r = safe_mkdir("/run/systemd/session", 0755, 0, 0);
+ if (!s->started)
+ return 0;
+
+ r = safe_mkdir("/run/systemd/sessions", 0755, 0, 0);
if (r < 0)
- return r;
+ goto finish;
- f = fopen(s->state_file, "we");
- if (!f)
- return -errno;
+ r = fopen_temporary(s->state_file, &f, &temp_path);
+ if (r < 0)
+ goto finish;
assert(s->user);
+ fchmod(fileno(f), 0644);
+
fprintf(f,
"# This is private data. Do not parse.\n"
"UID=%lu\n"
s->remote,
s->kill_processes);
+ if (s->type >= 0)
+ fprintf(f,
+ "TYPE=%s\n",
+ session_type_to_string(s->type));
+
if (s->cgroup_path)
fprintf(f,
"CGROUP=%s\n",
"REMOTE_HOST=%s\n",
s->remote_host);
- if (s->seat && s->seat->manager->vtconsole == s->seat)
+ if (s->remote_user)
+ fprintf(f,
+ "REMOTE_USER=%s\n",
+ s->remote_user);
+
+ if (s->service)
+ fprintf(f,
+ "SERVICE=%s\n",
+ s->service);
+
+ if (s->seat && seat_is_vtconsole(s->seat))
fprintf(f,
"VTNR=%i\n",
s->vtnr);
(unsigned long long) s->audit_id);
fflush(f);
- if (ferror(f)) {
+
+ if (ferror(f) || rename(temp_path, s->state_file) < 0) {
r = -errno;
unlink(s->state_file);
+ unlink(temp_path);
}
fclose(f);
+ free(temp_path);
+
+finish:
+ if (r < 0)
+ log_error("Failed to save session data for %s: %s", s->id, strerror(-r));
+
return r;
}
int session_load(Session *s) {
+ char *remote = NULL,
+ *kill_processes = NULL,
+ *seat = NULL,
+ *vtnr = NULL,
+ *leader = NULL,
+ *audit_id = NULL,
+ *type = NULL;
+
+ int k, r;
+
assert(s);
- return 0;
+ r = parse_env_file(s->state_file, NEWLINE,
+ "REMOTE", &remote,
+ "KILL_PROCESSES", &kill_processes,
+ "CGROUP", &s->cgroup_path,
+ "SEAT", &seat,
+ "TTY", &s->tty,
+ "DISPLAY", &s->display,
+ "REMOTE_HOST", &s->remote_host,
+ "REMOTE_USER", &s->remote_user,
+ "SERVICE", &s->service,
+ "VTNR", &vtnr,
+ "LEADER", &leader,
+ "TYPE", &type,
+ NULL);
+
+ if (r < 0)
+ goto finish;
+
+ if (remote) {
+ k = parse_boolean(remote);
+ if (k >= 0)
+ s->remote = k;
+ }
+
+ if (kill_processes) {
+ k = parse_boolean(kill_processes);
+ if (k >= 0)
+ s->kill_processes = k;
+ }
+
+ if (seat && !s->seat) {
+ Seat *o;
+
+ o = hashmap_get(s->manager->seats, seat);
+ if (o)
+ seat_attach_session(o, s);
+ }
+
+ if (vtnr && s->seat && seat_is_vtconsole(s->seat)) {
+ int v;
+
+ k = safe_atoi(vtnr, &v);
+ if (k >= 0 && v >= 1)
+ s->vtnr = v;
+ }
+
+ if (leader) {
+ pid_t pid;
+
+ k = parse_pid(leader, &pid);
+ if (k >= 0 && pid >= 1) {
+ s->leader = pid;
+
+ audit_session_from_pid(pid, &s->audit_id);
+ }
+ }
+
+ if (type) {
+ SessionType t;
+
+ t = session_type_from_string(type);
+ if (t >= 0)
+ s->type = t;
+ }
+
+finish:
+ free(remote);
+ free(kill_processes);
+ free(seat);
+ free(vtnr);
+ free(leader);
+ free(audit_id);
+
+ return r;
}
int session_activate(Session *s) {
int r;
+ Session *old_active;
assert(s);
if (s->seat->active == s)
return 0;
- assert(s->manager->vtconsole == s->seat);
+ assert(seat_is_vtconsole(s->seat));
r = chvt(s->vtnr);
if (r < 0)
return r;
+ old_active = s->seat->active;
s->seat->active = s;
- return seat_apply_acls(s->seat);
+ return seat_apply_acls(s->seat, old_active);
}
-bool x11_display_is_local(const char *display) {
- assert(display);
-
- return
- display[0] == ':' &&
- display[1] >= '0' &&
- display[1] <= '9';
-}
static int session_link_x11_socket(Session *s) {
char *t, *f, *c;
if (s->user->display)
return 0;
- if (!s->display || !x11_display_is_local(s->display))
+ if (!s->display || !display_is_local(s->display))
return 0;
k = strspn(s->display+1, "0123456789");
return 0;
}
+static int session_create_one_group(Session *s, const char *controller, const char *path) {
+ int r;
+
+ assert(s);
+ assert(controller);
+ assert(path);
+
+ if (s->leader > 0) {
+ r = cg_create_and_attach(controller, path, s->leader);
+ if (r < 0)
+ r = cg_create(controller, path);
+ } else
+ r = cg_create(controller, path);
+
+ if (r < 0)
+ return r;
+
+ r = cg_set_task_access(controller, path, 0644, s->user->uid, s->user->gid);
+ if (r >= 0)
+ r = cg_set_group_access(controller, path, 0755, s->user->uid, s->user->gid);
+
+ return r;
+}
+
static int session_create_cgroup(Session *s) {
char **k;
char *p;
} else
p = s->cgroup_path;
- if (s->leader > 0)
- r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, p, s->leader);
- else
- r = cg_create(SYSTEMD_CGROUP_CONTROLLER, p);
-
+ r = session_create_one_group(s, SYSTEMD_CGROUP_CONTROLLER, p);
if (r < 0) {
+ log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
free(p);
s->cgroup_path = NULL;
- log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
return r;
}
s->cgroup_path = p;
+ STRV_FOREACH(k, s->controllers) {
+
+ if (strv_contains(s->reset_controllers, *k))
+ continue;
+
+ r = session_create_one_group(s, *k, p);
+ if (r < 0)
+ log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r));
+ }
+
STRV_FOREACH(k, s->manager->controllers) {
- if (s->leader > 0)
- r = cg_create_and_attach(*k, p, s->leader);
- else
- r = cg_create(*k, p);
+ if (strv_contains(s->reset_controllers, *k) ||
+ strv_contains(s->controllers, *k))
+ continue;
+
+ r = session_create_one_group(s, *k, p);
if (r < 0)
- log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r));
+ log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r));
+ }
+
+ if (s->leader > 0) {
+
+ STRV_FOREACH(k, s->reset_controllers) {
+ r = cg_attach(*k, "/", s->leader);
+ if (r < 0)
+ log_warning("Failed to reset controller %s: %s", *k, strerror(-r));
+
+ }
}
+ hashmap_put(s->manager->cgroups, s->cgroup_path, s);
+
return 0;
}
assert(s);
assert(s->user);
- /* Create user first */
+ if (s->started)
+ return 0;
+
r = user_start(s->user);
if (r < 0)
return r;
+ log_info("New session %s of user %s.", s->id, s->user->name);
+
/* Create cgroup */
r = session_create_cgroup(s);
if (r < 0)
/* Create X11 symlink */
session_link_x11_socket(s);
+
+ dual_timestamp_get(&s->timestamp);
+
+ if (s->seat)
+ seat_read_active_vt(s->seat);
+
+ s->started = true;
+
+ /* Save session data */
+ session_save(s);
+
+ session_send_signal(s, true);
+
+ if (s->seat) {
+ if (s->seat->active == s)
+ seat_send_changed(s->seat, "Sessions\0ActiveSession\0");
+ else
+ seat_send_changed(s->seat, "Sessions\0");
+ }
+
+ user_send_changed(s->user, "Sessions\0");
+
return 0;
}
static bool session_shall_kill(Session *s) {
assert(s);
- return s->kill_processes;
+ if (!s->kill_processes)
+ return false;
+
+ if (strv_contains(s->manager->kill_exclude_users, s->user->name))
+ return false;
+
+ if (strv_isempty(s->manager->kill_only_users))
+ return true;
+
+ return strv_contains(s->manager->kill_only_users, s->user->name);
}
static int session_kill_cgroup(Session *s) {
STRV_FOREACH(k, s->user->manager->controllers)
cg_trim(*k, s->cgroup_path, true);
+ hashmap_remove(s->manager->cgroups, s->cgroup_path);
+
free(s->cgroup_path);
s->cgroup_path = NULL;
assert(s);
+ if (s->started)
+ log_info("Removed session %s.", s->id);
+
/* Kill cgroup */
k = session_kill_cgroup(s);
if (k < 0)
/* Remove X11 symlink */
session_unlink_x11_socket(s);
+ unlink(s->state_file);
+ session_add_to_gc_queue(s);
+ user_add_to_gc_queue(s->user);
+
+ if (s->started)
+ session_send_signal(s, false);
+
+ if (s->seat) {
+ if (s->seat->active == s)
+ seat_set_active(s->seat, NULL);
+
+ seat_send_changed(s->seat, "Sessions\0");
+ }
+
+ user_send_changed(s->user, "Sessions\0");
+
+ s->started = false;
+
return r;
}
return s->seat->active == s;
}
+int session_get_idle_hint(Session *s, dual_timestamp *t) {
+ char *p;
+ struct stat st;
+ usec_t u, n;
+ bool b;
+ int k;
+
+ assert(s);
+
+ if (s->idle_hint) {
+ if (t)
+ *t = s->idle_hint_timestamp;
+
+ return s->idle_hint;
+ }
+
+ if (isempty(s->tty))
+ goto dont_know;
+
+ if (s->tty[0] != '/') {
+ p = strappend("/dev/", s->tty);
+ if (!p)
+ return -ENOMEM;
+ } else
+ p = NULL;
+
+ if (!startswith(p ? p : s->tty, "/dev/")) {
+ free(p);
+ goto dont_know;
+ }
+
+ k = lstat(p ? p : s->tty, &st);
+ free(p);
+
+ if (k < 0)
+ goto dont_know;
+
+ u = timespec_load(&st.st_atim);
+ n = now(CLOCK_REALTIME);
+ b = u + IDLE_THRESHOLD_USEC < n;
+
+ if (t)
+ dual_timestamp_from_realtime(t, u + b ? IDLE_THRESHOLD_USEC : 0);
+
+ return b;
+
+dont_know:
+ if (t)
+ *t = s->idle_hint_timestamp;
+
+ return 0;
+}
+
+void session_set_idle_hint(Session *s, bool b) {
+ assert(s);
+
+ if (s->idle_hint == b)
+ return;
+
+ s->idle_hint = b;
+ dual_timestamp_get(&s->idle_hint_timestamp);
+
+ session_send_changed(s,
+ "IdleHint\0"
+ "IdleSinceHint\0"
+ "IdleSinceHintMonotonic\0");
+
+ if (s->seat)
+ seat_send_changed(s->seat,
+ "IdleHint\0"
+ "IdleSinceHint\0"
+ "IdleSinceHintMonotonic\0");
+
+ user_send_changed(s->user,
+ "IdleHint\0"
+ "IdleSinceHint\0"
+ "IdleSinceHintMonotonic\0");
+
+ manager_send_changed(s->manager,
+ "IdleHint\0"
+ "IdleSinceHint\0"
+ "IdleSinceHintMonotonic\0");
+}
+
+int session_set_pipe_fd(Session *s, int fd) {
+ struct epoll_event ev;
+ int r;
+
+ assert(s);
+ assert(fd >= 0);
+ assert(s->pipe_fd < 0);
+
+ r = hashmap_put(s->manager->pipe_fds, INT_TO_PTR(fd + 1), s);
+ if (r < 0)
+ return r;
+
+ zero(ev);
+ ev.events = 0;
+ ev.data.u32 = FD_PIPE_BASE + fd;
+
+ if (epoll_ctl(s->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ assert_se(hashmap_remove(s->manager->pipe_fds, INT_TO_PTR(fd + 1)) == s);
+ return -errno;
+ }
+
+ s->pipe_fd = fd;
+ return 0;
+}
+
+void session_unset_pipe_fd(Session *s) {
+ assert(s);
+
+ if (s->pipe_fd < 0)
+ return;
+
+ assert_se(hashmap_remove(s->manager->pipe_fds, INT_TO_PTR(s->pipe_fd + 1)) == s);
+
+ assert_se(epoll_ctl(s->manager->epoll_fd, EPOLL_CTL_DEL, s->pipe_fd, NULL) == 0);
+
+ close_nointr_nofail(s->pipe_fd);
+ s->pipe_fd = -1;
+}
+
int session_check_gc(Session *s) {
int r;
if (r < 0)
return r;
- if (r <= 0)
+ if (r == 0)
return 1;
}
return 0;
}
+void session_add_to_gc_queue(Session *s) {
+ assert(s);
+
+ if (s->in_gc_queue)
+ return;
+
+ LIST_PREPEND(Session, gc_queue, s->manager->session_gc_queue, s);
+ s->in_gc_queue = true;
+}
+
static const char* const session_type_table[_SESSION_TYPE_MAX] = {
- [SESSION_TERMINAL] = "terminal",
- [SESSION_X11] = "x11"
+ [SESSION_TTY] = "tty",
+ [SESSION_X11] = "x11",
+ [SESSION_UNSPECIFIED] = "unspecified"
};
DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);