chiark / gitweb /
logind: change check_gc to may_gc everywhere
[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 = s->pending_switch;
570         s->pending_switch = NULL;
571
572         seat_set_active(s, session);
573 }
574
575 bool seat_has_vts(Seat *s) {
576         assert(s);
577
578         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
579 }
580
581 bool seat_is_seat0(Seat *s) {
582         assert(s);
583
584         return s->manager->seat0 == s;
585 }
586
587 bool seat_can_multi_session(Seat *s) {
588         assert(s);
589
590         return seat_has_vts(s);
591 }
592
593 bool seat_can_tty(Seat *s) {
594         assert(s);
595
596         return seat_has_vts(s);
597 }
598
599 bool seat_has_master_device(Seat *s) {
600         assert(s);
601
602         /* device list is ordered by "master" flag */
603         return !!s->devices && s->devices->master;
604 }
605
606 bool seat_can_graphical(Seat *s) {
607         assert(s);
608
609         return seat_has_master_device(s);
610 }
611
612 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
613         Session *session;
614         bool idle_hint = true;
615         dual_timestamp ts = DUAL_TIMESTAMP_NULL;
616
617         assert(s);
618
619         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
620                 dual_timestamp k;
621                 int ih;
622
623                 ih = session_get_idle_hint(session, &k);
624                 if (ih < 0)
625                         return ih;
626
627                 if (!ih) {
628                         if (!idle_hint) {
629                                 if (k.monotonic > ts.monotonic)
630                                         ts = k;
631                         } else {
632                                 idle_hint = false;
633                                 ts = k;
634                         }
635                 } else if (idle_hint) {
636
637                         if (k.monotonic > ts.monotonic)
638                                 ts = k;
639                 }
640         }
641
642         if (t)
643                 *t = ts;
644
645         return idle_hint;
646 }
647
648 bool seat_may_gc(Seat *s, bool drop_not_started) {
649         assert(s);
650
651         if (drop_not_started && !s->started)
652                 return true;
653
654         if (seat_is_seat0(s))
655                 return false;
656
657         return !seat_has_master_device(s);
658 }
659
660 void seat_add_to_gc_queue(Seat *s) {
661         assert(s);
662
663         if (s->in_gc_queue)
664                 return;
665
666         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
667         s->in_gc_queue = true;
668 }
669
670 static bool seat_name_valid_char(char c) {
671         return
672                 (c >= 'a' && c <= 'z') ||
673                 (c >= 'A' && c <= 'Z') ||
674                 (c >= '0' && c <= '9') ||
675                 IN_SET(c, '-', '_');
676 }
677
678 bool seat_name_is_valid(const char *name) {
679         const char *p;
680
681         assert(name);
682
683         if (!startswith(name, "seat"))
684                 return false;
685
686         if (!name[4])
687                 return false;
688
689         for (p = name; *p; p++)
690                 if (!seat_name_valid_char(*p))
691                         return false;
692
693         if (strlen(name) > 255)
694                 return false;
695
696         return true;
697 }