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