]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-seat.c
tree-wide: use mfree more
[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"
07630cea 30#include "formats-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
fde78a3a 130 fputs("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
fde78a3a
LP
138 fputs("UIDS=", f);
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
382 lseek(s->manager->console_active_fd, SEEK_SET, 0);
383
384 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
385 if (k <= 0) {
386 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
387 return k < 0 ? -errno : -EIO;
388 }
389
390 t[k] = 0;
391 truncate_nl(t);
392
393 if (!startswith(t, "tty")) {
394 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
395 return -EIO;
396 }
397
92bd5ff3 398 r = safe_atou(t+3, &vtnr);
14c3baca
LP
399 if (r < 0) {
400 log_error("Failed to parse VT number %s", t+3);
401 return r;
402 }
403
92bd5ff3 404 if (!vtnr) {
14c3baca
LP
405 log_error("VT number invalid: %s", t+3);
406 return -EIO;
407 }
408
409 return seat_active_vt_changed(s, vtnr);
410}
411
412int seat_start(Seat *s) {
413 assert(s);
414
3f49d45a
LP
415 if (s->started)
416 return 0;
417
877d54e9 418 log_struct(LOG_INFO,
e2cc6eca 419 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
877d54e9 420 "SEAT_ID=%s", s->id,
e2cc6eca 421 LOG_MESSAGE("New seat %s.", s->id),
877d54e9 422 NULL);
3f49d45a 423
14c3baca
LP
424 /* Initialize VT magic stuff */
425 seat_preallocate_vts(s);
426
427 /* Read current VT */
428 seat_read_active_vt(s);
429
7f7bb946
LP
430 s->started = true;
431
14c3baca
LP
432 /* Save seat data */
433 seat_save(s);
434
da119395
LP
435 seat_send_signal(s, true);
436
14c3baca
LP
437 return 0;
438}
439
9bb69af4 440int seat_stop(Seat *s, bool force) {
a185c5aa 441 int r = 0;
20263082
LP
442
443 assert(s);
444
ed18b08b 445 if (s->started)
877d54e9 446 log_struct(LOG_INFO,
e2cc6eca 447 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
877d54e9 448 "SEAT_ID=%s", s->id,
e2cc6eca 449 LOG_MESSAGE("Removed seat %s.", s->id),
877d54e9 450 NULL);
da119395 451
9bb69af4 452 seat_stop_sessions(s, force);
a185c5aa
LP
453
454 unlink(s->state_file);
455 seat_add_to_gc_queue(s);
456
ed18b08b
LP
457 if (s->started)
458 seat_send_signal(s, false);
459
a185c5aa
LP
460 s->started = false;
461
462 return r;
463}
464
9bb69af4 465int seat_stop_sessions(Seat *s, bool force) {
a185c5aa
LP
466 Session *session;
467 int r = 0, k;
468
469 assert(s);
470
20263082 471 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
9bb69af4 472 k = session_stop(session, force);
20263082
LP
473 if (k < 0)
474 r = k;
475 }
476
a185c5aa
LP
477 return r;
478}
14c3baca 479
49e6fdbf 480void seat_evict_position(Seat *s, Session *session) {
3e6b205f 481 Session *iter;
e6494a07 482 unsigned int pos = session->position;
49e6fdbf 483
e6494a07 484 session->position = 0;
49e6fdbf 485
55493982 486 if (pos == 0)
49e6fdbf
DH
487 return;
488
3e6b205f 489 if (pos < s->position_count && s->positions[pos] == session) {
49e6fdbf 490 s->positions[pos] = NULL;
3e6b205f
DH
491
492 /* There might be another session claiming the same
55493982 493 * position (eg., during gdm->session transition), so let's look
3e6b205f
DH
494 * for it and set it on the free slot. */
495 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
28103328 496 if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
3e6b205f
DH
497 s->positions[pos] = iter;
498 break;
499 }
500 }
501 }
49e6fdbf
DH
502}
503
504void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
505 /* with VTs, the position is always the same as the VTnr */
506 if (seat_has_vts(s))
507 pos = session->vtnr;
508
ca2d3784 509 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
49e6fdbf
DH
510 return;
511
512 seat_evict_position(s, session);
513
e6494a07 514 session->position = pos;
da770c38 515 if (pos > 0)
49e6fdbf
DH
516 s->positions[pos] = session;
517}
518
519static void seat_assign_position(Seat *s, Session *session) {
520 unsigned int pos;
521
e6494a07 522 if (session->position > 0)
49e6fdbf
DH
523 return;
524
525 for (pos = 1; pos < s->position_count; ++pos)
526 if (!s->positions[pos])
527 break;
528
529 seat_claim_position(s, session, pos);
530}
531
a185c5aa
LP
532int seat_attach_session(Seat *s, Session *session) {
533 assert(s);
534 assert(session);
535 assert(!session->seat);
3f49d45a 536
c506027a
DH
537 if (!seat_has_vts(s) != !session->vtnr)
538 return -EINVAL;
539
a185c5aa 540 session->seat = s;
71fda00f 541 LIST_PREPEND(sessions_by_seat, s->sessions, session);
49e6fdbf 542 seat_assign_position(s, session);
a185c5aa 543
cc377381 544 seat_send_changed(s, "Sessions", NULL);
9418f147 545
bf7825ae 546 /* On seats with VTs, the VT logic defines which session is active. On
3fdb2494
DH
547 * seats without VTs, we automatically activate new sessions. */
548 if (!seat_has_vts(s))
9418f147 549 seat_set_active(s, session);
9418f147 550
a185c5aa
LP
551 return 0;
552}
553
d7bd01b5
DH
554void seat_complete_switch(Seat *s) {
555 Session *session;
556
557 assert(s);
558
559 /* if no session-switch is pending or if it got canceled, do nothing */
560 if (!s->pending_switch)
561 return;
562
563 session = s->pending_switch;
564 s->pending_switch = NULL;
565
566 seat_set_active(s, session);
567}
568
bf7825ae
DH
569bool seat_has_vts(Seat *s) {
570 assert(s);
571
572 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
573}
574
92432fcc 575bool seat_is_seat0(Seat *s) {
a185c5aa
LP
576 assert(s);
577
92432fcc 578 return s->manager->seat0 == s;
a185c5aa
LP
579}
580
addedec4
LP
581bool seat_can_multi_session(Seat *s) {
582 assert(s);
583
bf7825ae 584 return seat_has_vts(s);
addedec4
LP
585}
586
f1a8e221
LP
587bool seat_can_tty(Seat *s) {
588 assert(s);
589
bf7825ae 590 return seat_has_vts(s);
f1a8e221
LP
591}
592
718d006a
DH
593bool seat_has_master_device(Seat *s) {
594 assert(s);
595
596 /* device list is ordered by "master" flag */
597 return !!s->devices && s->devices->master;
598}
599
f1a8e221
LP
600bool seat_can_graphical(Seat *s) {
601 assert(s);
602
718d006a 603 return seat_has_master_device(s);
f1a8e221
LP
604}
605
a185c5aa
LP
606int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
607 Session *session;
608 bool idle_hint = true;
5cb14b37 609 dual_timestamp ts = DUAL_TIMESTAMP_NULL;
a185c5aa
LP
610
611 assert(s);
612
613 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
614 dual_timestamp k;
615 int ih;
616
617 ih = session_get_idle_hint(session, &k);
618 if (ih < 0)
619 return ih;
620
621 if (!ih) {
622 if (!idle_hint) {
4a271908 623 if (k.monotonic > ts.monotonic)
a185c5aa
LP
624 ts = k;
625 } else {
626 idle_hint = false;
627 ts = k;
628 }
629 } else if (idle_hint) {
630
631 if (k.monotonic > ts.monotonic)
632 ts = k;
633 }
634 }
635
636 if (t)
637 *t = ts;
638
639 return idle_hint;
20263082 640}
14c3baca 641
cc377381 642bool seat_check_gc(Seat *s, bool drop_not_started) {
14c3baca
LP
643 assert(s);
644
4a4b033f 645 if (drop_not_started && !s->started)
cc377381 646 return false;
932e3ee7 647
92432fcc 648 if (seat_is_seat0(s))
cc377381 649 return true;
14c3baca 650
718d006a 651 return seat_has_master_device(s);
14c3baca
LP
652}
653
654void seat_add_to_gc_queue(Seat *s) {
655 assert(s);
656
657 if (s->in_gc_queue)
658 return;
659
71fda00f 660 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
14c3baca
LP
661 s->in_gc_queue = true;
662}
3f49d45a
LP
663
664static bool seat_name_valid_char(char c) {
665 return
666 (c >= 'a' && c <= 'z') ||
667 (c >= 'A' && c <= 'Z') ||
668 (c >= '0' && c <= '9') ||
669 c == '-' ||
670 c == '_';
671}
672
673bool seat_name_is_valid(const char *name) {
674 const char *p;
675
676 assert(name);
677
678 if (!startswith(name, "seat"))
679 return false;
680
681 if (!name[4])
682 return false;
683
684 for (p = name; *p; p++)
685 if (!seat_name_valid_char(*p))
686 return false;
687
1c9a2c10
LP
688 if (strlen(name) > 255)
689 return false;
690
3f49d45a
LP
691 return true;
692}