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