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