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