1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include <stdio_ext.h>
27 #include "sd-messages.h"
29 #include "alloc-util.h"
32 #include "format-util.h"
33 #include "logind-acl.h"
34 #include "logind-seat.h"
36 #include "parse-util.h"
37 #include "stdio-util.h"
38 #include "string-util.h"
39 #include "terminal-util.h"
42 Seat *seat_new(Manager *m, const char *id) {
52 s->state_file = strappend("/run/systemd/seats/", id);
56 s->id = basename(s->state_file);
59 if (hashmap_put(m->seats, s->id, s) < 0) {
67 void seat_free(Seat *s) {
71 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
74 session_free(s->sessions);
79 device_free(s->devices);
81 hashmap_remove(s->manager->seats, s->id);
88 int seat_save(Seat *s) {
89 _cleanup_free_ char *temp_path = NULL;
90 _cleanup_fclose_ FILE *f = NULL;
98 r = mkdir_safe_label("/run/elogind/seats", 0755, 0, 0, false);
102 r = fopen_temporary(s->state_file, &f, &temp_path);
106 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
107 (void) fchmod(fileno(f), 0644);
110 "# This is private data. Do not parse.\n"
112 "CAN_MULTI_SESSION=%i\n"
114 "CAN_GRAPHICAL=%i\n",
116 seat_can_multi_session(s),
118 seat_can_graphical(s));
121 assert(s->active->user);
125 "ACTIVE_UID="UID_FMT"\n",
127 s->active->user->uid);
133 fputs("SESSIONS=", f);
134 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
138 i->sessions_by_seat_next ? ' ' : '\n');
142 LIST_FOREACH(sessions_by_seat, i, s->sessions)
146 i->sessions_by_seat_next ? ' ' : '\n');
149 r = fflush_and_check(f);
153 if (rename(temp_path, s->state_file) < 0) {
161 (void) unlink(s->state_file);
164 (void) unlink(temp_path);
166 return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
169 int seat_load(Seat *s) {
172 /* There isn't actually anything to read here ... */
177 #if 0 /// UNNEEDED by elogind
178 static int vt_allocate(unsigned int vtnr) {
179 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
180 _cleanup_close_ int fd = -1;
184 xsprintf(p, "/dev/tty%u", vtnr);
185 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
192 int seat_preallocate_vts(Seat *s) {
199 log_debug("Preallocating VTs...");
201 if (s->manager->n_autovts <= 0)
204 if (!seat_has_vts(s))
207 for (i = 1; i <= s->manager->n_autovts; i++) {
212 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
221 int seat_apply_acls(Seat *s, Session *old_active) {
226 r = devnode_acl_all(s->manager->udev,
229 !!old_active, old_active ? old_active->user->uid : 0,
230 !!s->active, s->active ? s->active->user->uid : 0);
233 log_error_errno(r, "Failed to apply ACLs: %m");
238 int seat_set_active(Seat *s, Session *session) {
242 assert(!session || session->seat == s);
244 if (session == s->active)
247 old_active = s->active;
251 session_device_pause_all(old_active);
252 session_send_changed(old_active, "Active", NULL);
255 seat_apply_acls(s, old_active);
257 if (session && session->started) {
258 session_send_changed(session, "Active", NULL);
259 session_device_resume_all(session);
262 if (!session || session->started)
263 seat_send_changed(s, "ActiveSession", NULL);
268 session_save(session);
269 user_save(session->user);
273 session_save(old_active);
274 if (!session || session->user != old_active->user)
275 user_save(old_active->user);
281 int seat_switch_to(Seat *s, unsigned int num) {
282 /* Public session positions skip 0 (there is only F1-F12). Maybe it
283 * will get reassigned in the future, so return error for now. */
287 if (num >= s->position_count || !s->positions[num]) {
288 /* allow switching to unused VTs to trigger auto-activate */
289 if (seat_has_vts(s) && num < 64)
295 return session_activate(s->positions[num]);
298 int seat_switch_to_next(Seat *s) {
299 unsigned int start, i;
301 if (s->position_count == 0)
305 if (s->active && s->active->position > 0)
306 start = s->active->position;
308 for (i = start + 1; i < s->position_count; ++i)
310 return session_activate(s->positions[i]);
312 for (i = 1; i < start; ++i)
314 return session_activate(s->positions[i]);
319 int seat_switch_to_previous(Seat *s) {
320 unsigned int start, i;
322 if (s->position_count == 0)
326 if (s->active && s->active->position > 0)
327 start = s->active->position;
329 for (i = start - 1; i > 0; --i)
331 return session_activate(s->positions[i]);
333 for (i = s->position_count - 1; i > start; --i)
335 return session_activate(s->positions[i]);
340 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
341 Session *i, *new_active = NULL;
347 if (!seat_has_vts(s))
350 log_debug("VT changed to %u", vtnr);
352 /* we might have earlier closing sessions on the same VT, so try to
353 * find a running one first */
354 LIST_FOREACH(sessions_by_seat, i, s->sessions)
355 if (i->vtnr == vtnr && !i->stopping) {
361 /* no running one? then we can't decide which one is the
362 * active one, let the first one win */
363 LIST_FOREACH(sessions_by_seat, i, s->sessions)
364 if (i->vtnr == vtnr) {
370 r = seat_set_active(s, new_active);
371 #if 0 /// elogind does not spawn autovt
372 manager_spawn_autovt(s->manager, vtnr);
378 int seat_read_active_vt(Seat *s) {
386 if (!seat_has_vts(s))
389 if (lseek(s->manager->console_active_fd, SEEK_SET, 0) < 0)
390 return log_error_errno(errno, "lseek on console_active_fd failed: %m");
392 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
394 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
395 return k < 0 ? -errno : -EIO;
401 if (!startswith(t, "tty")) {
402 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
406 r = safe_atou(t+3, &vtnr);
408 return log_error_errno(r, "Failed to parse VT number \"%s\": %m", t+3);
411 log_error("VT number invalid: %s", t+3);
415 return seat_active_vt_changed(s, vtnr);
418 int seat_start(Seat *s) {
425 "MESSAGE_ID=" SD_MESSAGE_SEAT_START_STR,
427 LOG_MESSAGE("New seat %s.", s->id),
430 /* Initialize VT magic stuff */
431 #if 0 /// elogind does not support autospawning vts
432 seat_preallocate_vts(s);
435 /* Read current VT */
436 seat_read_active_vt(s);
443 seat_send_signal(s, true);
448 int seat_stop(Seat *s, bool force) {
455 "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR,
457 LOG_MESSAGE("Removed seat %s.", s->id),
460 seat_stop_sessions(s, force);
462 unlink(s->state_file);
463 seat_add_to_gc_queue(s);
466 seat_send_signal(s, false);
473 int seat_stop_sessions(Seat *s, bool force) {
479 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
480 k = session_stop(session, force);
488 void seat_evict_position(Seat *s, Session *session) {
490 unsigned int pos = session->position;
492 session->position = 0;
497 if (pos < s->position_count && s->positions[pos] == session) {
498 s->positions[pos] = NULL;
500 /* There might be another session claiming the same
501 * position (eg., during gdm->session transition), so let's look
502 * for it and set it on the free slot. */
503 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
504 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
505 s->positions[pos] = iter;
512 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
513 /* with VTs, the position is always the same as the VTnr */
517 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
520 seat_evict_position(s, session);
522 session->position = pos;
524 s->positions[pos] = session;
527 static void seat_assign_position(Seat *s, Session *session) {
530 if (session->position > 0)
533 for (pos = 1; pos < s->position_count; ++pos)
534 if (!s->positions[pos])
537 seat_claim_position(s, session, pos);
540 int seat_attach_session(Seat *s, Session *session) {
543 assert(!session->seat);
545 if (!seat_has_vts(s) != !session->vtnr)
549 LIST_PREPEND(sessions_by_seat, s->sessions, session);
550 seat_assign_position(s, session);
552 /* On seats with VTs, the VT logic defines which session is active. On
553 * seats without VTs, we automatically activate new sessions. */
554 if (!seat_has_vts(s))
555 seat_set_active(s, session);
560 void seat_complete_switch(Seat *s) {
565 /* if no session-switch is pending or if it got canceled, do nothing */
566 if (!s->pending_switch)
569 session = s->pending_switch;
570 s->pending_switch = NULL;
572 seat_set_active(s, session);
575 bool seat_has_vts(Seat *s) {
578 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
581 bool seat_is_seat0(Seat *s) {
584 return s->manager->seat0 == s;
587 bool seat_can_multi_session(Seat *s) {
590 return seat_has_vts(s);
593 bool seat_can_tty(Seat *s) {
596 return seat_has_vts(s);
599 bool seat_has_master_device(Seat *s) {
602 /* device list is ordered by "master" flag */
603 return !!s->devices && s->devices->master;
606 bool seat_can_graphical(Seat *s) {
609 return seat_has_master_device(s);
612 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
614 bool idle_hint = true;
615 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
619 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
623 ih = session_get_idle_hint(session, &k);
629 if (k.monotonic > ts.monotonic)
635 } else if (idle_hint) {
637 if (k.monotonic > ts.monotonic)
648 bool seat_check_gc(Seat *s, bool drop_not_started) {
651 if (drop_not_started && !s->started)
654 if (seat_is_seat0(s))
657 return seat_has_master_device(s);
660 void seat_add_to_gc_queue(Seat *s) {
666 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
667 s->in_gc_queue = true;
670 static bool seat_name_valid_char(char c) {
672 (c >= 'a' && c <= 'z') ||
673 (c >= 'A' && c <= 'Z') ||
674 (c >= '0' && c <= '9') ||
678 bool seat_name_is_valid(const char *name) {
683 if (!startswith(name, "seat"))
689 for (p = name; *p; p++)
690 if (!seat_name_valid_char(*p))
693 if (strlen(name) > 255)