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