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