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