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