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