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