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