]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include "sd-messages.h"
27 #include "alloc-util.h"
30 #include "formats-util.h"
31 #include "logind-acl.h"
32 #include "logind-seat.h"
34 #include "parse-util.h"
35 #include "stdio-util.h"
36 #include "string-util.h"
37 #include "terminal-util.h"
40 Seat
*seat_new(Manager
*m
, const char *id
) {
50 s
->state_file
= strappend("/run/systemd/seats/", id
);
54 s
->id
= basename(s
->state_file
);
57 if (hashmap_put(m
->seats
, s
->id
, s
) < 0) {
65 void seat_free(Seat
*s
) {
69 LIST_REMOVE(gc_queue
, s
->manager
->seat_gc_queue
, s
);
72 session_free(s
->sessions
);
77 device_free(s
->devices
);
79 hashmap_remove(s
->manager
->seats
, s
->id
);
86 int seat_save(Seat
*s
) {
87 _cleanup_free_
char *temp_path
= NULL
;
88 _cleanup_fclose_
FILE *f
= NULL
;
96 r
= mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
100 r
= fopen_temporary(s
->state_file
, &f
, &temp_path
);
104 fchmod(fileno(f
), 0644);
107 "# This is private data. Do not parse.\n"
109 "CAN_MULTI_SESSION=%i\n"
111 "CAN_GRAPHICAL=%i\n",
113 seat_can_multi_session(s
),
115 seat_can_graphical(s
));
118 assert(s
->active
->user
);
122 "ACTIVE_UID="UID_FMT
"\n",
124 s
->active
->user
->uid
);
130 fputs("SESSIONS=", f
);
131 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
) {
135 i
->sessions_by_seat_next
? ' ' : '\n');
139 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
143 i
->sessions_by_seat_next
? ' ' : '\n');
146 r
= fflush_and_check(f
);
150 if (rename(temp_path
, s
->state_file
) < 0) {
158 (void) unlink(s
->state_file
);
161 (void) unlink(temp_path
);
163 return log_error_errno(r
, "Failed to save seat data %s: %m", s
->state_file
);
166 int seat_load(Seat
*s
) {
169 /* There isn't actually anything to read here ... */
174 static int vt_allocate(unsigned int vtnr
) {
175 char p
[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
176 _cleanup_close_
int fd
= -1;
180 xsprintf(p
, "/dev/tty%u", vtnr
);
181 fd
= open_terminal(p
, O_RDWR
|O_NOCTTY
|O_CLOEXEC
);
188 int seat_preallocate_vts(Seat
*s
) {
195 log_debug("Preallocating VTs...");
197 if (s
->manager
->n_autovts
<= 0)
200 if (!seat_has_vts(s
))
203 for (i
= 1; i
<= s
->manager
->n_autovts
; i
++) {
208 log_error_errno(q
, "Failed to preallocate VT %u: %m", i
);
216 int seat_apply_acls(Seat
*s
, Session
*old_active
) {
221 r
= devnode_acl_all(s
->manager
->udev
,
224 !!old_active
, old_active
? old_active
->user
->uid
: 0,
225 !!s
->active
, s
->active
? s
->active
->user
->uid
: 0);
228 log_error_errno(r
, "Failed to apply ACLs: %m");
233 int seat_set_active(Seat
*s
, Session
*session
) {
237 assert(!session
|| session
->seat
== s
);
239 if (session
== s
->active
)
242 old_active
= s
->active
;
246 session_device_pause_all(old_active
);
247 session_send_changed(old_active
, "Active", NULL
);
250 seat_apply_acls(s
, old_active
);
252 if (session
&& session
->started
) {
253 session_send_changed(session
, "Active", NULL
);
254 session_device_resume_all(session
);
257 if (!session
|| session
->started
)
258 seat_send_changed(s
, "ActiveSession", NULL
);
263 session_save(session
);
264 user_save(session
->user
);
268 session_save(old_active
);
269 if (!session
|| session
->user
!= old_active
->user
)
270 user_save(old_active
->user
);
276 int seat_switch_to(Seat
*s
, unsigned int num
) {
277 /* Public session positions skip 0 (there is only F1-F12). Maybe it
278 * will get reassigned in the future, so return error for now. */
282 if (num
>= s
->position_count
|| !s
->positions
[num
]) {
283 /* allow switching to unused VTs to trigger auto-activate */
284 if (seat_has_vts(s
) && num
< 64)
290 return session_activate(s
->positions
[num
]);
293 int seat_switch_to_next(Seat
*s
) {
294 unsigned int start
, i
;
296 if (s
->position_count
== 0)
300 if (s
->active
&& s
->active
->position
> 0)
301 start
= s
->active
->position
;
303 for (i
= start
+ 1; i
< s
->position_count
; ++i
)
305 return session_activate(s
->positions
[i
]);
307 for (i
= 1; i
< start
; ++i
)
309 return session_activate(s
->positions
[i
]);
314 int seat_switch_to_previous(Seat
*s
) {
315 unsigned int start
, i
;
317 if (s
->position_count
== 0)
321 if (s
->active
&& s
->active
->position
> 0)
322 start
= s
->active
->position
;
324 for (i
= start
- 1; i
> 0; --i
)
326 return session_activate(s
->positions
[i
]);
328 for (i
= s
->position_count
- 1; i
> start
; --i
)
330 return session_activate(s
->positions
[i
]);
335 int seat_active_vt_changed(Seat
*s
, unsigned int vtnr
) {
336 Session
*i
, *new_active
= NULL
;
342 if (!seat_has_vts(s
))
345 log_debug("VT changed to %u", vtnr
);
347 /* we might have earlier closing sessions on the same VT, so try to
348 * find a running one first */
349 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
350 if (i
->vtnr
== vtnr
&& !i
->stopping
) {
356 /* no running one? then we can't decide which one is the
357 * active one, let the first one win */
358 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
359 if (i
->vtnr
== vtnr
) {
365 r
= seat_set_active(s
, new_active
);
366 manager_spawn_autovt(s
->manager
, vtnr
);
371 int seat_read_active_vt(Seat
*s
) {
379 if (!seat_has_vts(s
))
382 lseek(s
->manager
->console_active_fd
, SEEK_SET
, 0);
384 k
= read(s
->manager
->console_active_fd
, t
, sizeof(t
)-1);
386 log_error("Failed to read current console: %s", k
< 0 ? strerror(-errno
) : "EOF");
387 return k
< 0 ? -errno
: -EIO
;
393 if (!startswith(t
, "tty")) {
394 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
398 r
= safe_atou(t
+3, &vtnr
);
400 log_error("Failed to parse VT number %s", 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 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START
),
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 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP
),
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 seat_send_changed(s
, "Sessions", NULL
);
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') ||
673 bool seat_name_is_valid(const char *name
) {
678 if (!startswith(name
, "seat"))
684 for (p
= name
; *p
; p
++)
685 if (!seat_name_valid_char(*p
))
688 if (strlen(name
) > 255)