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