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