]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/logind-seat.c
util-lib: split our string related calls from util.[ch] into its own file string...
[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
LP
28
29#include "formats-util.h"
5eda94dd 30#include "logind-acl.h"
49e942b2 31#include "mkdir.h"
07630cea 32#include "string-util.h"
288a74cc 33#include "terminal-util.h"
07630cea
LP
34#include "util.h"
35#include "logind-seat.h"
20263082
LP
36
37Seat *seat_new(Manager *m, const char *id) {
38 Seat *s;
39
40 assert(m);
41 assert(id);
42
43 s = new0(Seat, 1);
44 if (!s)
45 return NULL;
46
98a28fef 47 s->state_file = strappend("/run/systemd/seats/", id);
20263082
LP
48 if (!s->state_file) {
49 free(s);
50 return NULL;
51 }
52
2b6bf07d 53 s->id = basename(s->state_file);
14c3baca 54 s->manager = m;
20263082
LP
55
56 if (hashmap_put(m->seats, s->id, s) < 0) {
14c3baca 57 free(s->state_file);
20263082
LP
58 free(s);
59 return NULL;
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
61376f96 180 snprintf(p, sizeof(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}