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/systemd/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 static int vt_allocate(unsigned int vtnr
) {
178 char p
[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
179 _cleanup_close_
int fd
= -1;
183 xsprintf(p
, "/dev/tty%u", vtnr
);
184 fd
= open_terminal(p
, O_RDWR
|O_NOCTTY
|O_CLOEXEC
);
191 int seat_preallocate_vts(Seat
*s
) {
198 log_debug("Preallocating VTs...");
200 if (s
->manager
->n_autovts
<= 0)
203 if (!seat_has_vts(s
))
206 for (i
= 1; i
<= s
->manager
->n_autovts
; i
++) {
211 log_error_errno(q
, "Failed to preallocate VT %u: %m", i
);
219 int seat_apply_acls(Seat
*s
, Session
*old_active
) {
224 r
= devnode_acl_all(s
->manager
->udev
,
227 !!old_active
, old_active
? old_active
->user
->uid
: 0,
228 !!s
->active
, s
->active
? s
->active
->user
->uid
: 0);
231 log_error_errno(r
, "Failed to apply ACLs: %m");
236 int seat_set_active(Seat
*s
, Session
*session
) {
240 assert(!session
|| session
->seat
== s
);
242 if (session
== s
->active
)
245 old_active
= s
->active
;
249 session_device_pause_all(old_active
);
250 session_send_changed(old_active
, "Active", NULL
);
253 seat_apply_acls(s
, old_active
);
255 if (session
&& session
->started
) {
256 session_send_changed(session
, "Active", NULL
);
257 session_device_resume_all(session
);
260 if (!session
|| session
->started
)
261 seat_send_changed(s
, "ActiveSession", NULL
);
266 session_save(session
);
267 user_save(session
->user
);
271 session_save(old_active
);
272 if (!session
|| session
->user
!= old_active
->user
)
273 user_save(old_active
->user
);
279 int seat_switch_to(Seat
*s
, unsigned int num
) {
280 /* Public session positions skip 0 (there is only F1-F12). Maybe it
281 * will get reassigned in the future, so return error for now. */
285 if (num
>= s
->position_count
|| !s
->positions
[num
]) {
286 /* allow switching to unused VTs to trigger auto-activate */
287 if (seat_has_vts(s
) && num
< 64)
293 return session_activate(s
->positions
[num
]);
296 int seat_switch_to_next(Seat
*s
) {
297 unsigned int start
, i
;
299 if (s
->position_count
== 0)
303 if (s
->active
&& s
->active
->position
> 0)
304 start
= s
->active
->position
;
306 for (i
= start
+ 1; i
< s
->position_count
; ++i
)
308 return session_activate(s
->positions
[i
]);
310 for (i
= 1; i
< start
; ++i
)
312 return session_activate(s
->positions
[i
]);
317 int seat_switch_to_previous(Seat
*s
) {
318 unsigned int start
, i
;
320 if (s
->position_count
== 0)
324 if (s
->active
&& s
->active
->position
> 0)
325 start
= s
->active
->position
;
327 for (i
= start
- 1; i
> 0; --i
)
329 return session_activate(s
->positions
[i
]);
331 for (i
= s
->position_count
- 1; i
> start
; --i
)
333 return session_activate(s
->positions
[i
]);
338 int seat_active_vt_changed(Seat
*s
, unsigned int vtnr
) {
339 Session
*i
, *new_active
= NULL
;
345 if (!seat_has_vts(s
))
348 log_debug("VT changed to %u", vtnr
);
350 /* we might have earlier closing sessions on the same VT, so try to
351 * find a running one first */
352 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
353 if (i
->vtnr
== vtnr
&& !i
->stopping
) {
359 /* no running one? then we can't decide which one is the
360 * active one, let the first one win */
361 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
362 if (i
->vtnr
== vtnr
) {
368 r
= seat_set_active(s
, new_active
);
369 manager_spawn_autovt(s
->manager
, vtnr
);
374 int seat_read_active_vt(Seat
*s
) {
382 if (!seat_has_vts(s
))
385 if (lseek(s
->manager
->console_active_fd
, SEEK_SET
, 0) < 0)
386 return log_error_errno(errno
, "lseek on console_active_fd failed: %m");
388 k
= read(s
->manager
->console_active_fd
, t
, sizeof(t
)-1);
390 log_error("Failed to read current console: %s", k
< 0 ? strerror(-errno
) : "EOF");
391 return k
< 0 ? -errno
: -EIO
;
397 if (!startswith(t
, "tty")) {
398 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
402 r
= safe_atou(t
+3, &vtnr
);
404 return log_error_errno(r
, "Failed to parse VT number \"%s\": %m", t
+3);
407 log_error("VT number invalid: %s", t
+3);
411 return seat_active_vt_changed(s
, vtnr
);
414 int seat_start(Seat
*s
) {
421 "MESSAGE_ID=" SD_MESSAGE_SEAT_START_STR
,
423 LOG_MESSAGE("New seat %s.", s
->id
),
426 /* Initialize VT magic stuff */
427 seat_preallocate_vts(s
);
429 /* Read current VT */
430 seat_read_active_vt(s
);
437 seat_send_signal(s
, true);
442 int seat_stop(Seat
*s
, bool force
) {
449 "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR
,
451 LOG_MESSAGE("Removed seat %s.", s
->id
),
454 seat_stop_sessions(s
, force
);
456 unlink(s
->state_file
);
457 seat_add_to_gc_queue(s
);
460 seat_send_signal(s
, false);
467 int seat_stop_sessions(Seat
*s
, bool force
) {
473 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
474 k
= session_stop(session
, force
);
482 void seat_evict_position(Seat
*s
, Session
*session
) {
484 unsigned int pos
= session
->position
;
486 session
->position
= 0;
491 if (pos
< s
->position_count
&& s
->positions
[pos
] == session
) {
492 s
->positions
[pos
] = NULL
;
494 /* There might be another session claiming the same
495 * position (eg., during gdm->session transition), so let's look
496 * for it and set it on the free slot. */
497 LIST_FOREACH(sessions_by_seat
, iter
, s
->sessions
) {
498 if (iter
->position
== pos
&& session_get_state(iter
) != SESSION_CLOSING
) {
499 s
->positions
[pos
] = iter
;
506 void seat_claim_position(Seat
*s
, Session
*session
, unsigned int pos
) {
507 /* with VTs, the position is always the same as the VTnr */
511 if (!GREEDY_REALLOC0(s
->positions
, s
->position_count
, pos
+ 1))
514 seat_evict_position(s
, session
);
516 session
->position
= pos
;
518 s
->positions
[pos
] = session
;
521 static void seat_assign_position(Seat
*s
, Session
*session
) {
524 if (session
->position
> 0)
527 for (pos
= 1; pos
< s
->position_count
; ++pos
)
528 if (!s
->positions
[pos
])
531 seat_claim_position(s
, session
, pos
);
534 int seat_attach_session(Seat
*s
, Session
*session
) {
537 assert(!session
->seat
);
539 if (!seat_has_vts(s
) != !session
->vtnr
)
543 LIST_PREPEND(sessions_by_seat
, s
->sessions
, session
);
544 seat_assign_position(s
, session
);
546 /* On seats with VTs, the VT logic defines which session is active. On
547 * seats without VTs, we automatically activate new sessions. */
548 if (!seat_has_vts(s
))
549 seat_set_active(s
, session
);
554 void seat_complete_switch(Seat
*s
) {
559 /* if no session-switch is pending or if it got canceled, do nothing */
560 if (!s
->pending_switch
)
563 session
= s
->pending_switch
;
564 s
->pending_switch
= NULL
;
566 seat_set_active(s
, session
);
569 bool seat_has_vts(Seat
*s
) {
572 return seat_is_seat0(s
) && s
->manager
->console_active_fd
>= 0;
575 bool seat_is_seat0(Seat
*s
) {
578 return s
->manager
->seat0
== s
;
581 bool seat_can_multi_session(Seat
*s
) {
584 return seat_has_vts(s
);
587 bool seat_can_tty(Seat
*s
) {
590 return seat_has_vts(s
);
593 bool seat_has_master_device(Seat
*s
) {
596 /* device list is ordered by "master" flag */
597 return !!s
->devices
&& s
->devices
->master
;
600 bool seat_can_graphical(Seat
*s
) {
603 return seat_has_master_device(s
);
606 int seat_get_idle_hint(Seat
*s
, dual_timestamp
*t
) {
608 bool idle_hint
= true;
609 dual_timestamp ts
= DUAL_TIMESTAMP_NULL
;
613 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
617 ih
= session_get_idle_hint(session
, &k
);
623 if (k
.monotonic
> ts
.monotonic
)
629 } else if (idle_hint
) {
631 if (k
.monotonic
> ts
.monotonic
)
642 bool seat_check_gc(Seat
*s
, bool drop_not_started
) {
645 if (drop_not_started
&& !s
->started
)
648 if (seat_is_seat0(s
))
651 return seat_has_master_device(s
);
654 void seat_add_to_gc_queue(Seat
*s
) {
660 LIST_PREPEND(gc_queue
, s
->manager
->seat_gc_queue
, s
);
661 s
->in_gc_queue
= true;
664 static bool seat_name_valid_char(char c
) {
666 (c
>= 'a' && c
<= 'z') ||
667 (c
>= 'A' && c
<= 'Z') ||
668 (c
>= '0' && c
<= '9') ||
672 bool seat_name_is_valid(const char *name
) {
677 if (!startswith(name
, "seat"))
683 for (p
= name
; *p
; p
++)
684 if (!seat_name_valid_char(*p
))
687 if (strlen(name
) > 255)