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