]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/login/logind-seat.c
Merge pull request #2265 from ipuustin/ambient
[thirdparty/systemd.git] / src / login / logind-seat.c
... / ...
CommitLineData
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
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
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
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <errno.h>
23#include <fcntl.h>
24#include <string.h>
25#include <unistd.h>
26
27#include "sd-messages.h"
28
29#include "alloc-util.h"
30#include "fd-util.h"
31#include "fileio.h"
32#include "formats-util.h"
33#include "logind-acl.h"
34#include "logind-seat.h"
35#include "mkdir.h"
36#include "parse-util.h"
37#include "string-util.h"
38#include "terminal-util.h"
39#include "util.h"
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
51 s->state_file = strappend("/run/systemd/seats/", id);
52 if (!s->state_file) {
53 free(s);
54 return NULL;
55 }
56
57 s->id = basename(s->state_file);
58 s->manager = m;
59
60 if (hashmap_put(m->seats, s->id, s) < 0) {
61 free(s->state_file);
62 free(s);
63 return NULL;
64 }
65
66 return s;
67}
68
69void seat_free(Seat *s) {
70 assert(s);
71
72 if (s->in_gc_queue)
73 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
74
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
85 free(s->positions);
86 free(s->state_file);
87 free(s);
88}
89
90int seat_save(Seat *s) {
91 _cleanup_free_ char *temp_path = NULL;
92 _cleanup_fclose_ FILE *f = NULL;
93 int r;
94
95 assert(s);
96
97 if (!s->started)
98 return 0;
99
100 r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
101 if (r < 0)
102 goto fail;
103
104 r = fopen_temporary(s->state_file, &f, &temp_path);
105 if (r < 0)
106 goto fail;
107
108 fchmod(fileno(f), 0644);
109
110 fprintf(f,
111 "# This is private data. Do not parse.\n"
112 "IS_SEAT0=%i\n"
113 "CAN_MULTI_SESSION=%i\n"
114 "CAN_TTY=%i\n"
115 "CAN_GRAPHICAL=%i\n",
116 seat_is_seat0(s),
117 seat_can_multi_session(s),
118 seat_can_tty(s),
119 seat_can_graphical(s));
120
121 if (s->active) {
122 assert(s->active->user);
123
124 fprintf(f,
125 "ACTIVE=%s\n"
126 "ACTIVE_UID="UID_FMT"\n",
127 s->active->id,
128 s->active->user->uid);
129 }
130
131 if (s->sessions) {
132 Session *i;
133
134 fputs("SESSIONS=", f);
135 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
136 fprintf(f,
137 "%s%c",
138 i->id,
139 i->sessions_by_seat_next ? ' ' : '\n');
140 }
141
142 fputs("UIDS=", f);
143 LIST_FOREACH(sessions_by_seat, i, s->sessions)
144 fprintf(f,
145 UID_FMT"%c",
146 i->user->uid,
147 i->sessions_by_seat_next ? ' ' : '\n');
148 }
149
150 r = fflush_and_check(f);
151 if (r < 0)
152 goto fail;
153
154 if (rename(temp_path, s->state_file) < 0) {
155 r = -errno;
156 goto fail;
157 }
158
159 return 0;
160
161fail:
162 (void) unlink(s->state_file);
163
164 if (temp_path)
165 (void) unlink(temp_path);
166
167 return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
168}
169
170int seat_load(Seat *s) {
171 assert(s);
172
173 /* There isn't actually anything to read here ... */
174
175 return 0;
176}
177
178static int vt_allocate(unsigned int vtnr) {
179 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
180 _cleanup_close_ int fd = -1;
181
182 assert(vtnr >= 1);
183
184 snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
185 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
186 if (fd < 0)
187 return -errno;
188
189 return 0;
190}
191
192int seat_preallocate_vts(Seat *s) {
193 int r = 0;
194 unsigned i;
195
196 assert(s);
197 assert(s->manager);
198
199 log_debug("Preallocating VTs...");
200
201 if (s->manager->n_autovts <= 0)
202 return 0;
203
204 if (!seat_has_vts(s))
205 return 0;
206
207 for (i = 1; i <= s->manager->n_autovts; i++) {
208 int q;
209
210 q = vt_allocate(i);
211 if (q < 0) {
212 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
213 r = q;
214 }
215 }
216
217 return r;
218}
219
220int seat_apply_acls(Seat *s, Session *old_active) {
221 int r;
222
223 assert(s);
224
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);
230
231 if (r < 0)
232 log_error_errno(r, "Failed to apply ACLs: %m");
233
234 return r;
235}
236
237int seat_set_active(Seat *s, Session *session) {
238 Session *old_active;
239
240 assert(s);
241 assert(!session || session->seat == s);
242
243 if (session == s->active)
244 return 0;
245
246 old_active = s->active;
247 s->active = session;
248
249 if (old_active) {
250 session_device_pause_all(old_active);
251 session_send_changed(old_active, "Active", NULL);
252 }
253
254 seat_apply_acls(s, old_active);
255
256 if (session && session->started) {
257 session_send_changed(session, "Active", NULL);
258 session_device_resume_all(session);
259 }
260
261 if (!session || session->started)
262 seat_send_changed(s, "ActiveSession", NULL);
263
264 seat_save(s);
265
266 if (session) {
267 session_save(session);
268 user_save(session->user);
269 }
270
271 if (old_active) {
272 session_save(old_active);
273 if (!session || session->user != old_active->user)
274 user_save(old_active->user);
275 }
276
277 return 0;
278}
279
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. */
283 if (num == 0)
284 return -EINVAL;
285
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
291 return -EINVAL;
292 }
293
294 return session_activate(s->positions[num]);
295}
296
297int seat_switch_to_next(Seat *s) {
298 unsigned int start, i;
299
300 if (s->position_count == 0)
301 return -EINVAL;
302
303 start = 1;
304 if (s->active && s->active->position > 0)
305 start = s->active->position;
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
321 if (s->position_count == 0)
322 return -EINVAL;
323
324 start = 1;
325 if (s->active && s->active->position > 0)
326 start = s->active->position;
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
339int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
340 Session *i, *new_active = NULL;
341 int r;
342
343 assert(s);
344 assert(vtnr >= 1);
345
346 if (!seat_has_vts(s))
347 return -EINVAL;
348
349 log_debug("VT changed to %u", vtnr);
350
351 /* we might have earlier closing sessions on the same VT, so try to
352 * find a running one first */
353 LIST_FOREACH(sessions_by_seat, i, s->sessions)
354 if (i->vtnr == vtnr && !i->stopping) {
355 new_active = i;
356 break;
357 }
358
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
369 r = seat_set_active(s, new_active);
370 manager_spawn_autovt(s->manager, vtnr);
371
372 return r;
373}
374
375int seat_read_active_vt(Seat *s) {
376 char t[64];
377 ssize_t k;
378 unsigned int vtnr;
379 int r;
380
381 assert(s);
382
383 if (!seat_has_vts(s))
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
402 r = safe_atou(t+3, &vtnr);
403 if (r < 0) {
404 log_error("Failed to parse VT number %s", t+3);
405 return r;
406 }
407
408 if (!vtnr) {
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
419 if (s->started)
420 return 0;
421
422 log_struct(LOG_INFO,
423 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
424 "SEAT_ID=%s", s->id,
425 LOG_MESSAGE("New seat %s.", s->id),
426 NULL);
427
428 /* Initialize VT magic stuff */
429 seat_preallocate_vts(s);
430
431 /* Read current VT */
432 seat_read_active_vt(s);
433
434 s->started = true;
435
436 /* Save seat data */
437 seat_save(s);
438
439 seat_send_signal(s, true);
440
441 return 0;
442}
443
444int seat_stop(Seat *s, bool force) {
445 int r = 0;
446
447 assert(s);
448
449 if (s->started)
450 log_struct(LOG_INFO,
451 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
452 "SEAT_ID=%s", s->id,
453 LOG_MESSAGE("Removed seat %s.", s->id),
454 NULL);
455
456 seat_stop_sessions(s, force);
457
458 unlink(s->state_file);
459 seat_add_to_gc_queue(s);
460
461 if (s->started)
462 seat_send_signal(s, false);
463
464 s->started = false;
465
466 return r;
467}
468
469int seat_stop_sessions(Seat *s, bool force) {
470 Session *session;
471 int r = 0, k;
472
473 assert(s);
474
475 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
476 k = session_stop(session, force);
477 if (k < 0)
478 r = k;
479 }
480
481 return r;
482}
483
484void seat_evict_position(Seat *s, Session *session) {
485 Session *iter;
486 unsigned int pos = session->position;
487
488 session->position = 0;
489
490 if (pos == 0)
491 return;
492
493 if (pos < s->position_count && s->positions[pos] == session) {
494 s->positions[pos] = NULL;
495
496 /* There might be another session claiming the same
497 * position (eg., during gdm->session transition), so let's look
498 * for it and set it on the free slot. */
499 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
500 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
501 s->positions[pos] = iter;
502 break;
503 }
504 }
505 }
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
513 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
514 return;
515
516 seat_evict_position(s, session);
517
518 session->position = pos;
519 if (pos > 0)
520 s->positions[pos] = session;
521}
522
523static void seat_assign_position(Seat *s, Session *session) {
524 unsigned int pos;
525
526 if (session->position > 0)
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
536int seat_attach_session(Seat *s, Session *session) {
537 assert(s);
538 assert(session);
539 assert(!session->seat);
540
541 if (!seat_has_vts(s) != !session->vtnr)
542 return -EINVAL;
543
544 session->seat = s;
545 LIST_PREPEND(sessions_by_seat, s->sessions, session);
546 seat_assign_position(s, session);
547
548 seat_send_changed(s, "Sessions", NULL);
549
550 /* On seats with VTs, the VT logic defines which session is active. On
551 * seats without VTs, we automatically activate new sessions. */
552 if (!seat_has_vts(s))
553 seat_set_active(s, session);
554
555 return 0;
556}
557
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
573bool seat_has_vts(Seat *s) {
574 assert(s);
575
576 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
577}
578
579bool seat_is_seat0(Seat *s) {
580 assert(s);
581
582 return s->manager->seat0 == s;
583}
584
585bool seat_can_multi_session(Seat *s) {
586 assert(s);
587
588 return seat_has_vts(s);
589}
590
591bool seat_can_tty(Seat *s) {
592 assert(s);
593
594 return seat_has_vts(s);
595}
596
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
604bool seat_can_graphical(Seat *s) {
605 assert(s);
606
607 return seat_has_master_device(s);
608}
609
610int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
611 Session *session;
612 bool idle_hint = true;
613 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
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) {
627 if (k.monotonic > ts.monotonic)
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;
644}
645
646bool seat_check_gc(Seat *s, bool drop_not_started) {
647 assert(s);
648
649 if (drop_not_started && !s->started)
650 return false;
651
652 if (seat_is_seat0(s))
653 return true;
654
655 return seat_has_master_device(s);
656}
657
658void seat_add_to_gc_queue(Seat *s) {
659 assert(s);
660
661 if (s->in_gc_queue)
662 return;
663
664 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
665 s->in_gc_queue = true;
666}
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
692 if (strlen(name) > 255)
693 return false;
694
695 return true;
696}