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