]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
logind: always kill session when termination is requested
[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=%lu\n",
124 s->active->id,
125 (unsigned long) 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("Failed to save seat data %s: %s", s->state_file, strerror(-r));
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 _cleanup_free_ char *p = NULL;
172 _cleanup_close_ int fd = -1;
173
174 assert(vtnr >= 1);
175
176 if (asprintf(&p, "/dev/tty%u", vtnr) < 0)
177 return -ENOMEM;
178
179 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
180 if (fd < 0)
181 return -errno;
182
183 return 0;
184 }
185
186 int seat_preallocate_vts(Seat *s) {
187 int r = 0;
188 unsigned i;
189
190 assert(s);
191 assert(s->manager);
192
193 log_debug("Preallocating VTs...");
194
195 if (s->manager->n_autovts <= 0)
196 return 0;
197
198 if (!seat_has_vts(s))
199 return 0;
200
201 for (i = 1; i <= s->manager->n_autovts; i++) {
202 int q;
203
204 q = vt_allocate(i);
205 if (q < 0) {
206 log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
207 r = q;
208 }
209 }
210
211 return r;
212 }
213
214 int seat_apply_acls(Seat *s, Session *old_active) {
215 int r;
216
217 assert(s);
218
219 r = devnode_acl_all(s->manager->udev,
220 s->id,
221 false,
222 !!old_active, old_active ? old_active->user->uid : 0,
223 !!s->active, s->active ? s->active->user->uid : 0);
224
225 if (r < 0)
226 log_error("Failed to apply ACLs: %s", strerror(-r));
227
228 return r;
229 }
230
231 int seat_set_active(Seat *s, Session *session) {
232 Session *old_active;
233
234 assert(s);
235 assert(!session || session->seat == s);
236
237 if (session == s->active)
238 return 0;
239
240 old_active = s->active;
241 s->active = session;
242
243 if (old_active) {
244 session_device_pause_all(old_active);
245 session_send_changed(old_active, "Active", NULL);
246 }
247
248 seat_apply_acls(s, old_active);
249
250 if (session && session->started) {
251 session_send_changed(session, "Active", NULL);
252 session_device_resume_all(session);
253 }
254
255 if (!session || session->started)
256 seat_send_changed(s, "ActiveSession", NULL);
257
258 seat_save(s);
259
260 if (session) {
261 session_save(session);
262 user_save(session->user);
263 }
264
265 if (old_active) {
266 session_save(old_active);
267 if (!session || session->user != old_active->user)
268 user_save(old_active->user);
269 }
270
271 return 0;
272 }
273
274 int seat_switch_to(Seat *s, unsigned int num) {
275 /* Public session positions skip 0 (there is only F1-F12). Maybe it
276 * will get reassigned in the future, so return error for now. */
277 if (!num)
278 return -EINVAL;
279
280 if (num >= s->position_count || !s->positions[num])
281 return -EINVAL;
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)
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)
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 LIST_FOREACH(sessions_by_seat, i, s->sessions)
341 if (i->vtnr == vtnr) {
342 new_active = i;
343 break;
344 }
345
346 r = seat_set_active(s, new_active);
347 manager_spawn_autovt(s->manager, vtnr);
348
349 return r;
350 }
351
352 int seat_read_active_vt(Seat *s) {
353 char t[64];
354 ssize_t k;
355 unsigned int vtnr;
356 int r;
357
358 assert(s);
359
360 if (!seat_has_vts(s))
361 return 0;
362
363 lseek(s->manager->console_active_fd, SEEK_SET, 0);
364
365 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
366 if (k <= 0) {
367 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
368 return k < 0 ? -errno : -EIO;
369 }
370
371 t[k] = 0;
372 truncate_nl(t);
373
374 if (!startswith(t, "tty")) {
375 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
376 return -EIO;
377 }
378
379 r = safe_atou(t+3, &vtnr);
380 if (r < 0) {
381 log_error("Failed to parse VT number %s", t+3);
382 return r;
383 }
384
385 if (!vtnr) {
386 log_error("VT number invalid: %s", t+3);
387 return -EIO;
388 }
389
390 return seat_active_vt_changed(s, vtnr);
391 }
392
393 int seat_start(Seat *s) {
394 assert(s);
395
396 if (s->started)
397 return 0;
398
399 log_struct(LOG_INFO,
400 MESSAGE_ID(SD_MESSAGE_SEAT_START),
401 "SEAT_ID=%s", s->id,
402 "MESSAGE=New seat %s.", s->id,
403 NULL);
404
405 /* Initialize VT magic stuff */
406 seat_preallocate_vts(s);
407
408 /* Read current VT */
409 seat_read_active_vt(s);
410
411 s->started = true;
412
413 /* Save seat data */
414 seat_save(s);
415
416 seat_send_signal(s, true);
417
418 return 0;
419 }
420
421 int seat_stop(Seat *s, bool force) {
422 int r = 0;
423
424 assert(s);
425
426 if (s->started)
427 log_struct(LOG_INFO,
428 MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
429 "SEAT_ID=%s", s->id,
430 "MESSAGE=Removed seat %s.", s->id,
431 NULL);
432
433 seat_stop_sessions(s, force);
434
435 unlink(s->state_file);
436 seat_add_to_gc_queue(s);
437
438 if (s->started)
439 seat_send_signal(s, false);
440
441 s->started = false;
442
443 return r;
444 }
445
446 int seat_stop_sessions(Seat *s, bool force) {
447 Session *session;
448 int r = 0, k;
449
450 assert(s);
451
452 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
453 k = session_stop(session, force);
454 if (k < 0)
455 r = k;
456 }
457
458 return r;
459 }
460
461 void seat_evict_position(Seat *s, Session *session) {
462 unsigned int pos = session->pos;
463
464 session->pos = 0;
465
466 if (!pos)
467 return;
468
469 if (pos < s->position_count && s->positions[pos] == session)
470 s->positions[pos] = NULL;
471 }
472
473 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
474 /* with VTs, the position is always the same as the VTnr */
475 if (seat_has_vts(s))
476 pos = session->vtnr;
477
478 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
479 return;
480
481 seat_evict_position(s, session);
482
483 session->pos = pos;
484 if (pos > 0 && !s->positions[pos])
485 s->positions[pos] = session;
486 }
487
488 static void seat_assign_position(Seat *s, Session *session) {
489 unsigned int pos;
490
491 if (session->pos > 0)
492 return;
493
494 for (pos = 1; pos < s->position_count; ++pos)
495 if (!s->positions[pos])
496 break;
497
498 seat_claim_position(s, session, pos);
499 }
500
501 int seat_attach_session(Seat *s, Session *session) {
502 assert(s);
503 assert(session);
504 assert(!session->seat);
505
506 if (!seat_has_vts(s) != !session->vtnr)
507 return -EINVAL;
508
509 session->seat = s;
510 LIST_PREPEND(sessions_by_seat, s->sessions, session);
511 seat_assign_position(s, session);
512
513 seat_send_changed(s, "Sessions", NULL);
514
515 /* On seats with VTs, the VT logic defines which session is active. On
516 * seats without VTs, we automatically activate new sessions. */
517 if (!seat_has_vts(s))
518 seat_set_active(s, session);
519
520 return 0;
521 }
522
523 void seat_complete_switch(Seat *s) {
524 Session *session;
525
526 assert(s);
527
528 /* if no session-switch is pending or if it got canceled, do nothing */
529 if (!s->pending_switch)
530 return;
531
532 session = s->pending_switch;
533 s->pending_switch = NULL;
534
535 seat_set_active(s, session);
536 }
537
538 bool seat_has_vts(Seat *s) {
539 assert(s);
540
541 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
542 }
543
544 bool seat_is_seat0(Seat *s) {
545 assert(s);
546
547 return s->manager->seat0 == s;
548 }
549
550 bool seat_can_multi_session(Seat *s) {
551 assert(s);
552
553 return seat_has_vts(s);
554 }
555
556 bool seat_can_tty(Seat *s) {
557 assert(s);
558
559 return seat_has_vts(s);
560 }
561
562 bool seat_has_master_device(Seat *s) {
563 assert(s);
564
565 /* device list is ordered by "master" flag */
566 return !!s->devices && s->devices->master;
567 }
568
569 bool seat_can_graphical(Seat *s) {
570 assert(s);
571
572 return seat_has_master_device(s);
573 }
574
575 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
576 Session *session;
577 bool idle_hint = true;
578 dual_timestamp ts = { 0, 0 };
579
580 assert(s);
581
582 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
583 dual_timestamp k;
584 int ih;
585
586 ih = session_get_idle_hint(session, &k);
587 if (ih < 0)
588 return ih;
589
590 if (!ih) {
591 if (!idle_hint) {
592 if (k.monotonic > ts.monotonic)
593 ts = k;
594 } else {
595 idle_hint = false;
596 ts = k;
597 }
598 } else if (idle_hint) {
599
600 if (k.monotonic > ts.monotonic)
601 ts = k;
602 }
603 }
604
605 if (t)
606 *t = ts;
607
608 return idle_hint;
609 }
610
611 bool seat_check_gc(Seat *s, bool drop_not_started) {
612 assert(s);
613
614 if (drop_not_started && !s->started)
615 return false;
616
617 if (seat_is_seat0(s))
618 return true;
619
620 return seat_has_master_device(s);
621 }
622
623 void seat_add_to_gc_queue(Seat *s) {
624 assert(s);
625
626 if (s->in_gc_queue)
627 return;
628
629 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
630 s->in_gc_queue = true;
631 }
632
633 static bool seat_name_valid_char(char c) {
634 return
635 (c >= 'a' && c <= 'z') ||
636 (c >= 'A' && c <= 'Z') ||
637 (c >= '0' && c <= '9') ||
638 c == '-' ||
639 c == '_';
640 }
641
642 bool seat_name_is_valid(const char *name) {
643 const char *p;
644
645 assert(name);
646
647 if (!startswith(name, "seat"))
648 return false;
649
650 if (!name[4])
651 return false;
652
653 for (p = name; *p; p++)
654 if (!seat_name_valid_char(*p))
655 return false;
656
657 if (strlen(name) > 255)
658 return false;
659
660 return true;
661 }