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