]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
tree-wide: drop license boilerplate
[thirdparty/systemd.git] / src / login / logind-seat.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2011 Lennart Poettering
6 ***/
7
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <stdio_ext.h>
11 #include <string.h>
12 #include <unistd.h>
13
14 #include "sd-messages.h"
15
16 #include "alloc-util.h"
17 #include "fd-util.h"
18 #include "fileio.h"
19 #include "format-util.h"
20 #include "logind-acl.h"
21 #include "logind-seat.h"
22 #include "mkdir.h"
23 #include "parse-util.h"
24 #include "stdio-util.h"
25 #include "string-util.h"
26 #include "terminal-util.h"
27 #include "util.h"
28
29 Seat *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
39 s->state_file = strappend("/run/systemd/seats/", id);
40 if (!s->state_file)
41 return mfree(s);
42
43 s->id = basename(s->state_file);
44 s->manager = m;
45
46 if (hashmap_put(m->seats, s->id, s) < 0) {
47 free(s->state_file);
48 return mfree(s);
49 }
50
51 return s;
52 }
53
54 void seat_free(Seat *s) {
55 assert(s);
56
57 if (s->in_gc_queue)
58 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
59
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
70 free(s->positions);
71 free(s->state_file);
72 free(s);
73 }
74
75 int seat_save(Seat *s) {
76 _cleanup_free_ char *temp_path = NULL;
77 _cleanup_fclose_ FILE *f = NULL;
78 int r;
79
80 assert(s);
81
82 if (!s->started)
83 return 0;
84
85 r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0, MKDIR_WARN_MODE);
86 if (r < 0)
87 goto fail;
88
89 r = fopen_temporary(s->state_file, &f, &temp_path);
90 if (r < 0)
91 goto fail;
92
93 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
94 (void) fchmod(fileno(f), 0644);
95
96 fprintf(f,
97 "# This is private data. Do not parse.\n"
98 "IS_SEAT0=%i\n"
99 "CAN_MULTI_SESSION=%i\n"
100 "CAN_TTY=%i\n"
101 "CAN_GRAPHICAL=%i\n",
102 seat_is_seat0(s),
103 seat_can_multi_session(s),
104 seat_can_tty(s),
105 seat_can_graphical(s));
106
107 if (s->active) {
108 assert(s->active->user);
109
110 fprintf(f,
111 "ACTIVE=%s\n"
112 "ACTIVE_UID="UID_FMT"\n",
113 s->active->id,
114 s->active->user->uid);
115 }
116
117 if (s->sessions) {
118 Session *i;
119
120 fputs("SESSIONS=", f);
121 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
122 fprintf(f,
123 "%s%c",
124 i->id,
125 i->sessions_by_seat_next ? ' ' : '\n');
126 }
127
128 fputs("UIDS=", f);
129 LIST_FOREACH(sessions_by_seat, i, s->sessions)
130 fprintf(f,
131 UID_FMT"%c",
132 i->user->uid,
133 i->sessions_by_seat_next ? ' ' : '\n');
134 }
135
136 r = fflush_and_check(f);
137 if (r < 0)
138 goto fail;
139
140 if (rename(temp_path, s->state_file) < 0) {
141 r = -errno;
142 goto fail;
143 }
144
145 return 0;
146
147 fail:
148 (void) unlink(s->state_file);
149
150 if (temp_path)
151 (void) unlink(temp_path);
152
153 return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
154 }
155
156 int seat_load(Seat *s) {
157 assert(s);
158
159 /* There isn't actually anything to read here ... */
160
161 return 0;
162 }
163
164 static int vt_allocate(unsigned int vtnr) {
165 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
166 _cleanup_close_ int fd = -1;
167
168 assert(vtnr >= 1);
169
170 xsprintf(p, "/dev/tty%u", vtnr);
171 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
172 if (fd < 0)
173 return -errno;
174
175 return 0;
176 }
177
178 int seat_preallocate_vts(Seat *s) {
179 int r = 0;
180 unsigned i;
181
182 assert(s);
183 assert(s->manager);
184
185 log_debug("Preallocating VTs...");
186
187 if (s->manager->n_autovts <= 0)
188 return 0;
189
190 if (!seat_has_vts(s))
191 return 0;
192
193 for (i = 1; i <= s->manager->n_autovts; i++) {
194 int q;
195
196 q = vt_allocate(i);
197 if (q < 0) {
198 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
199 r = q;
200 }
201 }
202
203 return r;
204 }
205
206 int seat_apply_acls(Seat *s, Session *old_active) {
207 int r;
208
209 assert(s);
210
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);
216
217 if (r < 0)
218 log_error_errno(r, "Failed to apply ACLs: %m");
219
220 return r;
221 }
222
223 int seat_set_active(Seat *s, Session *session) {
224 Session *old_active;
225
226 assert(s);
227 assert(!session || session->seat == s);
228
229 if (session == s->active)
230 return 0;
231
232 old_active = s->active;
233 s->active = session;
234
235 if (old_active) {
236 session_device_pause_all(old_active);
237 session_send_changed(old_active, "Active", NULL);
238 }
239
240 seat_apply_acls(s, old_active);
241
242 if (session && session->started) {
243 session_send_changed(session, "Active", NULL);
244 session_device_resume_all(session);
245 }
246
247 if (!session || session->started)
248 seat_send_changed(s, "ActiveSession", NULL);
249
250 seat_save(s);
251
252 if (session) {
253 session_save(session);
254 user_save(session->user);
255 }
256
257 if (old_active) {
258 session_save(old_active);
259 if (!session || session->user != old_active->user)
260 user_save(old_active->user);
261 }
262
263 return 0;
264 }
265
266 int 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. */
269 if (num == 0)
270 return -EINVAL;
271
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
277 return -EINVAL;
278 }
279
280 return session_activate(s->positions[num]);
281 }
282
283 int seat_switch_to_next(Seat *s) {
284 unsigned int start, i;
285
286 if (s->position_count == 0)
287 return -EINVAL;
288
289 start = 1;
290 if (s->active && s->active->position > 0)
291 start = s->active->position;
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
304 int seat_switch_to_previous(Seat *s) {
305 unsigned int start, i;
306
307 if (s->position_count == 0)
308 return -EINVAL;
309
310 start = 1;
311 if (s->active && s->active->position > 0)
312 start = s->active->position;
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
325 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
326 Session *i, *new_active = NULL;
327 int r;
328
329 assert(s);
330 assert(vtnr >= 1);
331
332 if (!seat_has_vts(s))
333 return -EINVAL;
334
335 log_debug("VT changed to %u", vtnr);
336
337 /* we might have earlier closing sessions on the same VT, so try to
338 * find a running one first */
339 LIST_FOREACH(sessions_by_seat, i, s->sessions)
340 if (i->vtnr == vtnr && !i->stopping) {
341 new_active = i;
342 break;
343 }
344
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
355 r = seat_set_active(s, new_active);
356 manager_spawn_autovt(s->manager, vtnr);
357
358 return r;
359 }
360
361 int seat_read_active_vt(Seat *s) {
362 char t[64];
363 ssize_t k;
364 unsigned int vtnr;
365 int r;
366
367 assert(s);
368
369 if (!seat_has_vts(s))
370 return 0;
371
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");
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
389 r = safe_atou(t+3, &vtnr);
390 if (r < 0)
391 return log_error_errno(r, "Failed to parse VT number \"%s\": %m", t+3);
392
393 if (!vtnr) {
394 log_error("VT number invalid: %s", t+3);
395 return -EIO;
396 }
397
398 return seat_active_vt_changed(s, vtnr);
399 }
400
401 int seat_start(Seat *s) {
402 assert(s);
403
404 if (s->started)
405 return 0;
406
407 log_struct(LOG_INFO,
408 "MESSAGE_ID=" SD_MESSAGE_SEAT_START_STR,
409 "SEAT_ID=%s", s->id,
410 LOG_MESSAGE("New seat %s.", s->id),
411 NULL);
412
413 /* Initialize VT magic stuff */
414 seat_preallocate_vts(s);
415
416 /* Read current VT */
417 seat_read_active_vt(s);
418
419 s->started = true;
420
421 /* Save seat data */
422 seat_save(s);
423
424 seat_send_signal(s, true);
425
426 return 0;
427 }
428
429 int seat_stop(Seat *s, bool force) {
430 int r = 0;
431
432 assert(s);
433
434 if (s->started)
435 log_struct(LOG_INFO,
436 "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR,
437 "SEAT_ID=%s", s->id,
438 LOG_MESSAGE("Removed seat %s.", s->id),
439 NULL);
440
441 seat_stop_sessions(s, force);
442
443 unlink(s->state_file);
444 seat_add_to_gc_queue(s);
445
446 if (s->started)
447 seat_send_signal(s, false);
448
449 s->started = false;
450
451 return r;
452 }
453
454 int seat_stop_sessions(Seat *s, bool force) {
455 Session *session;
456 int r = 0, k;
457
458 assert(s);
459
460 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
461 k = session_stop(session, force);
462 if (k < 0)
463 r = k;
464 }
465
466 return r;
467 }
468
469 void seat_evict_position(Seat *s, Session *session) {
470 Session *iter;
471 unsigned int pos = session->position;
472
473 session->position = 0;
474
475 if (pos == 0)
476 return;
477
478 if (pos < s->position_count && s->positions[pos] == session) {
479 s->positions[pos] = NULL;
480
481 /* There might be another session claiming the same
482 * position (eg., during gdm->session transition), so let's look
483 * for it and set it on the free slot. */
484 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
485 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
486 s->positions[pos] = iter;
487 break;
488 }
489 }
490 }
491 }
492
493 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
494 /* with VTs, the position is always the same as the VTnr */
495 if (seat_has_vts(s))
496 pos = session->vtnr;
497
498 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
499 return;
500
501 seat_evict_position(s, session);
502
503 session->position = pos;
504 if (pos > 0)
505 s->positions[pos] = session;
506 }
507
508 static void seat_assign_position(Seat *s, Session *session) {
509 unsigned int pos;
510
511 if (session->position > 0)
512 return;
513
514 for (pos = 1; pos < s->position_count; ++pos)
515 if (!s->positions[pos])
516 break;
517
518 seat_claim_position(s, session, pos);
519 }
520
521 int seat_attach_session(Seat *s, Session *session) {
522 assert(s);
523 assert(session);
524 assert(!session->seat);
525
526 if (!seat_has_vts(s) != !session->vtnr)
527 return -EINVAL;
528
529 session->seat = s;
530 LIST_PREPEND(sessions_by_seat, s->sessions, session);
531 seat_assign_position(s, session);
532
533 /* On seats with VTs, the VT logic defines which session is active. On
534 * seats without VTs, we automatically activate new sessions. */
535 if (!seat_has_vts(s))
536 seat_set_active(s, session);
537
538 return 0;
539 }
540
541 void seat_complete_switch(Seat *s) {
542 Session *session;
543
544 assert(s);
545
546 /* if no session-switch is pending or if it got canceled, do nothing */
547 if (!s->pending_switch)
548 return;
549
550 session = TAKE_PTR(s->pending_switch);
551
552 seat_set_active(s, session);
553 }
554
555 bool seat_has_vts(Seat *s) {
556 assert(s);
557
558 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
559 }
560
561 bool seat_is_seat0(Seat *s) {
562 assert(s);
563
564 return s->manager->seat0 == s;
565 }
566
567 bool seat_can_multi_session(Seat *s) {
568 assert(s);
569
570 return seat_has_vts(s);
571 }
572
573 bool seat_can_tty(Seat *s) {
574 assert(s);
575
576 return seat_has_vts(s);
577 }
578
579 bool seat_has_master_device(Seat *s) {
580 assert(s);
581
582 /* device list is ordered by "master" flag */
583 return !!s->devices && s->devices->master;
584 }
585
586 bool seat_can_graphical(Seat *s) {
587 assert(s);
588
589 return seat_has_master_device(s);
590 }
591
592 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
593 Session *session;
594 bool idle_hint = true;
595 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
596
597 assert(s);
598
599 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
600 dual_timestamp k;
601 int ih;
602
603 ih = session_get_idle_hint(session, &k);
604 if (ih < 0)
605 return ih;
606
607 if (!ih) {
608 if (!idle_hint) {
609 if (k.monotonic > ts.monotonic)
610 ts = k;
611 } else {
612 idle_hint = false;
613 ts = k;
614 }
615 } else if (idle_hint) {
616
617 if (k.monotonic > ts.monotonic)
618 ts = k;
619 }
620 }
621
622 if (t)
623 *t = ts;
624
625 return idle_hint;
626 }
627
628 bool seat_may_gc(Seat *s, bool drop_not_started) {
629 assert(s);
630
631 if (drop_not_started && !s->started)
632 return true;
633
634 if (seat_is_seat0(s))
635 return false;
636
637 return !seat_has_master_device(s);
638 }
639
640 void seat_add_to_gc_queue(Seat *s) {
641 assert(s);
642
643 if (s->in_gc_queue)
644 return;
645
646 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
647 s->in_gc_queue = true;
648 }
649
650 static bool seat_name_valid_char(char c) {
651 return
652 (c >= 'a' && c <= 'z') ||
653 (c >= 'A' && c <= 'Z') ||
654 (c >= '0' && c <= '9') ||
655 IN_SET(c, '-', '_');
656 }
657
658 bool seat_name_is_valid(const char *name) {
659 const char *p;
660
661 assert(name);
662
663 if (!startswith(name, "seat"))
664 return false;
665
666 if (!name[4])
667 return false;
668
669 for (p = name; *p; p++)
670 if (!seat_name_valid_char(*p))
671 return false;
672
673 if (strlen(name) > 255)
674 return false;
675
676 return true;
677 }