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