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