]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
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"
29 #include "alloc-util.h"
32 #include "formats-util.h"
33 #include "logind-acl.h"
34 #include "logind-seat.h"
36 #include "parse-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
);
57 s
->id
= basename(s
->state_file
);
60 if (hashmap_put(m
->seats
, s
->id
, s
) < 0) {
69 void seat_free(Seat
*s
) {
73 LIST_REMOVE(gc_queue
, s
->manager
->seat_gc_queue
, s
);
76 session_free(s
->sessions
);
81 device_free(s
->devices
);
83 hashmap_remove(s
->manager
->seats
, s
->id
);
90 int seat_save(Seat
*s
) {
91 _cleanup_free_
char *temp_path
= NULL
;
92 _cleanup_fclose_
FILE *f
= NULL
;
100 r
= mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
104 r
= fopen_temporary(s
->state_file
, &f
, &temp_path
);
108 fchmod(fileno(f
), 0644);
111 "# This is private data. Do not parse.\n"
113 "CAN_MULTI_SESSION=%i\n"
115 "CAN_GRAPHICAL=%i\n",
117 seat_can_multi_session(s
),
119 seat_can_graphical(s
));
122 assert(s
->active
->user
);
126 "ACTIVE_UID="UID_FMT
"\n",
128 s
->active
->user
->uid
);
134 fputs("SESSIONS=", f
);
135 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
) {
139 i
->sessions_by_seat_next
? ' ' : '\n');
143 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
147 i
->sessions_by_seat_next
? ' ' : '\n');
150 r
= fflush_and_check(f
);
154 if (rename(temp_path
, s
->state_file
) < 0) {
162 (void) unlink(s
->state_file
);
165 (void) unlink(temp_path
);
167 return log_error_errno(r
, "Failed to save seat data %s: %m", s
->state_file
);
170 int seat_load(Seat
*s
) {
173 /* There isn't actually anything to read here ... */
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 snprintf(p
, sizeof(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
);
220 int seat_apply_acls(Seat
*s
, Session
*old_active
) {
225 r
= devnode_acl_all(s
->manager
->udev
,
228 !!old_active
, old_active
? old_active
->user
->uid
: 0,
229 !!s
->active
, s
->active
? s
->active
->user
->uid
: 0);
232 log_error_errno(r
, "Failed to apply ACLs: %m");
237 int seat_set_active(Seat
*s
, Session
*session
) {
241 assert(!session
|| session
->seat
== s
);
243 if (session
== s
->active
)
246 old_active
= s
->active
;
250 session_device_pause_all(old_active
);
251 session_send_changed(old_active
, "Active", NULL
);
254 seat_apply_acls(s
, old_active
);
256 if (session
&& session
->started
) {
257 session_send_changed(session
, "Active", NULL
);
258 session_device_resume_all(session
);
261 if (!session
|| session
->started
)
262 seat_send_changed(s
, "ActiveSession", NULL
);
267 session_save(session
);
268 user_save(session
->user
);
272 session_save(old_active
);
273 if (!session
|| session
->user
!= old_active
->user
)
274 user_save(old_active
->user
);
280 int seat_switch_to(Seat
*s
, unsigned int num
) {
281 /* Public session positions skip 0 (there is only F1-F12). Maybe it
282 * will get reassigned in the future, so return error for now. */
286 if (num
>= s
->position_count
|| !s
->positions
[num
]) {
287 /* allow switching to unused VTs to trigger auto-activate */
288 if (seat_has_vts(s
) && num
< 64)
294 return session_activate(s
->positions
[num
]);
297 int seat_switch_to_next(Seat
*s
) {
298 unsigned int start
, i
;
300 if (s
->position_count
== 0)
304 if (s
->active
&& s
->active
->position
> 0)
305 start
= s
->active
->position
;
307 for (i
= start
+ 1; i
< s
->position_count
; ++i
)
309 return session_activate(s
->positions
[i
]);
311 for (i
= 1; i
< start
; ++i
)
313 return session_activate(s
->positions
[i
]);
318 int seat_switch_to_previous(Seat
*s
) {
319 unsigned int start
, i
;
321 if (s
->position_count
== 0)
325 if (s
->active
&& s
->active
->position
> 0)
326 start
= s
->active
->position
;
328 for (i
= start
- 1; i
> 0; --i
)
330 return session_activate(s
->positions
[i
]);
332 for (i
= s
->position_count
- 1; i
> start
; --i
)
334 return session_activate(s
->positions
[i
]);
339 int seat_active_vt_changed(Seat
*s
, unsigned int vtnr
) {
340 Session
*i
, *new_active
= NULL
;
346 if (!seat_has_vts(s
))
349 log_debug("VT changed to %u", vtnr
);
351 /* we might have earlier closing sessions on the same VT, so try to
352 * find a running one first */
353 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
354 if (i
->vtnr
== vtnr
&& !i
->stopping
) {
360 /* no running one? then we can't decide which one is the
361 * active one, let the first one win */
362 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
363 if (i
->vtnr
== vtnr
) {
369 r
= seat_set_active(s
, new_active
);
370 manager_spawn_autovt(s
->manager
, vtnr
);
375 int seat_read_active_vt(Seat
*s
) {
383 if (!seat_has_vts(s
))
386 lseek(s
->manager
->console_active_fd
, SEEK_SET
, 0);
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 log_error("Failed to parse VT number %s", t
+3);
409 log_error("VT number invalid: %s", t
+3);
413 return seat_active_vt_changed(s
, vtnr
);
416 int seat_start(Seat
*s
) {
423 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START
),
425 LOG_MESSAGE("New seat %s.", s
->id
),
428 /* Initialize VT magic stuff */
429 seat_preallocate_vts(s
);
431 /* Read current VT */
432 seat_read_active_vt(s
);
439 seat_send_signal(s
, true);
444 int seat_stop(Seat
*s
, bool force
) {
451 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP
),
453 LOG_MESSAGE("Removed seat %s.", s
->id
),
456 seat_stop_sessions(s
, force
);
458 unlink(s
->state_file
);
459 seat_add_to_gc_queue(s
);
462 seat_send_signal(s
, false);
469 int seat_stop_sessions(Seat
*s
, bool force
) {
475 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
476 k
= session_stop(session
, force
);
484 void seat_evict_position(Seat
*s
, Session
*session
) {
486 unsigned int pos
= session
->position
;
488 session
->position
= 0;
493 if (pos
< s
->position_count
&& s
->positions
[pos
] == session
) {
494 s
->positions
[pos
] = NULL
;
496 /* There might be another session claiming the same
497 * position (eg., during gdm->session transition), so let's look
498 * for it and set it on the free slot. */
499 LIST_FOREACH(sessions_by_seat
, iter
, s
->sessions
) {
500 if (iter
->position
== pos
&& session_get_state(iter
) != SESSION_CLOSING
) {
501 s
->positions
[pos
] = iter
;
508 void seat_claim_position(Seat
*s
, Session
*session
, unsigned int pos
) {
509 /* with VTs, the position is always the same as the VTnr */
513 if (!GREEDY_REALLOC0(s
->positions
, s
->position_count
, pos
+ 1))
516 seat_evict_position(s
, session
);
518 session
->position
= pos
;
520 s
->positions
[pos
] = session
;
523 static void seat_assign_position(Seat
*s
, Session
*session
) {
526 if (session
->position
> 0)
529 for (pos
= 1; pos
< s
->position_count
; ++pos
)
530 if (!s
->positions
[pos
])
533 seat_claim_position(s
, session
, pos
);
536 int seat_attach_session(Seat
*s
, Session
*session
) {
539 assert(!session
->seat
);
541 if (!seat_has_vts(s
) != !session
->vtnr
)
545 LIST_PREPEND(sessions_by_seat
, s
->sessions
, session
);
546 seat_assign_position(s
, session
);
548 seat_send_changed(s
, "Sessions", NULL
);
550 /* On seats with VTs, the VT logic defines which session is active. On
551 * seats without VTs, we automatically activate new sessions. */
552 if (!seat_has_vts(s
))
553 seat_set_active(s
, session
);
558 void seat_complete_switch(Seat
*s
) {
563 /* if no session-switch is pending or if it got canceled, do nothing */
564 if (!s
->pending_switch
)
567 session
= s
->pending_switch
;
568 s
->pending_switch
= NULL
;
570 seat_set_active(s
, session
);
573 bool seat_has_vts(Seat
*s
) {
576 return seat_is_seat0(s
) && s
->manager
->console_active_fd
>= 0;
579 bool seat_is_seat0(Seat
*s
) {
582 return s
->manager
->seat0
== s
;
585 bool seat_can_multi_session(Seat
*s
) {
588 return seat_has_vts(s
);
591 bool seat_can_tty(Seat
*s
) {
594 return seat_has_vts(s
);
597 bool seat_has_master_device(Seat
*s
) {
600 /* device list is ordered by "master" flag */
601 return !!s
->devices
&& s
->devices
->master
;
604 bool seat_can_graphical(Seat
*s
) {
607 return seat_has_master_device(s
);
610 int seat_get_idle_hint(Seat
*s
, dual_timestamp
*t
) {
612 bool idle_hint
= true;
613 dual_timestamp ts
= DUAL_TIMESTAMP_NULL
;
617 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
621 ih
= session_get_idle_hint(session
, &k
);
627 if (k
.monotonic
> ts
.monotonic
)
633 } else if (idle_hint
) {
635 if (k
.monotonic
> ts
.monotonic
)
646 bool seat_check_gc(Seat
*s
, bool drop_not_started
) {
649 if (drop_not_started
&& !s
->started
)
652 if (seat_is_seat0(s
))
655 return seat_has_master_device(s
);
658 void seat_add_to_gc_queue(Seat
*s
) {
664 LIST_PREPEND(gc_queue
, s
->manager
->seat_gc_queue
, s
);
665 s
->in_gc_queue
= true;
668 static bool seat_name_valid_char(char c
) {
670 (c
>= 'a' && c
<= 'z') ||
671 (c
>= 'A' && c
<= 'Z') ||
672 (c
>= '0' && c
<= '9') ||
677 bool seat_name_is_valid(const char *name
) {
682 if (!startswith(name
, "seat"))
688 for (p
= name
; *p
; p
++)
689 if (!seat_name_valid_char(*p
))
692 if (strlen(name
) > 255)