]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-seat.c
util-lib: split string parsing related calls from util.[ch] into parse-util.[ch]
[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
3ffd4af2 29#include "fd-util.h"
07630cea 30#include "formats-util.h"
5eda94dd 31#include "logind-acl.h"
3ffd4af2 32#include "logind-seat.h"
49e942b2 33#include "mkdir.h"
6bedfcbb 34#include "parse-util.h"
07630cea 35#include "string-util.h"
288a74cc 36#include "terminal-util.h"
07630cea 37#include "util.h"
20263082
LP
38
39Seat *seat_new(Manager *m, const char *id) {
40 Seat *s;
41
42 assert(m);
43 assert(id);
44
45 s = new0(Seat, 1);
46 if (!s)
47 return NULL;
48
98a28fef 49 s->state_file = strappend("/run/systemd/seats/", id);
20263082
LP
50 if (!s->state_file) {
51 free(s);
52 return NULL;
53 }
54
2b6bf07d 55 s->id = basename(s->state_file);
14c3baca 56 s->manager = m;
20263082
LP
57
58 if (hashmap_put(m->seats, s->id, s) < 0) {
14c3baca 59 free(s->state_file);
20263082
LP
60 free(s);
61 return NULL;
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
d2e54fae 98 r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
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
14c3baca 106 fchmod(fileno(f), 0644);
20263082
LP
107
108 fprintf(f,
14c3baca 109 "# This is private data. Do not parse.\n"
92432fcc 110 "IS_SEAT0=%i\n"
f1a8e221
LP
111 "CAN_MULTI_SESSION=%i\n"
112 "CAN_TTY=%i\n"
113 "CAN_GRAPHICAL=%i\n",
92432fcc 114 seat_is_seat0(s),
f1a8e221
LP
115 seat_can_multi_session(s),
116 seat_can_tty(s),
117 seat_can_graphical(s));
20263082
LP
118
119 if (s->active) {
120 assert(s->active->user);
121
122 fprintf(f,
123 "ACTIVE=%s\n"
a08ac7e0 124 "ACTIVE_UID="UID_FMT"\n",
20263082 125 s->active->id,
a08ac7e0 126 s->active->user->uid);
20263082
LP
127 }
128
129 if (s->sessions) {
130 Session *i;
20263082 131
fde78a3a 132 fputs("SESSIONS=", f);
20263082 133 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
14c3baca
LP
134 fprintf(f,
135 "%s%c",
136 i->id,
137 i->sessions_by_seat_next ? ' ' : '\n');
138 }
20263082 139
fde78a3a
LP
140 fputs("UIDS=", f);
141 LIST_FOREACH(sessions_by_seat, i, s->sessions)
20263082 142 fprintf(f,
90b2de37
ZJS
143 UID_FMT"%c",
144 i->user->uid,
14c3baca 145 i->sessions_by_seat_next ? ' ' : '\n');
20263082
LP
146 }
147
dacd6cee
LP
148 r = fflush_and_check(f);
149 if (r < 0)
150 goto fail;
14c3baca 151
dacd6cee 152 if (rename(temp_path, s->state_file) < 0) {
20263082 153 r = -errno;
dacd6cee 154 goto fail;
20263082
LP
155 }
156
dacd6cee
LP
157 return 0;
158
159fail:
160 (void) unlink(s->state_file);
161
162 if (temp_path)
163 (void) unlink(temp_path);
14c3baca 164
dacd6cee 165 return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
20263082
LP
166}
167
168int seat_load(Seat *s) {
169 assert(s);
170
a185c5aa
LP
171 /* There isn't actually anything to read here ... */
172
20263082
LP
173 return 0;
174}
175
92bd5ff3 176static int vt_allocate(unsigned int vtnr) {
61376f96 177 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
cc377381 178 _cleanup_close_ int fd = -1;
20263082
LP
179
180 assert(vtnr >= 1);
181
61376f96 182 snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
20263082 183 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
cc377381
LP
184 if (fd < 0)
185 return -errno;
20263082 186
cc377381 187 return 0;
20263082
LP
188}
189
30ed21ce 190int seat_preallocate_vts(Seat *s) {
3f49d45a
LP
191 int r = 0;
192 unsigned i;
20263082
LP
193
194 assert(s);
195 assert(s->manager);
196
30ed21ce
LP
197 log_debug("Preallocating VTs...");
198
20263082
LP
199 if (s->manager->n_autovts <= 0)
200 return 0;
201
bf7825ae 202 if (!seat_has_vts(s))
20263082
LP
203 return 0;
204
e7886786 205 for (i = 1; i <= s->manager->n_autovts; i++) {
20263082
LP
206 int q;
207
208 q = vt_allocate(i);
14c3baca 209 if (q < 0) {
1fa2f38f 210 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
20263082 211 r = q;
14c3baca 212 }
20263082
LP
213 }
214
215 return r;
216}
217
5eda94dd
LP
218int seat_apply_acls(Seat *s, Session *old_active) {
219 int r;
20263082 220
5eda94dd 221 assert(s);
20263082 222
5eda94dd
LP
223 r = devnode_acl_all(s->manager->udev,
224 s->id,
225 false,
226 !!old_active, old_active ? old_active->user->uid : 0,
227 !!s->active, s->active ? s->active->user->uid : 0);
20263082 228
5eda94dd 229 if (r < 0)
da927ba9 230 log_error_errno(r, "Failed to apply ACLs: %m");
20263082
LP
231
232 return r;
233}
234
9418f147
LP
235int seat_set_active(Seat *s, Session *session) {
236 Session *old_active;
237
238 assert(s);
77527da0 239 assert(!session || session->seat == s);
9418f147
LP
240
241 if (session == s->active)
242 return 0;
243
244 old_active = s->active;
245 s->active = session;
246
dfd55270 247 if (old_active) {
118ecf32 248 session_device_pause_all(old_active);
cc377381 249 session_send_changed(old_active, "Active", NULL);
dfd55270 250 }
118ecf32 251
9418f147
LP
252 seat_apply_acls(s, old_active);
253
118ecf32 254 if (session && session->started) {
cc377381 255 session_send_changed(session, "Active", NULL);
118ecf32
DH
256 session_device_resume_all(session);
257 }
9418f147
LP
258
259 if (!session || session->started)
cc377381 260 seat_send_changed(s, "ActiveSession", NULL);
9418f147 261
98a28fef
LP
262 seat_save(s);
263
7f7bb946 264 if (session) {
98a28fef 265 session_save(session);
7f7bb946
LP
266 user_save(session->user);
267 }
98a28fef 268
7f7bb946 269 if (old_active) {
98a28fef 270 session_save(old_active);
78ab361c
CG
271 if (!session || session->user != old_active->user)
272 user_save(old_active->user);
7f7bb946 273 }
98a28fef 274
9418f147
LP
275 return 0;
276}
277
49e6fdbf
DH
278int seat_switch_to(Seat *s, unsigned int num) {
279 /* Public session positions skip 0 (there is only F1-F12). Maybe it
280 * will get reassigned in the future, so return error for now. */
55493982 281 if (num == 0)
49e6fdbf
DH
282 return -EINVAL;
283
15403427
DH
284 if (num >= s->position_count || !s->positions[num]) {
285 /* allow switching to unused VTs to trigger auto-activate */
286 if (seat_has_vts(s) && num < 64)
287 return chvt(num);
288
49e6fdbf 289 return -EINVAL;
15403427 290 }
49e6fdbf
DH
291
292 return session_activate(s->positions[num]);
293}
294
295int seat_switch_to_next(Seat *s) {
296 unsigned int start, i;
297
55493982 298 if (s->position_count == 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
DH
304
305 for (i = start + 1; i < s->position_count; ++i)
306 if (s->positions[i])
307 return session_activate(s->positions[i]);
308
309 for (i = 1; i < start; ++i)
310 if (s->positions[i])
311 return session_activate(s->positions[i]);
312
313 return -EINVAL;
314}
315
316int seat_switch_to_previous(Seat *s) {
317 unsigned int start, i;
318
55493982 319 if (s->position_count == 0)
49e6fdbf
DH
320 return -EINVAL;
321
322 start = 1;
e6494a07
DH
323 if (s->active && s->active->position > 0)
324 start = s->active->position;
49e6fdbf
DH
325
326 for (i = start - 1; i > 0; --i)
327 if (s->positions[i])
328 return session_activate(s->positions[i]);
329
330 for (i = s->position_count - 1; i > start; --i)
331 if (s->positions[i])
332 return session_activate(s->positions[i]);
333
334 return -EINVAL;
335}
336
92bd5ff3 337int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
9418f147
LP
338 Session *i, *new_active = NULL;
339 int r;
20263082
LP
340
341 assert(s);
342 assert(vtnr >= 1);
20263082 343
bf7825ae 344 if (!seat_has_vts(s))
14c3baca
LP
345 return -EINVAL;
346
92bd5ff3 347 log_debug("VT changed to %u", vtnr);
20263082 348
be94d954
MP
349 /* we might have earlier closing sessions on the same VT, so try to
350 * find a running one first */
20263082 351 LIST_FOREACH(sessions_by_seat, i, s->sessions)
be94d954 352 if (i->vtnr == vtnr && !i->stopping) {
14c3baca 353 new_active = i;
20263082
LP
354 break;
355 }
356
be94d954
MP
357 if (!new_active) {
358 /* no running one? then we can't decide which one is the
359 * active one, let the first one win */
360 LIST_FOREACH(sessions_by_seat, i, s->sessions)
361 if (i->vtnr == vtnr) {
362 new_active = i;
363 break;
364 }
365 }
366
9418f147 367 r = seat_set_active(s, new_active);
5eda94dd 368 manager_spawn_autovt(s->manager, vtnr);
20263082 369
9418f147 370 return r;
20263082
LP
371}
372
14c3baca
LP
373int seat_read_active_vt(Seat *s) {
374 char t[64];
375 ssize_t k;
92bd5ff3
DH
376 unsigned int vtnr;
377 int r;
14c3baca
LP
378
379 assert(s);
380
bf7825ae 381 if (!seat_has_vts(s))
14c3baca
LP
382 return 0;
383
384 lseek(s->manager->console_active_fd, SEEK_SET, 0);
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
92bd5ff3 400 r = safe_atou(t+3, &vtnr);
14c3baca
LP
401 if (r < 0) {
402 log_error("Failed to parse VT number %s", t+3);
403 return r;
404 }
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,
e2cc6eca 421 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
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,
e2cc6eca 449 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
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
cc377381 546 seat_send_changed(s, "Sessions", NULL);
9418f147 547
bf7825ae 548 /* On seats with VTs, the VT logic defines which session is active. On
3fdb2494
DH
549 * seats without VTs, we automatically activate new sessions. */
550 if (!seat_has_vts(s))
9418f147 551 seat_set_active(s, session);
9418f147 552
a185c5aa
LP
553 return 0;
554}
555
d7bd01b5
DH
556void seat_complete_switch(Seat *s) {
557 Session *session;
558
559 assert(s);
560
561 /* if no session-switch is pending or if it got canceled, do nothing */
562 if (!s->pending_switch)
563 return;
564
565 session = s->pending_switch;
566 s->pending_switch = NULL;
567
568 seat_set_active(s, session);
569}
570
bf7825ae
DH
571bool seat_has_vts(Seat *s) {
572 assert(s);
573
574 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
575}
576
92432fcc 577bool seat_is_seat0(Seat *s) {
a185c5aa
LP
578 assert(s);
579
92432fcc 580 return s->manager->seat0 == s;
a185c5aa
LP
581}
582
addedec4
LP
583bool seat_can_multi_session(Seat *s) {
584 assert(s);
585
bf7825ae 586 return seat_has_vts(s);
addedec4
LP
587}
588
f1a8e221
LP
589bool seat_can_tty(Seat *s) {
590 assert(s);
591
bf7825ae 592 return seat_has_vts(s);
f1a8e221
LP
593}
594
718d006a
DH
595bool seat_has_master_device(Seat *s) {
596 assert(s);
597
598 /* device list is ordered by "master" flag */
599 return !!s->devices && s->devices->master;
600}
601
f1a8e221
LP
602bool seat_can_graphical(Seat *s) {
603 assert(s);
604
718d006a 605 return seat_has_master_device(s);
f1a8e221
LP
606}
607
a185c5aa
LP
608int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
609 Session *session;
610 bool idle_hint = true;
5cb14b37 611 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
a185c5aa
LP
612
613 assert(s);
614
615 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
616 dual_timestamp k;
617 int ih;
618
619 ih = session_get_idle_hint(session, &k);
620 if (ih < 0)
621 return ih;
622
623 if (!ih) {
624 if (!idle_hint) {
4a271908 625 if (k.monotonic > ts.monotonic)
a185c5aa
LP
626 ts = k;
627 } else {
628 idle_hint = false;
629 ts = k;
630 }
631 } else if (idle_hint) {
632
633 if (k.monotonic > ts.monotonic)
634 ts = k;
635 }
636 }
637
638 if (t)
639 *t = ts;
640
641 return idle_hint;
20263082 642}
14c3baca 643
cc377381 644bool seat_check_gc(Seat *s, bool drop_not_started) {
14c3baca
LP
645 assert(s);
646
4a4b033f 647 if (drop_not_started && !s->started)
cc377381 648 return false;
932e3ee7 649
92432fcc 650 if (seat_is_seat0(s))
cc377381 651 return true;
14c3baca 652
718d006a 653 return seat_has_master_device(s);
14c3baca
LP
654}
655
656void seat_add_to_gc_queue(Seat *s) {
657 assert(s);
658
659 if (s->in_gc_queue)
660 return;
661
71fda00f 662 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
14c3baca
LP
663 s->in_gc_queue = true;
664}
3f49d45a
LP
665
666static bool seat_name_valid_char(char c) {
667 return
668 (c >= 'a' && c <= 'z') ||
669 (c >= 'A' && c <= 'Z') ||
670 (c >= '0' && c <= '9') ||
671 c == '-' ||
672 c == '_';
673}
674
675bool seat_name_is_valid(const char *name) {
676 const char *p;
677
678 assert(name);
679
680 if (!startswith(name, "seat"))
681 return false;
682
683 if (!name[4])
684 return false;
685
686 for (p = name; *p; p++)
687 if (!seat_name_valid_char(*p))
688 return false;
689
1c9a2c10
LP
690 if (strlen(name) > 255)
691 return false;
692
3f49d45a
LP
693 return true;
694}