]>
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"
30 #include "formats-util.h"
31 #include "logind-acl.h"
32 #include "logind-seat.h"
34 #include "parse-util.h"
35 #include "string-util.h"
36 #include "terminal-util.h"
39 Seat
*seat_new(Manager
*m
, const char *id
) {
49 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) {
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);
102 r
= fopen_temporary(s
->state_file
, &f
, &temp_path
);
106 fchmod(fileno(f
), 0644);
109 "# This is private data. Do not parse.\n"
111 "CAN_MULTI_SESSION=%i\n"
113 "CAN_GRAPHICAL=%i\n",
115 seat_can_multi_session(s
),
117 seat_can_graphical(s
));
120 assert(s
->active
->user
);
124 "ACTIVE_UID="UID_FMT
"\n",
126 s
->active
->user
->uid
);
132 fputs("SESSIONS=", f
);
133 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
) {
137 i
->sessions_by_seat_next
? ' ' : '\n');
141 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
145 i
->sessions_by_seat_next
? ' ' : '\n');
148 r
= fflush_and_check(f
);
152 if (rename(temp_path
, s
->state_file
) < 0) {
160 (void) unlink(s
->state_file
);
163 (void) unlink(temp_path
);
165 return log_error_errno(r
, "Failed to save seat data %s: %m", s
->state_file
);
168 int seat_load(Seat
*s
) {
171 /* There isn't actually anything to read here ... */
176 static int vt_allocate(unsigned int vtnr
) {
177 char p
[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
178 _cleanup_close_
int fd
= -1;
182 snprintf(p
, sizeof(p
), "/dev/tty%u", vtnr
);
183 fd
= open_terminal(p
, O_RDWR
|O_NOCTTY
|O_CLOEXEC
);
190 int seat_preallocate_vts(Seat
*s
) {
197 log_debug("Preallocating VTs...");
199 if (s
->manager
->n_autovts
<= 0)
202 if (!seat_has_vts(s
))
205 for (i
= 1; i
<= s
->manager
->n_autovts
; i
++) {
210 log_error_errno(q
, "Failed to preallocate VT %u: %m", i
);
218 int seat_apply_acls(Seat
*s
, Session
*old_active
) {
223 r
= devnode_acl_all(s
->manager
->udev
,
226 !!old_active
, old_active
? old_active
->user
->uid
: 0,
227 !!s
->active
, s
->active
? s
->active
->user
->uid
: 0);
230 log_error_errno(r
, "Failed to apply ACLs: %m");
235 int seat_set_active(Seat
*s
, Session
*session
) {
239 assert(!session
|| session
->seat
== s
);
241 if (session
== s
->active
)
244 old_active
= s
->active
;
248 session_device_pause_all(old_active
);
249 session_send_changed(old_active
, "Active", NULL
);
252 seat_apply_acls(s
, old_active
);
254 if (session
&& session
->started
) {
255 session_send_changed(session
, "Active", NULL
);
256 session_device_resume_all(session
);
259 if (!session
|| session
->started
)
260 seat_send_changed(s
, "ActiveSession", NULL
);
265 session_save(session
);
266 user_save(session
->user
);
270 session_save(old_active
);
271 if (!session
|| session
->user
!= old_active
->user
)
272 user_save(old_active
->user
);
278 int seat_switch_to(Seat
*s
, unsigned int num
) {
279 /* Public session positions skip 0 (there is only F1-F12). Maybe it
280 * will get reassigned in the future, so return error for now. */
284 if (num
>= s
->position_count
|| !s
->positions
[num
]) {
285 /* allow switching to unused VTs to trigger auto-activate */
286 if (seat_has_vts(s
) && num
< 64)
292 return session_activate(s
->positions
[num
]);
295 int seat_switch_to_next(Seat
*s
) {
296 unsigned int start
, i
;
298 if (s
->position_count
== 0)
302 if (s
->active
&& s
->active
->position
> 0)
303 start
= s
->active
->position
;
305 for (i
= start
+ 1; i
< s
->position_count
; ++i
)
307 return session_activate(s
->positions
[i
]);
309 for (i
= 1; i
< start
; ++i
)
311 return session_activate(s
->positions
[i
]);
316 int seat_switch_to_previous(Seat
*s
) {
317 unsigned int start
, i
;
319 if (s
->position_count
== 0)
323 if (s
->active
&& s
->active
->position
> 0)
324 start
= s
->active
->position
;
326 for (i
= start
- 1; i
> 0; --i
)
328 return session_activate(s
->positions
[i
]);
330 for (i
= s
->position_count
- 1; i
> start
; --i
)
332 return session_activate(s
->positions
[i
]);
337 int seat_active_vt_changed(Seat
*s
, unsigned int vtnr
) {
338 Session
*i
, *new_active
= NULL
;
344 if (!seat_has_vts(s
))
347 log_debug("VT changed to %u", vtnr
);
349 /* we might have earlier closing sessions on the same VT, so try to
350 * find a running one first */
351 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
352 if (i
->vtnr
== vtnr
&& !i
->stopping
) {
358 /* no running one? then we can't decide which one is the
359 * active one, let the first one win */
360 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
361 if (i
->vtnr
== vtnr
) {
367 r
= seat_set_active(s
, new_active
);
368 manager_spawn_autovt(s
->manager
, vtnr
);
373 int seat_read_active_vt(Seat
*s
) {
381 if (!seat_has_vts(s
))
384 lseek(s
->manager
->console_active_fd
, SEEK_SET
, 0);
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 log_error("Failed to parse VT number %s", 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 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START
),
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 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP
),
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 seat_send_changed(s
, "Sessions", NULL
);
548 /* On seats with VTs, the VT logic defines which session is active. On
549 * seats without VTs, we automatically activate new sessions. */
550 if (!seat_has_vts(s
))
551 seat_set_active(s
, session
);
556 void seat_complete_switch(Seat
*s
) {
561 /* if no session-switch is pending or if it got canceled, do nothing */
562 if (!s
->pending_switch
)
565 session
= s
->pending_switch
;
566 s
->pending_switch
= NULL
;
568 seat_set_active(s
, session
);
571 bool seat_has_vts(Seat
*s
) {
574 return seat_is_seat0(s
) && s
->manager
->console_active_fd
>= 0;
577 bool seat_is_seat0(Seat
*s
) {
580 return s
->manager
->seat0
== s
;
583 bool seat_can_multi_session(Seat
*s
) {
586 return seat_has_vts(s
);
589 bool seat_can_tty(Seat
*s
) {
592 return seat_has_vts(s
);
595 bool seat_has_master_device(Seat
*s
) {
598 /* device list is ordered by "master" flag */
599 return !!s
->devices
&& s
->devices
->master
;
602 bool seat_can_graphical(Seat
*s
) {
605 return seat_has_master_device(s
);
608 int seat_get_idle_hint(Seat
*s
, dual_timestamp
*t
) {
610 bool idle_hint
= true;
611 dual_timestamp ts
= DUAL_TIMESTAMP_NULL
;
615 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
619 ih
= session_get_idle_hint(session
, &k
);
625 if (k
.monotonic
> ts
.monotonic
)
631 } else if (idle_hint
) {
633 if (k
.monotonic
> ts
.monotonic
)
644 bool seat_check_gc(Seat
*s
, bool drop_not_started
) {
647 if (drop_not_started
&& !s
->started
)
650 if (seat_is_seat0(s
))
653 return seat_has_master_device(s
);
656 void seat_add_to_gc_queue(Seat
*s
) {
662 LIST_PREPEND(gc_queue
, s
->manager
->seat_gc_queue
, s
);
663 s
->in_gc_queue
= true;
666 static bool seat_name_valid_char(char c
) {
668 (c
>= 'a' && c
<= 'z') ||
669 (c
>= 'A' && c
<= 'Z') ||
670 (c
>= '0' && c
<= '9') ||
675 bool seat_name_is_valid(const char *name
) {
680 if (!startswith(name
, "seat"))
686 for (p
= name
; *p
; p
++)
687 if (!seat_name_valid_char(*p
))
690 if (strlen(name
) > 255)