]>
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"
28 #include "logind-seat.h"
29 #include "logind-acl.h"
32 #include "formats-util.h"
33 #include "terminal-util.h"
35 Seat
*seat_new(Manager
*m
, const char *id
) {
45 s
->state_file
= strappend("/run/systemd/seats/", id
);
51 s
->id
= basename(s
->state_file
);
54 if (hashmap_put(m
->seats
, s
->id
, s
) < 0) {
63 void seat_free(Seat
*s
) {
67 LIST_REMOVE(gc_queue
, s
->manager
->seat_gc_queue
, s
);
70 session_free(s
->sessions
);
75 device_free(s
->devices
);
77 hashmap_remove(s
->manager
->seats
, s
->id
);
84 int seat_save(Seat
*s
) {
85 _cleanup_free_
char *temp_path
= NULL
;
86 _cleanup_fclose_
FILE *f
= NULL
;
94 r
= mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
98 r
= fopen_temporary(s
->state_file
, &f
, &temp_path
);
102 fchmod(fileno(f
), 0644);
105 "# This is private data. Do not parse.\n"
107 "CAN_MULTI_SESSION=%i\n"
109 "CAN_GRAPHICAL=%i\n",
111 seat_can_multi_session(s
),
113 seat_can_graphical(s
));
116 assert(s
->active
->user
);
120 "ACTIVE_UID="UID_FMT
"\n",
122 s
->active
->user
->uid
);
128 fputs("SESSIONS=", f
);
129 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
) {
133 i
->sessions_by_seat_next
? ' ' : '\n');
137 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
141 i
->sessions_by_seat_next
? ' ' : '\n');
144 r
= fflush_and_check(f
);
148 if (rename(temp_path
, s
->state_file
) < 0) {
156 (void) unlink(s
->state_file
);
159 (void) unlink(temp_path
);
161 return log_error_errno(r
, "Failed to save seat data %s: %m", s
->state_file
);
164 int seat_load(Seat
*s
) {
167 /* There isn't actually anything to read here ... */
172 static int vt_allocate(unsigned int vtnr
) {
173 char p
[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
174 _cleanup_close_
int fd
= -1;
178 snprintf(p
, sizeof(p
), "/dev/tty%u", vtnr
);
179 fd
= open_terminal(p
, O_RDWR
|O_NOCTTY
|O_CLOEXEC
);
186 int seat_preallocate_vts(Seat
*s
) {
193 log_debug("Preallocating VTs...");
195 if (s
->manager
->n_autovts
<= 0)
198 if (!seat_has_vts(s
))
201 for (i
= 1; i
<= s
->manager
->n_autovts
; i
++) {
206 log_error_errno(q
, "Failed to preallocate VT %u: %m", i
);
214 int seat_apply_acls(Seat
*s
, Session
*old_active
) {
219 r
= devnode_acl_all(s
->manager
->udev
,
222 !!old_active
, old_active
? old_active
->user
->uid
: 0,
223 !!s
->active
, s
->active
? s
->active
->user
->uid
: 0);
226 log_error_errno(r
, "Failed to apply ACLs: %m");
231 int seat_set_active(Seat
*s
, Session
*session
) {
235 assert(!session
|| session
->seat
== s
);
237 if (session
== s
->active
)
240 old_active
= s
->active
;
244 session_device_pause_all(old_active
);
245 session_send_changed(old_active
, "Active", NULL
);
248 seat_apply_acls(s
, old_active
);
250 if (session
&& session
->started
) {
251 session_send_changed(session
, "Active", NULL
);
252 session_device_resume_all(session
);
255 if (!session
|| session
->started
)
256 seat_send_changed(s
, "ActiveSession", NULL
);
261 session_save(session
);
262 user_save(session
->user
);
266 session_save(old_active
);
267 if (!session
|| session
->user
!= old_active
->user
)
268 user_save(old_active
->user
);
274 int seat_switch_to(Seat
*s
, unsigned int num
) {
275 /* Public session positions skip 0 (there is only F1-F12). Maybe it
276 * will get reassigned in the future, so return error for now. */
280 if (num
>= s
->position_count
|| !s
->positions
[num
]) {
281 /* allow switching to unused VTs to trigger auto-activate */
282 if (seat_has_vts(s
) && num
< 64)
288 return session_activate(s
->positions
[num
]);
291 int seat_switch_to_next(Seat
*s
) {
292 unsigned int start
, i
;
294 if (s
->position_count
== 0)
298 if (s
->active
&& s
->active
->position
> 0)
299 start
= s
->active
->position
;
301 for (i
= start
+ 1; i
< s
->position_count
; ++i
)
303 return session_activate(s
->positions
[i
]);
305 for (i
= 1; i
< start
; ++i
)
307 return session_activate(s
->positions
[i
]);
312 int seat_switch_to_previous(Seat
*s
) {
313 unsigned int start
, i
;
315 if (s
->position_count
== 0)
319 if (s
->active
&& s
->active
->position
> 0)
320 start
= s
->active
->position
;
322 for (i
= start
- 1; i
> 0; --i
)
324 return session_activate(s
->positions
[i
]);
326 for (i
= s
->position_count
- 1; i
> start
; --i
)
328 return session_activate(s
->positions
[i
]);
333 int seat_active_vt_changed(Seat
*s
, unsigned int vtnr
) {
334 Session
*i
, *new_active
= NULL
;
340 if (!seat_has_vts(s
))
343 log_debug("VT changed to %u", vtnr
);
345 /* we might have earlier closing sessions on the same VT, so try to
346 * find a running one first */
347 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
348 if (i
->vtnr
== vtnr
&& !i
->stopping
) {
354 /* no running one? then we can't decide which one is the
355 * active one, let the first one win */
356 LIST_FOREACH(sessions_by_seat
, i
, s
->sessions
)
357 if (i
->vtnr
== vtnr
) {
363 r
= seat_set_active(s
, new_active
);
364 manager_spawn_autovt(s
->manager
, vtnr
);
369 int seat_read_active_vt(Seat
*s
) {
377 if (!seat_has_vts(s
))
380 lseek(s
->manager
->console_active_fd
, SEEK_SET
, 0);
382 k
= read(s
->manager
->console_active_fd
, t
, sizeof(t
)-1);
384 log_error("Failed to read current console: %s", k
< 0 ? strerror(-errno
) : "EOF");
385 return k
< 0 ? -errno
: -EIO
;
391 if (!startswith(t
, "tty")) {
392 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
396 r
= safe_atou(t
+3, &vtnr
);
398 log_error("Failed to parse VT number %s", t
+3);
403 log_error("VT number invalid: %s", t
+3);
407 return seat_active_vt_changed(s
, vtnr
);
410 int seat_start(Seat
*s
) {
417 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START
),
419 LOG_MESSAGE("New seat %s.", s
->id
),
422 /* Initialize VT magic stuff */
423 seat_preallocate_vts(s
);
425 /* Read current VT */
426 seat_read_active_vt(s
);
433 seat_send_signal(s
, true);
438 int seat_stop(Seat
*s
, bool force
) {
445 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP
),
447 LOG_MESSAGE("Removed seat %s.", s
->id
),
450 seat_stop_sessions(s
, force
);
452 unlink(s
->state_file
);
453 seat_add_to_gc_queue(s
);
456 seat_send_signal(s
, false);
463 int seat_stop_sessions(Seat
*s
, bool force
) {
469 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
470 k
= session_stop(session
, force
);
478 void seat_evict_position(Seat
*s
, Session
*session
) {
480 unsigned int pos
= session
->position
;
482 session
->position
= 0;
487 if (pos
< s
->position_count
&& s
->positions
[pos
] == session
) {
488 s
->positions
[pos
] = NULL
;
490 /* There might be another session claiming the same
491 * position (eg., during gdm->session transition), so let's look
492 * for it and set it on the free slot. */
493 LIST_FOREACH(sessions_by_seat
, iter
, s
->sessions
) {
494 if (iter
->position
== pos
&& session_get_state(iter
) != SESSION_CLOSING
) {
495 s
->positions
[pos
] = iter
;
502 void seat_claim_position(Seat
*s
, Session
*session
, unsigned int pos
) {
503 /* with VTs, the position is always the same as the VTnr */
507 if (!GREEDY_REALLOC0(s
->positions
, s
->position_count
, pos
+ 1))
510 seat_evict_position(s
, session
);
512 session
->position
= pos
;
514 s
->positions
[pos
] = session
;
517 static void seat_assign_position(Seat
*s
, Session
*session
) {
520 if (session
->position
> 0)
523 for (pos
= 1; pos
< s
->position_count
; ++pos
)
524 if (!s
->positions
[pos
])
527 seat_claim_position(s
, session
, pos
);
530 int seat_attach_session(Seat
*s
, Session
*session
) {
533 assert(!session
->seat
);
535 if (!seat_has_vts(s
) != !session
->vtnr
)
539 LIST_PREPEND(sessions_by_seat
, s
->sessions
, session
);
540 seat_assign_position(s
, session
);
542 seat_send_changed(s
, "Sessions", NULL
);
544 /* On seats with VTs, the VT logic defines which session is active. On
545 * seats without VTs, we automatically activate new sessions. */
546 if (!seat_has_vts(s
))
547 seat_set_active(s
, session
);
552 void seat_complete_switch(Seat
*s
) {
557 /* if no session-switch is pending or if it got canceled, do nothing */
558 if (!s
->pending_switch
)
561 session
= s
->pending_switch
;
562 s
->pending_switch
= NULL
;
564 seat_set_active(s
, session
);
567 bool seat_has_vts(Seat
*s
) {
570 return seat_is_seat0(s
) && s
->manager
->console_active_fd
>= 0;
573 bool seat_is_seat0(Seat
*s
) {
576 return s
->manager
->seat0
== s
;
579 bool seat_can_multi_session(Seat
*s
) {
582 return seat_has_vts(s
);
585 bool seat_can_tty(Seat
*s
) {
588 return seat_has_vts(s
);
591 bool seat_has_master_device(Seat
*s
) {
594 /* device list is ordered by "master" flag */
595 return !!s
->devices
&& s
->devices
->master
;
598 bool seat_can_graphical(Seat
*s
) {
601 return seat_has_master_device(s
);
604 int seat_get_idle_hint(Seat
*s
, dual_timestamp
*t
) {
606 bool idle_hint
= true;
607 dual_timestamp ts
= DUAL_TIMESTAMP_NULL
;
611 LIST_FOREACH(sessions_by_seat
, session
, s
->sessions
) {
615 ih
= session_get_idle_hint(session
, &k
);
621 if (k
.monotonic
> ts
.monotonic
)
627 } else if (idle_hint
) {
629 if (k
.monotonic
> ts
.monotonic
)
640 bool seat_check_gc(Seat
*s
, bool drop_not_started
) {
643 if (drop_not_started
&& !s
->started
)
646 if (seat_is_seat0(s
))
649 return seat_has_master_device(s
);
652 void seat_add_to_gc_queue(Seat
*s
) {
658 LIST_PREPEND(gc_queue
, s
->manager
->seat_gc_queue
, s
);
659 s
->in_gc_queue
= true;
662 static bool seat_name_valid_char(char c
) {
664 (c
>= 'a' && c
<= 'z') ||
665 (c
>= 'A' && c
<= 'Z') ||
666 (c
>= '0' && c
<= '9') ||
671 bool seat_name_is_valid(const char *name
) {
676 if (!startswith(name
, "seat"))
682 for (p
= name
; *p
; p
++)
683 if (!seat_name_valid_char(*p
))
686 if (strlen(name
) > 255)