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/>.
26 #include "sd-messages.h"
28 #include "alloc-util.h"
31 #include "format-util.h"
32 #include "logind-acl.h"
33 #include "logind-seat.h"
35 #include "parse-util.h"
36 #include "stdio-util.h"
37 #include "string-util.h"
38 #include "terminal-util.h"
41 Seat
*seat_new(Manager
*m
, const char *id
) {
51 s
->state_file
= strappend("/run/systemd/seats/", id
);
55 s
->id
= basename(s
->state_file
);
58 if (hashmap_put(m
->seats
, s
->id
, s
) < 0) {
66 void seat_free(Seat
*s
) {
70 LIST_REMOVE(gc_queue
, s
->manager
->seat_gc_queue
, s
);
73 session_free(s
->sessions
);
78 device_free(s
->devices
);
80 hashmap_remove(s
->manager
->seats
, s
->id
);
87 int seat_save(Seat
*s
) {
88 _cleanup_free_
char *temp_path
= NULL
;
89 _cleanup_fclose_
FILE *f
= NULL
;
97 r
= mkdir_safe_label("/run/systemd/seats", 0755, 0, 0, false);
101 r
= fopen_temporary(s
->state_file
, &f
, &temp_path
);
105 fchmod(fileno(f
), 0644);
108 "# This is private data. Do not parse.\n"
110 "CAN_MULTI_SESSION=%i\n"
112 "CAN_GRAPHICAL=%i\n",
114 seat_can_multi_session(s
),
116 seat_can_graphical(s
));
119 assert(s
->active
->user
);
123 "ACTIVE_UID="UID_FMT
"\n",
125 s
->active
->user
->uid
);
131 fputs_unlocked("SESSIONS=", f
);
132 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
) {
136 i
->sessions_by_seat_next
? ' ' : '\n');
139 fputs_unlocked("UIDS=", f
);
140 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
144 i
->sessions_by_seat_next
? ' ' : '\n');
147 r
= fflush_and_check(f
);
151 if (rename(temp_path
, s
->state_file
) < 0) {
159 (void) unlink(s
->state_file
);
162 (void) unlink(temp_path
);
164 return log_error_errno(r
, "Failed to save seat data %s: %m", s
->state_file
);
167 int seat_load(Seat
*s
) {
170 /* There isn't actually anything to read here ... */
175 static int vt_allocate(unsigned int vtnr
) {
176 char p
[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
177 _cleanup_close_
int fd
= -1;
181 xsprintf(p
, "/dev/tty%u", vtnr
);
182 fd
= open_terminal(p
, O_RDWR
|O_NOCTTY
|O_CLOEXEC
);
189 int seat_preallocate_vts(Seat
*s
) {
196 log_debug("Preallocating VTs...");
198 if (s
->manager
->n_autovts
<= 0)
201 if (!seat_has_vts(s
))
204 for (i
= 1; i
<= s
->manager
->n_autovts
; i
++) {
209 log_error_errno(q
, "Failed to preallocate VT %u: %m", i
);
217 int seat_apply_acls(Seat
*s
, Session
*old_active
) {
222 r
= devnode_acl_all(s
->manager
->udev
,
225 !!old_active
, old_active
? old_active
->user
->uid
: 0,
226 !!s
->active
, s
->active
? s
->active
->user
->uid
: 0);
229 log_error_errno(r
, "Failed to apply ACLs: %m");
234 int seat_set_active(Seat
*s
, Session
*session
) {
238 assert(!session
|| session
->seat
== s
);
240 if (session
== s
->active
)
243 old_active
= s
->active
;
247 session_device_pause_all(old_active
);
248 session_send_changed(old_active
, "Active", NULL
);
251 seat_apply_acls(s
, old_active
);
253 if (session
&& session
->started
) {
254 session_send_changed(session
, "Active", NULL
);
255 session_device_resume_all(session
);
258 if (!session
|| session
->started
)
259 seat_send_changed(s
, "ActiveSession", NULL
);
264 session_save(session
);
265 user_save(session
->user
);
269 session_save(old_active
);
270 if (!session
|| session
->user
!= old_active
->user
)
271 user_save(old_active
->user
);
277 int seat_switch_to(Seat
*s
, unsigned int num
) {
278 /* Public session positions skip 0 (there is only F1-F12). Maybe it
279 * will get reassigned in the future, so return error for now. */
283 if (num
>= s
->position_count
|| !s
->positions
[num
]) {
284 /* allow switching to unused VTs to trigger auto-activate */
285 if (seat_has_vts(s
) && num
< 64)
291 return session_activate(s
->positions
[num
]);
294 int seat_switch_to_next(Seat
*s
) {
295 unsigned int start
, i
;
297 if (s
->position_count
== 0)
301 if (s
->active
&& s
->active
->position
> 0)
302 start
= s
->active
->position
;
304 for (i
= start
+ 1; i
< s
->position_count
; ++i
)
306 return session_activate(s
->positions
[i
]);
308 for (i
= 1; i
< start
; ++i
)
310 return session_activate(s
->positions
[i
]);
315 int seat_switch_to_previous(Seat
*s
) {
316 unsigned int start
, i
;
318 if (s
->position_count
== 0)
322 if (s
->active
&& s
->active
->position
> 0)
323 start
= s
->active
->position
;
325 for (i
= start
- 1; i
> 0; --i
)
327 return session_activate(s
->positions
[i
]);
329 for (i
= s
->position_count
- 1; i
> start
; --i
)
331 return session_activate(s
->positions
[i
]);
336 int seat_active_vt_changed(Seat
*s
, unsigned int vtnr
) {
337 Session
*i
, *new_active
= NULL
;
343 if (!seat_has_vts(s
))
346 log_debug("VT changed to %u", vtnr
);
348 /* we might have earlier closing sessions on the same VT, so try to
349 * find a running one first */
350 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
351 if (i
->vtnr
== vtnr
&& !i
->stopping
) {
357 /* no running one? then we can't decide which one is the
358 * active one, let the first one win */
359 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
360 if (i
->vtnr
== vtnr
) {
366 r
= seat_set_active(s
, new_active
);
367 manager_spawn_autovt(s
->manager
, vtnr
);
372 int seat_read_active_vt(Seat
*s
) {
380 if (!seat_has_vts(s
))
383 if (lseek(s
->manager
->console_active_fd
, SEEK_SET
, 0) < 0)
384 return log_error_errno(errno
, "lseek on console_active_fd failed: %m");
386 k
= read(s
->manager
->console_active_fd
, t
, sizeof(t
)-1);
388 log_error("Failed to read current console: %s", k
< 0 ? strerror(-errno
) : "EOF");
389 return k
< 0 ? -errno
: -EIO
;
395 if (!startswith(t
, "tty")) {
396 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
400 r
= safe_atou(t
+3, &vtnr
);
402 return log_error_errno(r
, "Failed to parse VT number \"%s\": %m", t
+3);
405 log_error("VT number invalid: %s", t
+3);
409 return seat_active_vt_changed(s
, vtnr
);
412 int seat_start(Seat
*s
) {
419 "MESSAGE_ID=" SD_MESSAGE_SEAT_START_STR
,
421 LOG_MESSAGE("New seat %s.", s
->id
),
424 /* Initialize VT magic stuff */
425 seat_preallocate_vts(s
);
427 /* Read current VT */
428 seat_read_active_vt(s
);
435 seat_send_signal(s
, true);
440 int seat_stop(Seat
*s
, bool force
) {
447 "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR
,
449 LOG_MESSAGE("Removed seat %s.", s
->id
),
452 seat_stop_sessions(s
, force
);
454 unlink(s
->state_file
);
455 seat_add_to_gc_queue(s
);
458 seat_send_signal(s
, false);
465 int seat_stop_sessions(Seat
*s
, bool force
) {
471 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
472 k
= session_stop(session
, force
);
480 void seat_evict_position(Seat
*s
, Session
*session
) {
482 unsigned int pos
= session
->position
;
484 session
->position
= 0;
489 if (pos
< s
->position_count
&& s
->positions
[pos
] == session
) {
490 s
->positions
[pos
] = NULL
;
492 /* There might be another session claiming the same
493 * position (eg., during gdm->session transition), so let's look
494 * for it and set it on the free slot. */
495 LIST_FOREACH(sessions_by_seat
, iter
, s
->sessions
) {
496 if (iter
->position
== pos
&& session_get_state(iter
) != SESSION_CLOSING
) {
497 s
->positions
[pos
] = iter
;
504 void seat_claim_position(Seat
*s
, Session
*session
, unsigned int pos
) {
505 /* with VTs, the position is always the same as the VTnr */
509 if (!GREEDY_REALLOC0(s
->positions
, s
->position_count
, pos
+ 1))
512 seat_evict_position(s
, session
);
514 session
->position
= pos
;
516 s
->positions
[pos
] = session
;
519 static void seat_assign_position(Seat
*s
, Session
*session
) {
522 if (session
->position
> 0)
525 for (pos
= 1; pos
< s
->position_count
; ++pos
)
526 if (!s
->positions
[pos
])
529 seat_claim_position(s
, session
, pos
);
532 int seat_attach_session(Seat
*s
, Session
*session
) {
535 assert(!session
->seat
);
537 if (!seat_has_vts(s
) != !session
->vtnr
)
541 LIST_PREPEND(sessions_by_seat
, s
->sessions
, session
);
542 seat_assign_position(s
, session
);
544 /* On seats with VTs, the VT logic defines which session is active. On
545 * seats without VTs, we automatically activate new sessions. */
546 if (!seat_has_vts(s
))
547 seat_set_active(s
, session
);
552 void seat_complete_switch(Seat
*s
) {
557 /* if no session-switch is pending or if it got canceled, do nothing */
558 if (!s
->pending_switch
)
561 session
= s
->pending_switch
;
562 s
->pending_switch
= NULL
;
564 seat_set_active(s
, session
);
567 bool seat_has_vts(Seat
*s
) {
570 return seat_is_seat0(s
) && s
->manager
->console_active_fd
>= 0;
573 bool seat_is_seat0(Seat
*s
) {
576 return s
->manager
->seat0
== s
;
579 bool seat_can_multi_session(Seat
*s
) {
582 return seat_has_vts(s
);
585 bool seat_can_tty(Seat
*s
) {
588 return seat_has_vts(s
);
591 bool seat_has_master_device(Seat
*s
) {
594 /* device list is ordered by "master" flag */
595 return !!s
->devices
&& s
->devices
->master
;
598 bool seat_can_graphical(Seat
*s
) {
601 return seat_has_master_device(s
);
604 int seat_get_idle_hint(Seat
*s
, dual_timestamp
*t
) {
606 bool idle_hint
= true;
607 dual_timestamp ts
= DUAL_TIMESTAMP_NULL
;
611 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
615 ih
= session_get_idle_hint(session
, &k
);
621 if (k
.monotonic
> ts
.monotonic
)
627 } else if (idle_hint
) {
629 if (k
.monotonic
> ts
.monotonic
)
640 bool seat_check_gc(Seat
*s
, bool drop_not_started
) {
643 if (drop_not_started
&& !s
->started
)
646 if (seat_is_seat0(s
))
649 return seat_has_master_device(s
);
652 void seat_add_to_gc_queue(Seat
*s
) {
658 LIST_PREPEND(gc_queue
, s
->manager
->seat_gc_queue
, s
);
659 s
->in_gc_queue
= true;
662 static bool seat_name_valid_char(char c
) {
664 (c
>= 'a' && c
<= 'z') ||
665 (c
>= 'A' && c
<= 'Z') ||
666 (c
>= '0' && c
<= '9') ||
670 bool seat_name_is_valid(const char *name
) {
675 if (!startswith(name
, "seat"))
681 for (p
= name
; *p
; p
++)
682 if (!seat_name_valid_char(*p
))
685 if (strlen(name
) > 255)