]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-seat.c
Fix some format strings for enums, they are signed
[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
22#include <assert.h>
23#include <errno.h>
24#include <unistd.h>
25#include <fcntl.h>
26#include <sys/ioctl.h>
27#include <linux/vt.h>
5eda94dd 28#include <string.h>
20263082 29
cc377381
LP
30#include "sd-id128.h"
31#include "sd-messages.h"
90821c93 32#include "logind-seat.h"
5eda94dd 33#include "logind-acl.h"
20263082 34#include "util.h"
49e942b2 35#include "mkdir.h"
9eb977db 36#include "path-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)
14c3baca
LP
99 goto finish;
100
101 r = fopen_temporary(s->state_file, &f, &temp_path);
102 if (r < 0)
103 goto finish;
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
147 fflush(f);
14c3baca
LP
148
149 if (ferror(f) || rename(temp_path, s->state_file) < 0) {
20263082
LP
150 r = -errno;
151 unlink(s->state_file);
14c3baca 152 unlink(temp_path);
20263082
LP
153 }
154
14c3baca
LP
155finish:
156 if (r < 0)
da927ba9 157 log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
14c3baca 158
20263082
LP
159 return r;
160}
161
162int seat_load(Seat *s) {
163 assert(s);
164
a185c5aa
LP
165 /* There isn't actually anything to read here ... */
166
20263082
LP
167 return 0;
168}
169
92bd5ff3 170static int vt_allocate(unsigned int vtnr) {
61376f96 171 char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
cc377381 172 _cleanup_close_ int fd = -1;
20263082
LP
173
174 assert(vtnr >= 1);
175
61376f96 176 snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
20263082 177 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
cc377381
LP
178 if (fd < 0)
179 return -errno;
20263082 180
cc377381 181 return 0;
20263082
LP
182}
183
30ed21ce 184int seat_preallocate_vts(Seat *s) {
3f49d45a
LP
185 int r = 0;
186 unsigned i;
20263082
LP
187
188 assert(s);
189 assert(s->manager);
190
30ed21ce
LP
191 log_debug("Preallocating VTs...");
192
20263082
LP
193 if (s->manager->n_autovts <= 0)
194 return 0;
195
bf7825ae 196 if (!seat_has_vts(s))
20263082
LP
197 return 0;
198
e7886786 199 for (i = 1; i <= s->manager->n_autovts; i++) {
20263082
LP
200 int q;
201
202 q = vt_allocate(i);
14c3baca 203 if (q < 0) {
da927ba9 204 log_error_errno(q, "Failed to preallocate VT %i: %m", i);
20263082 205 r = q;
14c3baca 206 }
20263082
LP
207 }
208
209 return r;
210}
211
5eda94dd
LP
212int seat_apply_acls(Seat *s, Session *old_active) {
213 int r;
20263082 214
5eda94dd 215 assert(s);
20263082 216
5eda94dd
LP
217 r = devnode_acl_all(s->manager->udev,
218 s->id,
219 false,
220 !!old_active, old_active ? old_active->user->uid : 0,
221 !!s->active, s->active ? s->active->user->uid : 0);
20263082 222
5eda94dd 223 if (r < 0)
da927ba9 224 log_error_errno(r, "Failed to apply ACLs: %m");
20263082
LP
225
226 return r;
227}
228
9418f147
LP
229int seat_set_active(Seat *s, Session *session) {
230 Session *old_active;
231
232 assert(s);
77527da0 233 assert(!session || session->seat == s);
9418f147
LP
234
235 if (session == s->active)
236 return 0;
237
238 old_active = s->active;
239 s->active = session;
240
dfd55270 241 if (old_active) {
118ecf32 242 session_device_pause_all(old_active);
cc377381 243 session_send_changed(old_active, "Active", NULL);
dfd55270 244 }
118ecf32 245
9418f147
LP
246 seat_apply_acls(s, old_active);
247
118ecf32 248 if (session && session->started) {
cc377381 249 session_send_changed(session, "Active", NULL);
118ecf32
DH
250 session_device_resume_all(session);
251 }
9418f147
LP
252
253 if (!session || session->started)
cc377381 254 seat_send_changed(s, "ActiveSession", NULL);
9418f147 255
98a28fef
LP
256 seat_save(s);
257
7f7bb946 258 if (session) {
98a28fef 259 session_save(session);
7f7bb946
LP
260 user_save(session->user);
261 }
98a28fef 262
7f7bb946 263 if (old_active) {
98a28fef 264 session_save(old_active);
78ab361c
CG
265 if (!session || session->user != old_active->user)
266 user_save(old_active->user);
7f7bb946 267 }
98a28fef 268
9418f147
LP
269 return 0;
270}
271
49e6fdbf
DH
272int seat_switch_to(Seat *s, unsigned int num) {
273 /* Public session positions skip 0 (there is only F1-F12). Maybe it
274 * will get reassigned in the future, so return error for now. */
275 if (!num)
276 return -EINVAL;
277
15403427
DH
278 if (num >= s->position_count || !s->positions[num]) {
279 /* allow switching to unused VTs to trigger auto-activate */
280 if (seat_has_vts(s) && num < 64)
281 return chvt(num);
282
49e6fdbf 283 return -EINVAL;
15403427 284 }
49e6fdbf
DH
285
286 return session_activate(s->positions[num]);
287}
288
289int seat_switch_to_next(Seat *s) {
290 unsigned int start, i;
291
292 if (!s->position_count)
293 return -EINVAL;
294
295 start = 1;
296 if (s->active && s->active->pos > 0)
297 start = s->active->pos;
298
299 for (i = start + 1; i < s->position_count; ++i)
300 if (s->positions[i])
301 return session_activate(s->positions[i]);
302
303 for (i = 1; i < start; ++i)
304 if (s->positions[i])
305 return session_activate(s->positions[i]);
306
307 return -EINVAL;
308}
309
310int seat_switch_to_previous(Seat *s) {
311 unsigned int start, i;
312
313 if (!s->position_count)
314 return -EINVAL;
315
316 start = 1;
317 if (s->active && s->active->pos > 0)
318 start = s->active->pos;
319
320 for (i = start - 1; i > 0; --i)
321 if (s->positions[i])
322 return session_activate(s->positions[i]);
323
324 for (i = s->position_count - 1; i > start; --i)
325 if (s->positions[i])
326 return session_activate(s->positions[i]);
327
328 return -EINVAL;
329}
330
92bd5ff3 331int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
9418f147
LP
332 Session *i, *new_active = NULL;
333 int r;
20263082
LP
334
335 assert(s);
336 assert(vtnr >= 1);
20263082 337
bf7825ae 338 if (!seat_has_vts(s))
14c3baca
LP
339 return -EINVAL;
340
92bd5ff3 341 log_debug("VT changed to %u", vtnr);
20263082
LP
342
343 LIST_FOREACH(sessions_by_seat, i, s->sessions)
344 if (i->vtnr == vtnr) {
14c3baca 345 new_active = i;
20263082
LP
346 break;
347 }
348
9418f147 349 r = seat_set_active(s, new_active);
5eda94dd 350 manager_spawn_autovt(s->manager, vtnr);
20263082 351
9418f147 352 return r;
20263082
LP
353}
354
14c3baca
LP
355int seat_read_active_vt(Seat *s) {
356 char t[64];
357 ssize_t k;
92bd5ff3
DH
358 unsigned int vtnr;
359 int r;
14c3baca
LP
360
361 assert(s);
362
bf7825ae 363 if (!seat_has_vts(s))
14c3baca
LP
364 return 0;
365
366 lseek(s->manager->console_active_fd, SEEK_SET, 0);
367
368 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
369 if (k <= 0) {
370 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
371 return k < 0 ? -errno : -EIO;
372 }
373
374 t[k] = 0;
375 truncate_nl(t);
376
377 if (!startswith(t, "tty")) {
378 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
379 return -EIO;
380 }
381
92bd5ff3 382 r = safe_atou(t+3, &vtnr);
14c3baca
LP
383 if (r < 0) {
384 log_error("Failed to parse VT number %s", t+3);
385 return r;
386 }
387
92bd5ff3 388 if (!vtnr) {
14c3baca
LP
389 log_error("VT number invalid: %s", t+3);
390 return -EIO;
391 }
392
393 return seat_active_vt_changed(s, vtnr);
394}
395
396int seat_start(Seat *s) {
397 assert(s);
398
3f49d45a
LP
399 if (s->started)
400 return 0;
401
877d54e9 402 log_struct(LOG_INFO,
e2cc6eca 403 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
877d54e9 404 "SEAT_ID=%s", s->id,
e2cc6eca 405 LOG_MESSAGE("New seat %s.", s->id),
877d54e9 406 NULL);
3f49d45a 407
14c3baca
LP
408 /* Initialize VT magic stuff */
409 seat_preallocate_vts(s);
410
411 /* Read current VT */
412 seat_read_active_vt(s);
413
7f7bb946
LP
414 s->started = true;
415
14c3baca
LP
416 /* Save seat data */
417 seat_save(s);
418
da119395
LP
419 seat_send_signal(s, true);
420
14c3baca
LP
421 return 0;
422}
423
9bb69af4 424int seat_stop(Seat *s, bool force) {
a185c5aa 425 int r = 0;
20263082
LP
426
427 assert(s);
428
ed18b08b 429 if (s->started)
877d54e9 430 log_struct(LOG_INFO,
e2cc6eca 431 LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
877d54e9 432 "SEAT_ID=%s", s->id,
e2cc6eca 433 LOG_MESSAGE("Removed seat %s.", s->id),
877d54e9 434 NULL);
da119395 435
9bb69af4 436 seat_stop_sessions(s, force);
a185c5aa
LP
437
438 unlink(s->state_file);
439 seat_add_to_gc_queue(s);
440
ed18b08b
LP
441 if (s->started)
442 seat_send_signal(s, false);
443
a185c5aa
LP
444 s->started = false;
445
446 return r;
447}
448
9bb69af4 449int seat_stop_sessions(Seat *s, bool force) {
a185c5aa
LP
450 Session *session;
451 int r = 0, k;
452
453 assert(s);
454
20263082 455 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
9bb69af4 456 k = session_stop(session, force);
20263082
LP
457 if (k < 0)
458 r = k;
459 }
460
a185c5aa
LP
461 return r;
462}
14c3baca 463
49e6fdbf 464void seat_evict_position(Seat *s, Session *session) {
3e6b205f 465 Session *iter;
49e6fdbf
DH
466 unsigned int pos = session->pos;
467
468 session->pos = 0;
469
470 if (!pos)
471 return;
472
3e6b205f 473 if (pos < s->position_count && s->positions[pos] == session) {
49e6fdbf 474 s->positions[pos] = NULL;
3e6b205f
DH
475
476 /* There might be another session claiming the same
477 * position (eg., during gdm->session transition), so lets look
478 * for it and set it on the free slot. */
479 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
480 if (iter->pos == pos) {
481 s->positions[pos] = iter;
482 break;
483 }
484 }
485 }
49e6fdbf
DH
486}
487
488void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
489 /* with VTs, the position is always the same as the VTnr */
490 if (seat_has_vts(s))
491 pos = session->vtnr;
492
ca2d3784 493 if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
49e6fdbf
DH
494 return;
495
496 seat_evict_position(s, session);
497
498 session->pos = pos;
499 if (pos > 0 && !s->positions[pos])
500 s->positions[pos] = session;
501}
502
503static void seat_assign_position(Seat *s, Session *session) {
504 unsigned int pos;
505
506 if (session->pos > 0)
507 return;
508
509 for (pos = 1; pos < s->position_count; ++pos)
510 if (!s->positions[pos])
511 break;
512
513 seat_claim_position(s, session, pos);
514}
515
a185c5aa
LP
516int seat_attach_session(Seat *s, Session *session) {
517 assert(s);
518 assert(session);
519 assert(!session->seat);
3f49d45a 520
c506027a
DH
521 if (!seat_has_vts(s) != !session->vtnr)
522 return -EINVAL;
523
a185c5aa 524 session->seat = s;
71fda00f 525 LIST_PREPEND(sessions_by_seat, s->sessions, session);
49e6fdbf 526 seat_assign_position(s, session);
a185c5aa 527
cc377381 528 seat_send_changed(s, "Sessions", NULL);
9418f147 529
bf7825ae 530 /* On seats with VTs, the VT logic defines which session is active. On
3fdb2494
DH
531 * seats without VTs, we automatically activate new sessions. */
532 if (!seat_has_vts(s))
9418f147 533 seat_set_active(s, session);
9418f147 534
a185c5aa
LP
535 return 0;
536}
537
d7bd01b5
DH
538void seat_complete_switch(Seat *s) {
539 Session *session;
540
541 assert(s);
542
543 /* if no session-switch is pending or if it got canceled, do nothing */
544 if (!s->pending_switch)
545 return;
546
547 session = s->pending_switch;
548 s->pending_switch = NULL;
549
550 seat_set_active(s, session);
551}
552
bf7825ae
DH
553bool seat_has_vts(Seat *s) {
554 assert(s);
555
556 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
557}
558
92432fcc 559bool seat_is_seat0(Seat *s) {
a185c5aa
LP
560 assert(s);
561
92432fcc 562 return s->manager->seat0 == s;
a185c5aa
LP
563}
564
addedec4
LP
565bool seat_can_multi_session(Seat *s) {
566 assert(s);
567
bf7825ae 568 return seat_has_vts(s);
addedec4
LP
569}
570
f1a8e221
LP
571bool seat_can_tty(Seat *s) {
572 assert(s);
573
bf7825ae 574 return seat_has_vts(s);
f1a8e221
LP
575}
576
718d006a
DH
577bool seat_has_master_device(Seat *s) {
578 assert(s);
579
580 /* device list is ordered by "master" flag */
581 return !!s->devices && s->devices->master;
582}
583
f1a8e221
LP
584bool seat_can_graphical(Seat *s) {
585 assert(s);
586
718d006a 587 return seat_has_master_device(s);
f1a8e221
LP
588}
589
a185c5aa
LP
590int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
591 Session *session;
592 bool idle_hint = true;
593 dual_timestamp ts = { 0, 0 };
594
595 assert(s);
596
597 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
598 dual_timestamp k;
599 int ih;
600
601 ih = session_get_idle_hint(session, &k);
602 if (ih < 0)
603 return ih;
604
605 if (!ih) {
606 if (!idle_hint) {
4a271908 607 if (k.monotonic > ts.monotonic)
a185c5aa
LP
608 ts = k;
609 } else {
610 idle_hint = false;
611 ts = k;
612 }
613 } else if (idle_hint) {
614
615 if (k.monotonic > ts.monotonic)
616 ts = k;
617 }
618 }
619
620 if (t)
621 *t = ts;
622
623 return idle_hint;
20263082 624}
14c3baca 625
cc377381 626bool seat_check_gc(Seat *s, bool drop_not_started) {
14c3baca
LP
627 assert(s);
628
4a4b033f 629 if (drop_not_started && !s->started)
cc377381 630 return false;
932e3ee7 631
92432fcc 632 if (seat_is_seat0(s))
cc377381 633 return true;
14c3baca 634
718d006a 635 return seat_has_master_device(s);
14c3baca
LP
636}
637
638void seat_add_to_gc_queue(Seat *s) {
639 assert(s);
640
641 if (s->in_gc_queue)
642 return;
643
71fda00f 644 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
14c3baca
LP
645 s->in_gc_queue = true;
646}
3f49d45a
LP
647
648static bool seat_name_valid_char(char c) {
649 return
650 (c >= 'a' && c <= 'z') ||
651 (c >= 'A' && c <= 'Z') ||
652 (c >= '0' && c <= '9') ||
653 c == '-' ||
654 c == '_';
655}
656
657bool seat_name_is_valid(const char *name) {
658 const char *p;
659
660 assert(name);
661
662 if (!startswith(name, "seat"))
663 return false;
664
665 if (!name[4])
666 return false;
667
668 for (p = name; *p; p++)
669 if (!seat_name_valid_char(*p))
670 return false;
671
1c9a2c10
LP
672 if (strlen(name) > 255)
673 return false;
674
3f49d45a
LP
675 return true;
676}