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