]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
Merge pull request #502 from keszybz/login-small-cleanup
[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 finish;
97
98 r = fopen_temporary(s->state_file, &f, &temp_path);
99 if (r < 0)
100 goto finish;
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 fflush(f);
145
146 if (ferror(f) || rename(temp_path, s->state_file) < 0) {
147 r = -errno;
148 unlink(s->state_file);
149 unlink(temp_path);
150 }
151
152 finish:
153 if (r < 0)
154 log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
155
156 return r;
157 }
158
159 int seat_load(Seat *s) {
160 assert(s);
161
162 /* There isn't actually anything to read here ... */
163
164 return 0;
165 }
166
167 static int vt_allocate(unsigned int vtnr) {
168 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
169 _cleanup_close_ int fd = -1;
170
171 assert(vtnr >= 1);
172
173 snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
174 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
175 if (fd < 0)
176 return -errno;
177
178 return 0;
179 }
180
181 int seat_preallocate_vts(Seat *s) {
182 int r = 0;
183 unsigned i;
184
185 assert(s);
186 assert(s->manager);
187
188 log_debug("Preallocating VTs...");
189
190 if (s->manager->n_autovts <= 0)
191 return 0;
192
193 if (!seat_has_vts(s))
194 return 0;
195
196 for (i = 1; i <= s->manager->n_autovts; i++) {
197 int q;
198
199 q = vt_allocate(i);
200 if (q < 0) {
201 log_error_errno(q, "Failed to preallocate VT %u: %m", i);
202 r = q;
203 }
204 }
205
206 return r;
207 }
208
209 int seat_apply_acls(Seat *s, Session *old_active) {
210 int r;
211
212 assert(s);
213
214 r = devnode_acl_all(s->manager->udev,
215 s->id,
216 false,
217 !!old_active, old_active ? old_active->user->uid : 0,
218 !!s->active, s->active ? s->active->user->uid : 0);
219
220 if (r < 0)
221 log_error_errno(r, "Failed to apply ACLs: %m");
222
223 return r;
224 }
225
226 int seat_set_active(Seat *s, Session *session) {
227 Session *old_active;
228
229 assert(s);
230 assert(!session || session->seat == s);
231
232 if (session == s->active)
233 return 0;
234
235 old_active = s->active;
236 s->active = session;
237
238 if (old_active) {
239 session_device_pause_all(old_active);
240 session_send_changed(old_active, "Active", NULL);
241 }
242
243 seat_apply_acls(s, old_active);
244
245 if (session && session->started) {
246 session_send_changed(session, "Active", NULL);
247 session_device_resume_all(session);
248 }
249
250 if (!session || session->started)
251 seat_send_changed(s, "ActiveSession", NULL);
252
253 seat_save(s);
254
255 if (session) {
256 session_save(session);
257 user_save(session->user);
258 }
259
260 if (old_active) {
261 session_save(old_active);
262 if (!session || session->user != old_active->user)
263 user_save(old_active->user);
264 }
265
266 return 0;
267 }
268
269 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 if (num == 0)
273 return -EINVAL;
274
275 if (num >= s->position_count || !s->positions[num]) {
276 /* allow switching to unused VTs to trigger auto-activate */
277 if (seat_has_vts(s) && num < 64)
278 return chvt(num);
279
280 return -EINVAL;
281 }
282
283 return session_activate(s->positions[num]);
284 }
285
286 int seat_switch_to_next(Seat *s) {
287 unsigned int start, i;
288
289 if (s->position_count == 0)
290 return -EINVAL;
291
292 start = 1;
293 if (s->active && s->active->pos > 0)
294 start = s->active->pos;
295
296 for (i = start + 1; i < s->position_count; ++i)
297 if (s->positions[i])
298 return session_activate(s->positions[i]);
299
300 for (i = 1; i < start; ++i)
301 if (s->positions[i])
302 return session_activate(s->positions[i]);
303
304 return -EINVAL;
305 }
306
307 int seat_switch_to_previous(Seat *s) {
308 unsigned int start, i;
309
310 if (s->position_count == 0)
311 return -EINVAL;
312
313 start = 1;
314 if (s->active && s->active->pos > 0)
315 start = s->active->pos;
316
317 for (i = start - 1; i > 0; --i)
318 if (s->positions[i])
319 return session_activate(s->positions[i]);
320
321 for (i = s->position_count - 1; i > start; --i)
322 if (s->positions[i])
323 return session_activate(s->positions[i]);
324
325 return -EINVAL;
326 }
327
328 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
329 Session *i, *new_active = NULL;
330 int r;
331
332 assert(s);
333 assert(vtnr >= 1);
334
335 if (!seat_has_vts(s))
336 return -EINVAL;
337
338 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 LIST_FOREACH(sessions_by_seat, i, s->sessions)
343 if (i->vtnr == vtnr && !i->stopping) {
344 new_active = i;
345 break;
346 }
347
348 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 LIST_FOREACH(sessions_by_seat, i, s->sessions)
352 if (i->vtnr == vtnr) {
353 new_active = i;
354 break;
355 }
356 }
357
358 r = seat_set_active(s, new_active);
359 manager_spawn_autovt(s->manager, vtnr);
360
361 return r;
362 }
363
364 int seat_read_active_vt(Seat *s) {
365 char t[64];
366 ssize_t k;
367 unsigned int vtnr;
368 int r;
369
370 assert(s);
371
372 if (!seat_has_vts(s))
373 return 0;
374
375 lseek(s->manager->console_active_fd, SEEK_SET, 0);
376
377 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
378 if (k <= 0) {
379 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
380 return k < 0 ? -errno : -EIO;
381 }
382
383 t[k] = 0;
384 truncate_nl(t);
385
386 if (!startswith(t, "tty")) {
387 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
388 return -EIO;
389 }
390
391 r = safe_atou(t+3, &vtnr);
392 if (r < 0) {
393 log_error("Failed to parse VT number %s", t+3);
394 return r;
395 }
396
397 if (!vtnr) {
398 log_error("VT number invalid: %s", t+3);
399 return -EIO;
400 }
401
402 return seat_active_vt_changed(s, vtnr);
403 }
404
405 int seat_start(Seat *s) {
406 assert(s);
407
408 if (s->started)
409 return 0;
410
411 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 seat_preallocate_vts(s);
419
420 /* Read current VT */
421 seat_read_active_vt(s);
422
423 s->started = true;
424
425 /* Save seat data */
426 seat_save(s);
427
428 seat_send_signal(s, true);
429
430 return 0;
431 }
432
433 int seat_stop(Seat *s, bool force) {
434 int r = 0;
435
436 assert(s);
437
438 if (s->started)
439 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 seat_stop_sessions(s, force);
446
447 unlink(s->state_file);
448 seat_add_to_gc_queue(s);
449
450 if (s->started)
451 seat_send_signal(s, false);
452
453 s->started = false;
454
455 return r;
456 }
457
458 int seat_stop_sessions(Seat *s, bool force) {
459 Session *session;
460 int r = 0, k;
461
462 assert(s);
463
464 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
465 k = session_stop(session, force);
466 if (k < 0)
467 r = k;
468 }
469
470 return r;
471 }
472
473 void seat_evict_position(Seat *s, Session *session) {
474 Session *iter;
475 unsigned int pos = session->pos;
476
477 session->pos = 0;
478
479 if (pos == 0)
480 return;
481
482 if (pos < s->position_count && s->positions[pos] == session) {
483 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 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
489 if (iter->pos == pos) {
490 s->positions[pos] = iter;
491 break;
492 }
493 }
494 }
495 }
496
497 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
498 /* with VTs, the position is always the same as the VTnr */
499 if (seat_has_vts(s))
500 pos = session->vtnr;
501
502 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
503 return;
504
505 seat_evict_position(s, session);
506
507 session->pos = pos;
508 if (pos > 0 && !s->positions[pos])
509 s->positions[pos] = session;
510 }
511
512 static void seat_assign_position(Seat *s, Session *session) {
513 unsigned int pos;
514
515 if (session->pos > 0)
516 return;
517
518 for (pos = 1; pos < s->position_count; ++pos)
519 if (!s->positions[pos])
520 break;
521
522 seat_claim_position(s, session, pos);
523 }
524
525 int seat_attach_session(Seat *s, Session *session) {
526 assert(s);
527 assert(session);
528 assert(!session->seat);
529
530 if (!seat_has_vts(s) != !session->vtnr)
531 return -EINVAL;
532
533 session->seat = s;
534 LIST_PREPEND(sessions_by_seat, s->sessions, session);
535 seat_assign_position(s, session);
536
537 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 if (!seat_has_vts(s))
542 seat_set_active(s, session);
543
544 return 0;
545 }
546
547 void seat_complete_switch(Seat *s) {
548 Session *session;
549
550 assert(s);
551
552 /* if no session-switch is pending or if it got canceled, do nothing */
553 if (!s->pending_switch)
554 return;
555
556 session = s->pending_switch;
557 s->pending_switch = NULL;
558
559 seat_set_active(s, session);
560 }
561
562 bool seat_has_vts(Seat *s) {
563 assert(s);
564
565 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
566 }
567
568 bool seat_is_seat0(Seat *s) {
569 assert(s);
570
571 return s->manager->seat0 == s;
572 }
573
574 bool seat_can_multi_session(Seat *s) {
575 assert(s);
576
577 return seat_has_vts(s);
578 }
579
580 bool seat_can_tty(Seat *s) {
581 assert(s);
582
583 return seat_has_vts(s);
584 }
585
586 bool seat_has_master_device(Seat *s) {
587 assert(s);
588
589 /* device list is ordered by "master" flag */
590 return !!s->devices && s->devices->master;
591 }
592
593 bool seat_can_graphical(Seat *s) {
594 assert(s);
595
596 return seat_has_master_device(s);
597 }
598
599 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
600 Session *session;
601 bool idle_hint = true;
602 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
603
604 assert(s);
605
606 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
607 dual_timestamp k;
608 int ih;
609
610 ih = session_get_idle_hint(session, &k);
611 if (ih < 0)
612 return ih;
613
614 if (!ih) {
615 if (!idle_hint) {
616 if (k.monotonic > ts.monotonic)
617 ts = k;
618 } else {
619 idle_hint = false;
620 ts = k;
621 }
622 } else if (idle_hint) {
623
624 if (k.monotonic > ts.monotonic)
625 ts = k;
626 }
627 }
628
629 if (t)
630 *t = ts;
631
632 return idle_hint;
633 }
634
635 bool seat_check_gc(Seat *s, bool drop_not_started) {
636 assert(s);
637
638 if (drop_not_started && !s->started)
639 return false;
640
641 if (seat_is_seat0(s))
642 return true;
643
644 return seat_has_master_device(s);
645 }
646
647 void seat_add_to_gc_queue(Seat *s) {
648 assert(s);
649
650 if (s->in_gc_queue)
651 return;
652
653 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
654 s->in_gc_queue = true;
655 }
656
657 static bool seat_name_valid_char(char c) {
658 return
659 (c >= 'a' && c <= 'z') ||
660 (c >= 'A' && c <= 'Z') ||
661 (c >= '0' && c <= '9') ||
662 c == '-' ||
663 c == '_';
664 }
665
666 bool seat_name_is_valid(const char *name) {
667 const char *p;
668
669 assert(name);
670
671 if (!startswith(name, "seat"))
672 return false;
673
674 if (!name[4])
675 return false;
676
677 for (p = name; *p; p++)
678 if (!seat_name_valid_char(*p))
679 return false;
680
681 if (strlen(name) > 255)
682 return false;
683
684 return true;
685 }