]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
tree-wide: use mfree more
[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 "formats-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 lseek(s->manager->console_active_fd, SEEK_SET, 0);
383
384 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
385 if (k <= 0) {
386 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
387 return k < 0 ? -errno : -EIO;
388 }
389
390 t[k] = 0;
391 truncate_nl(t);
392
393 if (!startswith(t, "tty")) {
394 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
395 return -EIO;
396 }
397
398 r = safe_atou(t+3, &vtnr);
399 if (r < 0) {
400 log_error("Failed to parse VT number %s", t+3);
401 return r;
402 }
403
404 if (!vtnr) {
405 log_error("VT number invalid: %s", t+3);
406 return -EIO;
407 }
408
409 return seat_active_vt_changed(s, vtnr);
410 }
411
412 int seat_start(Seat *s) {
413 assert(s);
414
415 if (s->started)
416 return 0;
417
418 log_struct(LOG_INFO,
419 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
420 "SEAT_ID=%s", s->id,
421 LOG_MESSAGE("New seat %s.", s->id),
422 NULL);
423
424 /* Initialize VT magic stuff */
425 seat_preallocate_vts(s);
426
427 /* Read current VT */
428 seat_read_active_vt(s);
429
430 s->started = true;
431
432 /* Save seat data */
433 seat_save(s);
434
435 seat_send_signal(s, true);
436
437 return 0;
438 }
439
440 int seat_stop(Seat *s, bool force) {
441 int r = 0;
442
443 assert(s);
444
445 if (s->started)
446 log_struct(LOG_INFO,
447 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
448 "SEAT_ID=%s", s->id,
449 LOG_MESSAGE("Removed seat %s.", s->id),
450 NULL);
451
452 seat_stop_sessions(s, force);
453
454 unlink(s->state_file);
455 seat_add_to_gc_queue(s);
456
457 if (s->started)
458 seat_send_signal(s, false);
459
460 s->started = false;
461
462 return r;
463 }
464
465 int seat_stop_sessions(Seat *s, bool force) {
466 Session *session;
467 int r = 0, k;
468
469 assert(s);
470
471 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
472 k = session_stop(session, force);
473 if (k < 0)
474 r = k;
475 }
476
477 return r;
478 }
479
480 void seat_evict_position(Seat *s, Session *session) {
481 Session *iter;
482 unsigned int pos = session->position;
483
484 session->position = 0;
485
486 if (pos == 0)
487 return;
488
489 if (pos < s->position_count && s->positions[pos] == session) {
490 s->positions[pos] = NULL;
491
492 /* There might be another session claiming the same
493 * position (eg., during gdm->session transition), so let's look
494 * for it and set it on the free slot. */
495 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
496 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
497 s->positions[pos] = iter;
498 break;
499 }
500 }
501 }
502 }
503
504 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
505 /* with VTs, the position is always the same as the VTnr */
506 if (seat_has_vts(s))
507 pos = session->vtnr;
508
509 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
510 return;
511
512 seat_evict_position(s, session);
513
514 session->position = pos;
515 if (pos > 0)
516 s->positions[pos] = session;
517 }
518
519 static void seat_assign_position(Seat *s, Session *session) {
520 unsigned int pos;
521
522 if (session->position > 0)
523 return;
524
525 for (pos = 1; pos < s->position_count; ++pos)
526 if (!s->positions[pos])
527 break;
528
529 seat_claim_position(s, session, pos);
530 }
531
532 int seat_attach_session(Seat *s, Session *session) {
533 assert(s);
534 assert(session);
535 assert(!session->seat);
536
537 if (!seat_has_vts(s) != !session->vtnr)
538 return -EINVAL;
539
540 session->seat = s;
541 LIST_PREPEND(sessions_by_seat, s->sessions, session);
542 seat_assign_position(s, session);
543
544 seat_send_changed(s, "Sessions", NULL);
545
546 /* On seats with VTs, the VT logic defines which session is active. On
547 * seats without VTs, we automatically activate new sessions. */
548 if (!seat_has_vts(s))
549 seat_set_active(s, session);
550
551 return 0;
552 }
553
554 void seat_complete_switch(Seat *s) {
555 Session *session;
556
557 assert(s);
558
559 /* if no session-switch is pending or if it got canceled, do nothing */
560 if (!s->pending_switch)
561 return;
562
563 session = s->pending_switch;
564 s->pending_switch = NULL;
565
566 seat_set_active(s, session);
567 }
568
569 bool seat_has_vts(Seat *s) {
570 assert(s);
571
572 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
573 }
574
575 bool seat_is_seat0(Seat *s) {
576 assert(s);
577
578 return s->manager->seat0 == s;
579 }
580
581 bool seat_can_multi_session(Seat *s) {
582 assert(s);
583
584 return seat_has_vts(s);
585 }
586
587 bool seat_can_tty(Seat *s) {
588 assert(s);
589
590 return seat_has_vts(s);
591 }
592
593 bool seat_has_master_device(Seat *s) {
594 assert(s);
595
596 /* device list is ordered by "master" flag */
597 return !!s->devices && s->devices->master;
598 }
599
600 bool seat_can_graphical(Seat *s) {
601 assert(s);
602
603 return seat_has_master_device(s);
604 }
605
606 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
607 Session *session;
608 bool idle_hint = true;
609 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
610
611 assert(s);
612
613 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
614 dual_timestamp k;
615 int ih;
616
617 ih = session_get_idle_hint(session, &k);
618 if (ih < 0)
619 return ih;
620
621 if (!ih) {
622 if (!idle_hint) {
623 if (k.monotonic > ts.monotonic)
624 ts = k;
625 } else {
626 idle_hint = false;
627 ts = k;
628 }
629 } else if (idle_hint) {
630
631 if (k.monotonic > ts.monotonic)
632 ts = k;
633 }
634 }
635
636 if (t)
637 *t = ts;
638
639 return idle_hint;
640 }
641
642 bool seat_check_gc(Seat *s, bool drop_not_started) {
643 assert(s);
644
645 if (drop_not_started && !s->started)
646 return false;
647
648 if (seat_is_seat0(s))
649 return true;
650
651 return seat_has_master_device(s);
652 }
653
654 void seat_add_to_gc_queue(Seat *s) {
655 assert(s);
656
657 if (s->in_gc_queue)
658 return;
659
660 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
661 s->in_gc_queue = true;
662 }
663
664 static bool seat_name_valid_char(char c) {
665 return
666 (c >= 'a' && c <= 'z') ||
667 (c >= 'A' && c <= 'Z') ||
668 (c >= '0' && c <= '9') ||
669 c == '-' ||
670 c == '_';
671 }
672
673 bool seat_name_is_valid(const char *name) {
674 const char *p;
675
676 assert(name);
677
678 if (!startswith(name, "seat"))
679 return false;
680
681 if (!name[4])
682 return false;
683
684 for (p = name; *p; p++)
685 if (!seat_name_valid_char(*p))
686 return false;
687
688 if (strlen(name) > 255)
689 return false;
690
691 return true;
692 }