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