]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-seat.c
Merge pull request #7381 from poettering/cgroup-unified-delegate-rework
[thirdparty/systemd.git] / src / login / logind-seat.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
20263082
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2011 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
20263082
LP
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
20263082 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
20263082
LP
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
20263082 21#include <errno.h>
20263082 22#include <fcntl.h>
5eda94dd 23#include <string.h>
07630cea 24#include <unistd.h>
20263082 25
cc377381 26#include "sd-messages.h"
07630cea 27
b5efdb8a 28#include "alloc-util.h"
3ffd4af2 29#include "fd-util.h"
0d39fa9c 30#include "fileio.h"
f97b34a6 31#include "format-util.h"
5eda94dd 32#include "logind-acl.h"
3ffd4af2 33#include "logind-seat.h"
49e942b2 34#include "mkdir.h"
6bedfcbb 35#include "parse-util.h"
d054f0a4 36#include "stdio-util.h"
07630cea 37#include "string-util.h"
288a74cc 38#include "terminal-util.h"
07630cea 39#include "util.h"
20263082
LP
40
41Seat *seat_new(Manager *m, const char *id) {
42 Seat *s;
43
44 assert(m);
45 assert(id);
46
47 s = new0(Seat, 1);
48 if (!s)
49 return NULL;
50
98a28fef 51 s->state_file = strappend("/run/systemd/seats/", id);
6b430fdb
ZJS
52 if (!s->state_file)
53 return mfree(s);
20263082 54
2b6bf07d 55 s->id = basename(s->state_file);
14c3baca 56 s->manager = m;
20263082
LP
57
58 if (hashmap_put(m->seats, s->id, s) < 0) {
14c3baca 59 free(s->state_file);
6b430fdb 60 return mfree(s);
20263082
LP
61 }
62
20263082
LP
63 return s;
64}
65
66void seat_free(Seat *s) {
67 assert(s);
68
14c3baca 69 if (s->in_gc_queue)
71fda00f 70 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
14c3baca 71
20263082
LP
72 while (s->sessions)
73 session_free(s->sessions);
74
75 assert(!s->active);
76
77 while (s->devices)
78 device_free(s->devices);
79
80 hashmap_remove(s->manager->seats, s->id);
81
49e6fdbf 82 free(s->positions);
d2f92cdf 83 free(s->state_file);
20263082
LP
84 free(s);
85}
86
87int seat_save(Seat *s) {
cc377381
LP
88 _cleanup_free_ char *temp_path = NULL;
89 _cleanup_fclose_ FILE *f = NULL;
20263082
LP
90 int r;
91
92 assert(s);
93
accaeded
LP
94 if (!s->started)
95 return 0;
96
c31ad024 97 r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0, false);
20263082 98 if (r < 0)
dacd6cee 99 goto fail;
14c3baca
LP
100
101 r = fopen_temporary(s->state_file, &f, &temp_path);
102 if (r < 0)
dacd6cee 103 goto fail;
20263082 104
14c3baca 105 fchmod(fileno(f), 0644);
20263082
LP
106
107 fprintf(f,
14c3baca 108 "# This is private data. Do not parse.\n"
92432fcc 109 "IS_SEAT0=%i\n"
f1a8e221
LP
110 "CAN_MULTI_SESSION=%i\n"
111 "CAN_TTY=%i\n"
112 "CAN_GRAPHICAL=%i\n",
92432fcc 113 seat_is_seat0(s),
f1a8e221
LP
114 seat_can_multi_session(s),
115 seat_can_tty(s),
116 seat_can_graphical(s));
20263082
LP
117
118 if (s->active) {
119 assert(s->active->user);
120
121 fprintf(f,
122 "ACTIVE=%s\n"
a08ac7e0 123 "ACTIVE_UID="UID_FMT"\n",
20263082 124 s->active->id,
a08ac7e0 125 s->active->user->uid);
20263082
LP
126 }
127
128 if (s->sessions) {
129 Session *i;
20263082 130
4b61c875 131 fputs_unlocked("SESSIONS=", f);
20263082 132 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
14c3baca
LP
133 fprintf(f,
134 "%s%c",
135 i->id,
136 i->sessions_by_seat_next ? ' ' : '\n');
137 }
20263082 138
4b61c875 139 fputs_unlocked("UIDS=", f);
fde78a3a 140 LIST_FOREACH(sessions_by_seat, i, s->sessions)
20263082 141 fprintf(f,
90b2de37
ZJS
142 UID_FMT"%c",
143 i->user->uid,
14c3baca 144 i->sessions_by_seat_next ? ' ' : '\n');
20263082
LP
145 }
146
dacd6cee
LP
147 r = fflush_and_check(f);
148 if (r < 0)
149 goto fail;
14c3baca 150
dacd6cee 151 if (rename(temp_path, s->state_file) < 0) {
20263082 152 r = -errno;
dacd6cee 153 goto fail;
20263082
LP
154 }
155
dacd6cee
LP
156 return 0;
157
158fail:
159 (void) unlink(s->state_file);
160
161 if (temp_path)
162 (void) unlink(temp_path);
14c3baca 163
dacd6cee 164 return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
20263082
LP
165}
166
167int seat_load(Seat *s) {
168 assert(s);
169
a185c5aa
LP
170 /* There isn't actually anything to read here ... */
171
20263082
LP
172 return 0;
173}
174
92bd5ff3 175static int vt_allocate(unsigned int vtnr) {
61376f96 176 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
cc377381 177 _cleanup_close_ int fd = -1;
20263082
LP
178
179 assert(vtnr >= 1);
180
d054f0a4 181 xsprintf(p, "/dev/tty%u", vtnr);
20263082 182 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
cc377381
LP
183 if (fd < 0)
184 return -errno;
20263082 185
cc377381 186 return 0;
20263082
LP
187}
188
30ed21ce 189int seat_preallocate_vts(Seat *s) {
3f49d45a
LP
190 int r = 0;
191 unsigned i;
20263082
LP
192
193 assert(s);
194 assert(s->manager);
195
30ed21ce
LP
196 log_debug("Preallocating VTs...");
197
20263082
LP
198 if (s->manager->n_autovts <= 0)
199 return 0;
200
bf7825ae 201 if (!seat_has_vts(s))
20263082
LP
202 return 0;
203
e7886786 204 for (i = 1; i <= s->manager->n_autovts; i++) {
20263082
LP
205 int q;
206
207 q = vt_allocate(i);
14c3baca 208 if (q < 0) {
1fa2f38f 209 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
20263082 210 r = q;
14c3baca 211 }
20263082
LP
212 }
213
214 return r;
215}
216
5eda94dd
LP
217int seat_apply_acls(Seat *s, Session *old_active) {
218 int r;
20263082 219
5eda94dd 220 assert(s);
20263082 221
5eda94dd
LP
222 r = devnode_acl_all(s->manager->udev,
223 s->id,
224 false,
225 !!old_active, old_active ? old_active->user->uid : 0,
226 !!s->active, s->active ? s->active->user->uid : 0);
20263082 227
5eda94dd 228 if (r < 0)
da927ba9 229 log_error_errno(r, "Failed to apply ACLs: %m");
20263082
LP
230
231 return r;
232}
233
9418f147
LP
234int seat_set_active(Seat *s, Session *session) {
235 Session *old_active;
236
237 assert(s);
77527da0 238 assert(!session || session->seat == s);
9418f147
LP
239
240 if (session == s->active)
241 return 0;
242
243 old_active = s->active;
244 s->active = session;
245
dfd55270 246 if (old_active) {
118ecf32 247 session_device_pause_all(old_active);
cc377381 248 session_send_changed(old_active, "Active", NULL);
dfd55270 249 }
118ecf32 250
9418f147
LP
251 seat_apply_acls(s, old_active);
252
118ecf32 253 if (session && session->started) {
cc377381 254 session_send_changed(session, "Active", NULL);
118ecf32
DH
255 session_device_resume_all(session);
256 }
9418f147
LP
257
258 if (!session || session->started)
cc377381 259 seat_send_changed(s, "ActiveSession", NULL);
9418f147 260
98a28fef
LP
261 seat_save(s);
262
7f7bb946 263 if (session) {
98a28fef 264 session_save(session);
7f7bb946
LP
265 user_save(session->user);
266 }
98a28fef 267
7f7bb946 268 if (old_active) {
98a28fef 269 session_save(old_active);
78ab361c
CG
270 if (!session || session->user != old_active->user)
271 user_save(old_active->user);
7f7bb946 272 }
98a28fef 273
9418f147
LP
274 return 0;
275}
276
49e6fdbf
DH
277int seat_switch_to(Seat *s, unsigned int num) {
278 /* Public session positions skip 0 (there is only F1-F12). Maybe it
279 * will get reassigned in the future, so return error for now. */
55493982 280 if (num == 0)
49e6fdbf
DH
281 return -EINVAL;
282
15403427
DH
283 if (num >= s->position_count || !s->positions[num]) {
284 /* allow switching to unused VTs to trigger auto-activate */
285 if (seat_has_vts(s) && num < 64)
286 return chvt(num);
287
49e6fdbf 288 return -EINVAL;
15403427 289 }
49e6fdbf
DH
290
291 return session_activate(s->positions[num]);
292}
293
294int seat_switch_to_next(Seat *s) {
295 unsigned int start, i;
296
55493982 297 if (s->position_count == 0)
49e6fdbf
DH
298 return -EINVAL;
299
300 start = 1;
e6494a07
DH
301 if (s->active && s->active->position > 0)
302 start = s->active->position;
49e6fdbf
DH
303
304 for (i = start + 1; i < s->position_count; ++i)
305 if (s->positions[i])
306 return session_activate(s->positions[i]);
307
308 for (i = 1; i < start; ++i)
309 if (s->positions[i])
310 return session_activate(s->positions[i]);
311
312 return -EINVAL;
313}
314
315int seat_switch_to_previous(Seat *s) {
316 unsigned int start, i;
317
55493982 318 if (s->position_count == 0)
49e6fdbf
DH
319 return -EINVAL;
320
321 start = 1;
e6494a07
DH
322 if (s->active && s->active->position > 0)
323 start = s->active->position;
49e6fdbf
DH
324
325 for (i = start - 1; i > 0; --i)
326 if (s->positions[i])
327 return session_activate(s->positions[i]);
328
329 for (i = s->position_count - 1; i > start; --i)
330 if (s->positions[i])
331 return session_activate(s->positions[i]);
332
333 return -EINVAL;
334}
335
92bd5ff3 336int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
9418f147
LP
337 Session *i, *new_active = NULL;
338 int r;
20263082
LP
339
340 assert(s);
341 assert(vtnr >= 1);
20263082 342
bf7825ae 343 if (!seat_has_vts(s))
14c3baca
LP
344 return -EINVAL;
345
92bd5ff3 346 log_debug("VT changed to %u", vtnr);
20263082 347
be94d954
MP
348 /* we might have earlier closing sessions on the same VT, so try to
349 * find a running one first */
20263082 350 LIST_FOREACH(sessions_by_seat, i, s->sessions)
be94d954 351 if (i->vtnr == vtnr && !i->stopping) {
14c3baca 352 new_active = i;
20263082
LP
353 break;
354 }
355
be94d954
MP
356 if (!new_active) {
357 /* no running one? then we can't decide which one is the
358 * active one, let the first one win */
359 LIST_FOREACH(sessions_by_seat, i, s->sessions)
360 if (i->vtnr == vtnr) {
361 new_active = i;
362 break;
363 }
364 }
365
9418f147 366 r = seat_set_active(s, new_active);
5eda94dd 367 manager_spawn_autovt(s->manager, vtnr);
20263082 368
9418f147 369 return r;
20263082
LP
370}
371
14c3baca
LP
372int seat_read_active_vt(Seat *s) {
373 char t[64];
374 ssize_t k;
92bd5ff3
DH
375 unsigned int vtnr;
376 int r;
14c3baca
LP
377
378 assert(s);
379
bf7825ae 380 if (!seat_has_vts(s))
14c3baca
LP
381 return 0;
382
e8f3e7a7
ZJS
383 if (lseek(s->manager->console_active_fd, SEEK_SET, 0) < 0)
384 return log_error_errno(errno, "lseek on console_active_fd failed: %m");
14c3baca
LP
385
386 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
387 if (k <= 0) {
388 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
389 return k < 0 ? -errno : -EIO;
390 }
391
392 t[k] = 0;
393 truncate_nl(t);
394
395 if (!startswith(t, "tty")) {
396 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
397 return -EIO;
398 }
399
92bd5ff3 400 r = safe_atou(t+3, &vtnr);
e8f3e7a7
ZJS
401 if (r < 0)
402 return log_error_errno(r, "Failed to parse VT number \"%s\": %m", t+3);
14c3baca 403
92bd5ff3 404 if (!vtnr) {
14c3baca
LP
405 log_error("VT number invalid: %s", t+3);
406 return -EIO;
407 }
408
409 return seat_active_vt_changed(s, vtnr);
410}
411
412int seat_start(Seat *s) {
413 assert(s);
414
3f49d45a
LP
415 if (s->started)
416 return 0;
417
877d54e9 418 log_struct(LOG_INFO,
2b044526 419 "MESSAGE_ID=" SD_MESSAGE_SEAT_START_STR,
877d54e9 420 "SEAT_ID=%s", s->id,
e2cc6eca 421 LOG_MESSAGE("New seat %s.", s->id),
877d54e9 422 NULL);
3f49d45a 423
14c3baca
LP
424 /* Initialize VT magic stuff */
425 seat_preallocate_vts(s);
426
427 /* Read current VT */
428 seat_read_active_vt(s);
429
7f7bb946
LP
430 s->started = true;
431
14c3baca
LP
432 /* Save seat data */
433 seat_save(s);
434
da119395
LP
435 seat_send_signal(s, true);
436
14c3baca
LP
437 return 0;
438}
439
9bb69af4 440int seat_stop(Seat *s, bool force) {
a185c5aa 441 int r = 0;
20263082
LP
442
443 assert(s);
444
ed18b08b 445 if (s->started)
877d54e9 446 log_struct(LOG_INFO,
2b044526 447 "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR,
877d54e9 448 "SEAT_ID=%s", s->id,
e2cc6eca 449 LOG_MESSAGE("Removed seat %s.", s->id),
877d54e9 450 NULL);
da119395 451
9bb69af4 452 seat_stop_sessions(s, force);
a185c5aa
LP
453
454 unlink(s->state_file);
455 seat_add_to_gc_queue(s);
456
ed18b08b
LP
457 if (s->started)
458 seat_send_signal(s, false);
459
a185c5aa
LP
460 s->started = false;
461
462 return r;
463}
464
9bb69af4 465int seat_stop_sessions(Seat *s, bool force) {
a185c5aa
LP
466 Session *session;
467 int r = 0, k;
468
469 assert(s);
470
20263082 471 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
9bb69af4 472 k = session_stop(session, force);
20263082
LP
473 if (k < 0)
474 r = k;
475 }
476
a185c5aa
LP
477 return r;
478}
14c3baca 479
49e6fdbf 480void seat_evict_position(Seat *s, Session *session) {
3e6b205f 481 Session *iter;
e6494a07 482 unsigned int pos = session->position;
49e6fdbf 483
e6494a07 484 session->position = 0;
49e6fdbf 485
55493982 486 if (pos == 0)
49e6fdbf
DH
487 return;
488
3e6b205f 489 if (pos < s->position_count && s->positions[pos] == session) {
49e6fdbf 490 s->positions[pos] = NULL;
3e6b205f
DH
491
492 /* There might be another session claiming the same
55493982 493 * position (eg., during gdm->session transition), so let's look
3e6b205f
DH
494 * for it and set it on the free slot. */
495 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
28103328 496 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
3e6b205f
DH
497 s->positions[pos] = iter;
498 break;
499 }
500 }
501 }
49e6fdbf
DH
502}
503
504void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
505 /* with VTs, the position is always the same as the VTnr */
506 if (seat_has_vts(s))
507 pos = session->vtnr;
508
ca2d3784 509 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
49e6fdbf
DH
510 return;
511
512 seat_evict_position(s, session);
513
e6494a07 514 session->position = pos;
da770c38 515 if (pos > 0)
49e6fdbf
DH
516 s->positions[pos] = session;
517}
518
519static void seat_assign_position(Seat *s, Session *session) {
520 unsigned int pos;
521
e6494a07 522 if (session->position > 0)
49e6fdbf
DH
523 return;
524
525 for (pos = 1; pos < s->position_count; ++pos)
526 if (!s->positions[pos])
527 break;
528
529 seat_claim_position(s, session, pos);
530}
531
a185c5aa
LP
532int seat_attach_session(Seat *s, Session *session) {
533 assert(s);
534 assert(session);
535 assert(!session->seat);
3f49d45a 536
c506027a
DH
537 if (!seat_has_vts(s) != !session->vtnr)
538 return -EINVAL;
539
a185c5aa 540 session->seat = s;
71fda00f 541 LIST_PREPEND(sessions_by_seat, s->sessions, session);
49e6fdbf 542 seat_assign_position(s, session);
a185c5aa 543
bf7825ae 544 /* On seats with VTs, the VT logic defines which session is active. On
3fdb2494
DH
545 * seats without VTs, we automatically activate new sessions. */
546 if (!seat_has_vts(s))
9418f147 547 seat_set_active(s, session);
9418f147 548
a185c5aa
LP
549 return 0;
550}
551
d7bd01b5
DH
552void seat_complete_switch(Seat *s) {
553 Session *session;
554
555 assert(s);
556
557 /* if no session-switch is pending or if it got canceled, do nothing */
558 if (!s->pending_switch)
559 return;
560
561 session = s->pending_switch;
562 s->pending_switch = NULL;
563
564 seat_set_active(s, session);
565}
566
bf7825ae
DH
567bool seat_has_vts(Seat *s) {
568 assert(s);
569
570 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
571}
572
92432fcc 573bool seat_is_seat0(Seat *s) {
a185c5aa
LP
574 assert(s);
575
92432fcc 576 return s->manager->seat0 == s;
a185c5aa
LP
577}
578
addedec4
LP
579bool seat_can_multi_session(Seat *s) {
580 assert(s);
581
bf7825ae 582 return seat_has_vts(s);
addedec4
LP
583}
584
f1a8e221
LP
585bool seat_can_tty(Seat *s) {
586 assert(s);
587
bf7825ae 588 return seat_has_vts(s);
f1a8e221
LP
589}
590
718d006a
DH
591bool seat_has_master_device(Seat *s) {
592 assert(s);
593
594 /* device list is ordered by "master" flag */
595 return !!s->devices && s->devices->master;
596}
597
f1a8e221
LP
598bool seat_can_graphical(Seat *s) {
599 assert(s);
600
718d006a 601 return seat_has_master_device(s);
f1a8e221
LP
602}
603
a185c5aa
LP
604int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
605 Session *session;
606 bool idle_hint = true;
5cb14b37 607 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
a185c5aa
LP
608
609 assert(s);
610
611 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
612 dual_timestamp k;
613 int ih;
614
615 ih = session_get_idle_hint(session, &k);
616 if (ih < 0)
617 return ih;
618
619 if (!ih) {
620 if (!idle_hint) {
4a271908 621 if (k.monotonic > ts.monotonic)
a185c5aa
LP
622 ts = k;
623 } else {
624 idle_hint = false;
625 ts = k;
626 }
627 } else if (idle_hint) {
628
629 if (k.monotonic > ts.monotonic)
630 ts = k;
631 }
632 }
633
634 if (t)
635 *t = ts;
636
637 return idle_hint;
20263082 638}
14c3baca 639
cc377381 640bool seat_check_gc(Seat *s, bool drop_not_started) {
14c3baca
LP
641 assert(s);
642
4a4b033f 643 if (drop_not_started && !s->started)
cc377381 644 return false;
932e3ee7 645
92432fcc 646 if (seat_is_seat0(s))
cc377381 647 return true;
14c3baca 648
718d006a 649 return seat_has_master_device(s);
14c3baca
LP
650}
651
652void seat_add_to_gc_queue(Seat *s) {
653 assert(s);
654
655 if (s->in_gc_queue)
656 return;
657
71fda00f 658 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
14c3baca
LP
659 s->in_gc_queue = true;
660}
3f49d45a
LP
661
662static bool seat_name_valid_char(char c) {
663 return
664 (c >= 'a' && c <= 'z') ||
665 (c >= 'A' && c <= 'Z') ||
666 (c >= '0' && c <= '9') ||
4c701096 667 IN_SET(c, '-', '_');
3f49d45a
LP
668}
669
670bool seat_name_is_valid(const char *name) {
671 const char *p;
672
673 assert(name);
674
675 if (!startswith(name, "seat"))
676 return false;
677
678 if (!name[4])
679 return false;
680
681 for (p = name; *p; p++)
682 if (!seat_name_valid_char(*p))
683 return false;
684
1c9a2c10
LP
685 if (strlen(name) > 255)
686 return false;
687
3f49d45a
LP
688 return true;
689}