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