1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "sd-messages.h"
28 #include "logind-seat.h"
29 #include "logind-acl.h"
32 #include "formats-util.h"
33 #include "terminal-util.h"
35 Seat *seat_new(Manager *m, const char *id) {
45 s->state_file = strappend("/run/systemd/seats/", id);
51 s->id = basename(s->state_file);
54 if (hashmap_put(m->seats, s->id, s) < 0) {
63 void seat_free(Seat *s) {
67 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
70 session_free(s->sessions);
75 device_free(s->devices);
77 hashmap_remove(s->manager->seats, s->id);
84 int seat_save(Seat *s) {
85 _cleanup_free_ char *temp_path = NULL;
86 _cleanup_fclose_ FILE *f = NULL;
94 r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
98 r = fopen_temporary(s->state_file, &f, &temp_path);
102 fchmod(fileno(f), 0644);
105 "# This is private data. Do not parse.\n"
107 "CAN_MULTI_SESSION=%i\n"
109 "CAN_GRAPHICAL=%i\n",
111 seat_can_multi_session(s),
113 seat_can_graphical(s));
116 assert(s->active->user);
120 "ACTIVE_UID="UID_FMT"\n",
122 s->active->user->uid);
128 fputs("SESSIONS=", f);
129 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
133 i->sessions_by_seat_next ? ' ' : '\n');
137 LIST_FOREACH(sessions_by_seat, i, s->sessions)
141 i->sessions_by_seat_next ? ' ' : '\n');
146 if (ferror(f) || rename(temp_path, s->state_file) < 0) {
148 unlink(s->state_file);
154 log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
159 int seat_load(Seat *s) {
162 /* There isn't actually anything to read here ... */
167 static int vt_allocate(unsigned int vtnr) {
168 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
169 _cleanup_close_ int fd = -1;
173 snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
174 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
181 int seat_apply_acls(Seat *s, Session *old_active) {
186 r = devnode_acl_all(s->manager->udev,
189 !!old_active, old_active ? old_active->user->uid : 0,
190 !!s->active, s->active ? s->active->user->uid : 0);
193 log_error_errno(r, "Failed to apply ACLs: %m");
198 int seat_set_active(Seat *s, Session *session) {
202 assert(!session || session->seat == s);
204 if (session == s->active)
207 old_active = s->active;
211 session_device_pause_all(old_active);
212 session_send_changed(old_active, "Active", NULL);
215 seat_apply_acls(s, old_active);
217 if (session && session->started) {
218 session_send_changed(session, "Active", NULL);
219 session_device_resume_all(session);
222 if (!session || session->started)
223 seat_send_changed(s, "ActiveSession", NULL);
228 session_save(session);
229 user_save(session->user);
233 session_save(old_active);
234 if (!session || session->user != old_active->user)
235 user_save(old_active->user);
241 int seat_switch_to(Seat *s, unsigned int num) {
242 /* Public session positions skip 0 (there is only F1-F12). Maybe it
243 * will get reassigned in the future, so return error for now. */
247 if (num >= s->position_count || !s->positions[num]) {
248 /* allow switching to unused VTs to trigger auto-activate */
249 if (seat_has_vts(s) && num < 64)
255 return session_activate(s->positions[num]);
258 int seat_switch_to_next(Seat *s) {
259 unsigned int start, i;
261 if (s->position_count == 0)
265 if (s->active && s->active->pos > 0)
266 start = s->active->pos;
268 for (i = start + 1; i < s->position_count; ++i)
270 return session_activate(s->positions[i]);
272 for (i = 1; i < start; ++i)
274 return session_activate(s->positions[i]);
279 int seat_switch_to_previous(Seat *s) {
280 unsigned int start, i;
282 if (s->position_count == 0)
286 if (s->active && s->active->pos > 0)
287 start = s->active->pos;
289 for (i = start - 1; i > 0; --i)
291 return session_activate(s->positions[i]);
293 for (i = s->position_count - 1; i > start; --i)
295 return session_activate(s->positions[i]);
300 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
301 Session *i, *new_active = NULL;
307 if (!seat_has_vts(s))
310 log_debug("VT changed to %u", vtnr);
312 /* we might have earlier closing sessions on the same VT, so try to
313 * find a running one first */
314 LIST_FOREACH(sessions_by_seat, i, s->sessions)
315 if (i->vtnr == vtnr && !i->stopping) {
321 /* no running one? then we can't decide which one is the
322 * active one, let the first one win */
323 LIST_FOREACH(sessions_by_seat, i, s->sessions)
324 if (i->vtnr == vtnr) {
330 r = seat_set_active(s, new_active);
335 int seat_read_active_vt(Seat *s) {
343 if (!seat_has_vts(s))
346 lseek(s->manager->console_active_fd, SEEK_SET, 0);
348 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
350 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
351 return k < 0 ? -errno : -EIO;
357 if (!startswith(t, "tty")) {
358 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
362 r = safe_atou(t+3, &vtnr);
364 log_error("Failed to parse VT number %s", t+3);
369 log_error("VT number invalid: %s", t+3);
373 return seat_active_vt_changed(s, vtnr);
376 int seat_start(Seat *s) {
383 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
385 LOG_MESSAGE("New seat %s.", s->id),
388 /* Read current VT */
389 seat_read_active_vt(s);
396 seat_send_signal(s, true);
401 int seat_stop(Seat *s, bool force) {
408 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
410 LOG_MESSAGE("Removed seat %s.", s->id),
413 seat_stop_sessions(s, force);
415 unlink(s->state_file);
416 seat_add_to_gc_queue(s);
419 seat_send_signal(s, false);
426 int seat_stop_sessions(Seat *s, bool force) {
432 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
433 k = session_stop(session, force);
441 void seat_evict_position(Seat *s, Session *session) {
443 unsigned int pos = session->pos;
450 if (pos < s->position_count && s->positions[pos] == session) {
451 s->positions[pos] = NULL;
453 /* There might be another session claiming the same
454 * position (eg., during gdm->session transition), so let's look
455 * for it and set it on the free slot. */
456 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
457 if (iter->pos == pos) {
458 s->positions[pos] = iter;
465 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
466 /* with VTs, the position is always the same as the VTnr */
470 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
473 seat_evict_position(s, session);
476 if (pos > 0 && !s->positions[pos])
477 s->positions[pos] = session;
480 static void seat_assign_position(Seat *s, Session *session) {
483 if (session->pos > 0)
486 for (pos = 1; pos < s->position_count; ++pos)
487 if (!s->positions[pos])
490 seat_claim_position(s, session, pos);
493 int seat_attach_session(Seat *s, Session *session) {
496 assert(!session->seat);
498 if (!seat_has_vts(s) != !session->vtnr)
502 LIST_PREPEND(sessions_by_seat, s->sessions, session);
503 seat_assign_position(s, session);
505 seat_send_changed(s, "Sessions", NULL);
507 /* On seats with VTs, the VT logic defines which session is active. On
508 * seats without VTs, we automatically activate new sessions. */
509 if (!seat_has_vts(s))
510 seat_set_active(s, session);
515 void seat_complete_switch(Seat *s) {
520 /* if no session-switch is pending or if it got canceled, do nothing */
521 if (!s->pending_switch)
524 session = s->pending_switch;
525 s->pending_switch = NULL;
527 seat_set_active(s, session);
530 bool seat_has_vts(Seat *s) {
533 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
536 bool seat_is_seat0(Seat *s) {
539 return s->manager->seat0 == s;
542 bool seat_can_multi_session(Seat *s) {
545 return seat_has_vts(s);
548 bool seat_can_tty(Seat *s) {
551 return seat_has_vts(s);
554 bool seat_has_master_device(Seat *s) {
557 /* device list is ordered by "master" flag */
558 return !!s->devices && s->devices->master;
561 bool seat_can_graphical(Seat *s) {
564 return seat_has_master_device(s);
567 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
569 bool idle_hint = true;
570 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
574 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
578 ih = session_get_idle_hint(session, &k);
584 if (k.monotonic > ts.monotonic)
590 } else if (idle_hint) {
592 if (k.monotonic > ts.monotonic)
603 bool seat_check_gc(Seat *s, bool drop_not_started) {
606 if (drop_not_started && !s->started)
609 if (seat_is_seat0(s))
612 return seat_has_master_device(s);
615 void seat_add_to_gc_queue(Seat *s) {
621 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
622 s->in_gc_queue = true;
625 static bool seat_name_valid_char(char c) {
627 (c >= 'a' && c <= 'z') ||
628 (c >= 'A' && c <= 'Z') ||
629 (c >= '0' && c <= '9') ||
634 bool seat_name_is_valid(const char *name) {
639 if (!startswith(name, "seat"))
645 for (p = name; *p; p++)
646 if (!seat_name_valid_char(*p))
649 if (strlen(name) > 255)