]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
util-lib: split our string related calls from util.[ch] into its own file string...
[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 <fcntl.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "sd-messages.h"
28
29 #include "formats-util.h"
30 #include "logind-acl.h"
31 #include "mkdir.h"
32 #include "string-util.h"
33 #include "terminal-util.h"
34 #include "util.h"
35 #include "logind-seat.h"
36
37 Seat *seat_new(Manager *m, const char *id) {
38 Seat *s;
39
40 assert(m);
41 assert(id);
42
43 s = new0(Seat, 1);
44 if (!s)
45 return NULL;
46
47 s->state_file = strappend("/run/systemd/seats/", id);
48 if (!s->state_file) {
49 free(s);
50 return NULL;
51 }
52
53 s->id = basename(s->state_file);
54 s->manager = m;
55
56 if (hashmap_put(m->seats, s->id, s) < 0) {
57 free(s->state_file);
58 free(s);
59 return NULL;
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 snprintf(p, sizeof(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 }