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_preallocate_vts(Seat *s) {
188 log_debug("Preallocating VTs...");
190 if (s->manager->n_autovts <= 0)
193 if (!seat_has_vts(s))
196 for (i = 1; i <= s->manager->n_autovts; i++) {
201 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
209 int seat_apply_acls(Seat *s, Session *old_active) {
214 r = devnode_acl_all(s->manager->udev,
217 !!old_active, old_active ? old_active->user->uid : 0,
218 !!s->active, s->active ? s->active->user->uid : 0);
221 log_error_errno(r, "Failed to apply ACLs: %m");
226 int seat_set_active(Seat *s, Session *session) {
230 assert(!session || session->seat == s);
232 if (session == s->active)
235 old_active = s->active;
239 session_device_pause_all(old_active);
240 session_send_changed(old_active, "Active", NULL);
243 seat_apply_acls(s, old_active);
245 if (session && session->started) {
246 session_send_changed(session, "Active", NULL);
247 session_device_resume_all(session);
250 if (!session || session->started)
251 seat_send_changed(s, "ActiveSession", NULL);
256 session_save(session);
257 user_save(session->user);
261 session_save(old_active);
262 if (!session || session->user != old_active->user)
263 user_save(old_active->user);
269 int seat_switch_to(Seat *s, unsigned int num) {
270 /* Public session positions skip 0 (there is only F1-F12). Maybe it
271 * will get reassigned in the future, so return error for now. */
275 if (num >= s->position_count || !s->positions[num]) {
276 /* allow switching to unused VTs to trigger auto-activate */
277 if (seat_has_vts(s) && num < 64)
283 return session_activate(s->positions[num]);
286 int seat_switch_to_next(Seat *s) {
287 unsigned int start, i;
289 if (s->position_count == 0)
293 if (s->active && s->active->position > 0)
294 start = s->active->position;
296 for (i = start + 1; i < s->position_count; ++i)
298 return session_activate(s->positions[i]);
300 for (i = 1; i < start; ++i)
302 return session_activate(s->positions[i]);
307 int seat_switch_to_previous(Seat *s) {
308 unsigned int start, i;
310 if (s->position_count == 0)
314 if (s->active && s->active->position > 0)
315 start = s->active->position;
317 for (i = start - 1; i > 0; --i)
319 return session_activate(s->positions[i]);
321 for (i = s->position_count - 1; i > start; --i)
323 return session_activate(s->positions[i]);
328 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
329 Session *i, *new_active = NULL;
335 if (!seat_has_vts(s))
338 log_debug("VT changed to %u", vtnr);
340 /* we might have earlier closing sessions on the same VT, so try to
341 * find a running one first */
342 LIST_FOREACH(sessions_by_seat, i, s->sessions)
343 if (i->vtnr == vtnr && !i->stopping) {
349 /* no running one? then we can't decide which one is the
350 * active one, let the first one win */
351 LIST_FOREACH(sessions_by_seat, i, s->sessions)
352 if (i->vtnr == vtnr) {
358 r = seat_set_active(s, new_active);
359 manager_spawn_autovt(s->manager, vtnr);
364 int seat_read_active_vt(Seat *s) {
372 if (!seat_has_vts(s))
375 lseek(s->manager->console_active_fd, SEEK_SET, 0);
377 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
379 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
380 return k < 0 ? -errno : -EIO;
386 if (!startswith(t, "tty")) {
387 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
391 r = safe_atou(t+3, &vtnr);
393 log_error("Failed to parse VT number %s", t+3);
398 log_error("VT number invalid: %s", t+3);
402 return seat_active_vt_changed(s, vtnr);
405 int seat_start(Seat *s) {
412 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
414 LOG_MESSAGE("New seat %s.", s->id),
417 /* Initialize VT magic stuff */
418 seat_preallocate_vts(s);
420 /* Read current VT */
421 seat_read_active_vt(s);
428 seat_send_signal(s, true);
433 int seat_stop(Seat *s, bool force) {
440 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
442 LOG_MESSAGE("Removed seat %s.", s->id),
445 seat_stop_sessions(s, force);
447 unlink(s->state_file);
448 seat_add_to_gc_queue(s);
451 seat_send_signal(s, false);
458 int seat_stop_sessions(Seat *s, bool force) {
464 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
465 k = session_stop(session, force);
473 void seat_evict_position(Seat *s, Session *session) {
475 unsigned int pos = session->position;
477 session->position = 0;
482 if (pos < s->position_count && s->positions[pos] == session) {
483 s->positions[pos] = NULL;
485 /* There might be another session claiming the same
486 * position (eg., during gdm->session transition), so let's look
487 * for it and set it on the free slot. */
488 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
489 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
490 s->positions[pos] = iter;
497 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
498 /* with VTs, the position is always the same as the VTnr */
502 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
505 seat_evict_position(s, session);
507 session->position = pos;
509 s->positions[pos] = session;
512 static void seat_assign_position(Seat *s, Session *session) {
515 if (session->position > 0)
518 for (pos = 1; pos < s->position_count; ++pos)
519 if (!s->positions[pos])
522 seat_claim_position(s, session, pos);
525 int seat_attach_session(Seat *s, Session *session) {
528 assert(!session->seat);
530 if (!seat_has_vts(s) != !session->vtnr)
534 LIST_PREPEND(sessions_by_seat, s->sessions, session);
535 seat_assign_position(s, session);
537 seat_send_changed(s, "Sessions", NULL);
539 /* On seats with VTs, the VT logic defines which session is active. On
540 * seats without VTs, we automatically activate new sessions. */
541 if (!seat_has_vts(s))
542 seat_set_active(s, session);
547 void seat_complete_switch(Seat *s) {
552 /* if no session-switch is pending or if it got canceled, do nothing */
553 if (!s->pending_switch)
556 session = s->pending_switch;
557 s->pending_switch = NULL;
559 seat_set_active(s, session);
562 bool seat_has_vts(Seat *s) {
565 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
568 bool seat_is_seat0(Seat *s) {
571 return s->manager->seat0 == s;
574 bool seat_can_multi_session(Seat *s) {
577 return seat_has_vts(s);
580 bool seat_can_tty(Seat *s) {
583 return seat_has_vts(s);
586 bool seat_has_master_device(Seat *s) {
589 /* device list is ordered by "master" flag */
590 return !!s->devices && s->devices->master;
593 bool seat_can_graphical(Seat *s) {
596 return seat_has_master_device(s);
599 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
601 bool idle_hint = true;
602 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
606 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
610 ih = session_get_idle_hint(session, &k);
616 if (k.monotonic > ts.monotonic)
622 } else if (idle_hint) {
624 if (k.monotonic > ts.monotonic)
635 bool seat_check_gc(Seat *s, bool drop_not_started) {
638 if (drop_not_started && !s->started)
641 if (seat_is_seat0(s))
644 return seat_has_master_device(s);
647 void seat_add_to_gc_queue(Seat *s) {
653 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
654 s->in_gc_queue = true;
657 static bool seat_name_valid_char(char c) {
659 (c >= 'a' && c <= 'z') ||
660 (c >= 'A' && c <= 'Z') ||
661 (c >= '0' && c <= '9') ||
666 bool seat_name_is_valid(const char *name) {
671 if (!startswith(name, "seat"))
677 for (p = name; *p; p++)
678 if (!seat_name_valid_char(*p))
681 if (strlen(name) > 255)