]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
basic/log: add the log_struct terminator to macro
[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
412 /* Initialize VT magic stuff */
413 seat_preallocate_vts(s);
414
415 /* Read current VT */
416 seat_read_active_vt(s);
417
418 s->started = true;
419
420 /* Save seat data */
421 seat_save(s);
422
423 seat_send_signal(s, true);
424
425 return 0;
426 }
427
428 int seat_stop(Seat *s, bool force) {
429 int r = 0;
430
431 assert(s);
432
433 if (s->started)
434 log_struct(LOG_INFO,
435 "MESSAGE_ID=" SD_MESSAGE_SEAT_STOP_STR,
436 "SEAT_ID=%s", s->id,
437 LOG_MESSAGE("Removed seat %s.", s->id));
438
439 seat_stop_sessions(s, force);
440
441 unlink(s->state_file);
442 seat_add_to_gc_queue(s);
443
444 if (s->started)
445 seat_send_signal(s, false);
446
447 s->started = false;
448
449 return r;
450 }
451
452 int seat_stop_sessions(Seat *s, bool force) {
453 Session *session;
454 int r = 0, k;
455
456 assert(s);
457
458 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
459 k = session_stop(session, force);
460 if (k < 0)
461 r = k;
462 }
463
464 return r;
465 }
466
467 void seat_evict_position(Seat *s, Session *session) {
468 Session *iter;
469 unsigned int pos = session->position;
470
471 session->position = 0;
472
473 if (pos == 0)
474 return;
475
476 if (pos < s->position_count && s->positions[pos] == session) {
477 s->positions[pos] = NULL;
478
479 /* There might be another session claiming the same
480 * position (eg., during gdm->session transition), so let's look
481 * for it and set it on the free slot. */
482 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
483 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
484 s->positions[pos] = iter;
485 break;
486 }
487 }
488 }
489 }
490
491 void 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
496 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
497 return;
498
499 seat_evict_position(s, session);
500
501 session->position = pos;
502 if (pos > 0)
503 s->positions[pos] = session;
504 }
505
506 static void seat_assign_position(Seat *s, Session *session) {
507 unsigned int pos;
508
509 if (session->position > 0)
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
519 int seat_attach_session(Seat *s, Session *session) {
520 assert(s);
521 assert(session);
522 assert(!session->seat);
523
524 if (!seat_has_vts(s) != !session->vtnr)
525 return -EINVAL;
526
527 session->seat = s;
528 LIST_PREPEND(sessions_by_seat, s->sessions, session);
529 seat_assign_position(s, session);
530
531 /* On seats with VTs, the VT logic defines which session is active. On
532 * seats without VTs, we automatically activate new sessions. */
533 if (!seat_has_vts(s))
534 seat_set_active(s, session);
535
536 return 0;
537 }
538
539 void 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
548 session = TAKE_PTR(s->pending_switch);
549
550 seat_set_active(s, session);
551 }
552
553 bool seat_has_vts(Seat *s) {
554 assert(s);
555
556 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
557 }
558
559 bool seat_is_seat0(Seat *s) {
560 assert(s);
561
562 return s->manager->seat0 == s;
563 }
564
565 bool seat_can_multi_session(Seat *s) {
566 assert(s);
567
568 return seat_has_vts(s);
569 }
570
571 bool seat_can_tty(Seat *s) {
572 assert(s);
573
574 return seat_has_vts(s);
575 }
576
577 bool 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
584 bool seat_can_graphical(Seat *s) {
585 assert(s);
586
587 return seat_has_master_device(s);
588 }
589
590 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
591 Session *session;
592 bool idle_hint = true;
593 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
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) {
607 if (k.monotonic > ts.monotonic)
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;
624 }
625
626 bool seat_may_gc(Seat *s, bool drop_not_started) {
627 assert(s);
628
629 if (drop_not_started && !s->started)
630 return true;
631
632 if (seat_is_seat0(s))
633 return false;
634
635 return !seat_has_master_device(s);
636 }
637
638 void seat_add_to_gc_queue(Seat *s) {
639 assert(s);
640
641 if (s->in_gc_queue)
642 return;
643
644 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
645 s->in_gc_queue = true;
646 }
647
648 static bool seat_name_valid_char(char c) {
649 return
650 (c >= 'a' && c <= 'z') ||
651 (c >= 'A' && c <= 'Z') ||
652 (c >= '0' && c <= '9') ||
653 IN_SET(c, '-', '_');
654 }
655
656 bool 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
671 if (strlen(name) > 255)
672 return false;
673
674 return true;
675 }