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