]>
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 "formats-util.h"
30 #include "logind-acl.h"
32 #include "string-util.h"
33 #include "terminal-util.h"
35 #include "logind-seat.h"
37 Seat
*seat_new(Manager
*m
, const char *id
) {
47 s
->state_file
= strappend("/run/systemd/seats/", id
);
53 s
->id
= basename(s
->state_file
);
56 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 snprintf(p
, sizeof(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)