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