chiark / gitweb /
journalctl: Unify boot id lookup into common function get_boots
[elogind.git] / src / login / logind-seat.c
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
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
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
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
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>
28 #include <string.h>
29
30 #include "sd-id128.h"
31 #include "sd-messages.h"
32 #include "logind-seat.h"
33 #include "logind-acl.h"
34 #include "util.h"
35 #include "mkdir.h"
36 #include "path-util.h"
37
38 Seat *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
48         s->state_file = strappend("/run/systemd/seats/", id);
49         if (!s->state_file) {
50                 free(s);
51                 return NULL;
52         }
53
54         s->id = basename(s->state_file);
55         s->manager = m;
56
57         if (hashmap_put(m->seats, s->id, s) < 0) {
58                 free(s->state_file);
59                 free(s);
60                 return NULL;
61         }
62
63         return s;
64 }
65
66 void seat_free(Seat *s) {
67         assert(s);
68
69         if (s->in_gc_queue)
70                 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
71
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
82         free(s->positions);
83         free(s->state_file);
84         free(s);
85 }
86
87 int seat_save(Seat *s) {
88         _cleanup_free_ char *temp_path = NULL;
89         _cleanup_fclose_ FILE *f = NULL;
90         int r;
91
92         assert(s);
93
94         if (!s->started)
95                 return 0;
96
97         r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
98         if (r < 0)
99                 goto finish;
100
101         r = fopen_temporary(s->state_file, &f, &temp_path);
102         if (r < 0)
103                 goto finish;
104
105         fchmod(fileno(f), 0644);
106
107         fprintf(f,
108                 "# This is private data. Do not parse.\n"
109                 "IS_SEAT0=%i\n"
110                 "CAN_MULTI_SESSION=%i\n"
111                 "CAN_TTY=%i\n"
112                 "CAN_GRAPHICAL=%i\n",
113                 seat_is_seat0(s),
114                 seat_can_multi_session(s),
115                 seat_can_tty(s),
116                 seat_can_graphical(s));
117
118         if (s->active) {
119                 assert(s->active->user);
120
121                 fprintf(f,
122                         "ACTIVE=%s\n"
123                         "ACTIVE_UID="UID_FMT"\n",
124                         s->active->id,
125                         s->active->user->uid);
126         }
127
128         if (s->sessions) {
129                 Session *i;
130
131                 fputs("SESSIONS=", f);
132                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
133                         fprintf(f,
134                                 "%s%c",
135                                 i->id,
136                                 i->sessions_by_seat_next ? ' ' : '\n');
137                 }
138
139                 fputs("UIDS=", f);
140                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
141                         fprintf(f,
142                                 UID_FMT"%c",
143                                 i->user->uid,
144                                 i->sessions_by_seat_next ? ' ' : '\n');
145         }
146
147         fflush(f);
148
149         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
150                 r = -errno;
151                 unlink(s->state_file);
152                 unlink(temp_path);
153         }
154
155 finish:
156         if (r < 0)
157                 log_error("Failed to save seat data %s: %s", s->state_file, strerror(-r));
158
159         return r;
160 }
161
162 int seat_load(Seat *s) {
163         assert(s);
164
165         /* There isn't actually anything to read here ... */
166
167         return 0;
168 }
169
170 static int vt_allocate(unsigned int vtnr) {
171         char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
172         _cleanup_close_ int fd = -1;
173
174         assert(vtnr >= 1);
175
176         snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
177         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
178         if (fd < 0)
179                 return -errno;
180
181         return 0;
182 }
183
184 int seat_preallocate_vts(Seat *s) {
185         int r = 0;
186         unsigned i;
187
188         assert(s);
189         assert(s->manager);
190
191         log_debug("Preallocating VTs...");
192
193         if (s->manager->n_autovts <= 0)
194                 return 0;
195
196         if (!seat_has_vts(s))
197                 return 0;
198
199         for (i = 1; i <= s->manager->n_autovts; i++) {
200                 int q;
201
202                 q = vt_allocate(i);
203                 if (q < 0) {
204                         log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
205                         r = q;
206                 }
207         }
208
209         return r;
210 }
211
212 int seat_apply_acls(Seat *s, Session *old_active) {
213         int r;
214
215         assert(s);
216
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);
222
223         if (r < 0)
224                 log_error("Failed to apply ACLs: %s", strerror(-r));
225
226         return r;
227 }
228
229 int seat_set_active(Seat *s, Session *session) {
230         Session *old_active;
231
232         assert(s);
233         assert(!session || session->seat == s);
234
235         if (session == s->active)
236                 return 0;
237
238         old_active = s->active;
239         s->active = session;
240
241         if (old_active) {
242                 session_device_pause_all(old_active);
243                 session_send_changed(old_active, "Active", NULL);
244         }
245
246         seat_apply_acls(s, old_active);
247
248         if (session && session->started) {
249                 session_send_changed(session, "Active", NULL);
250                 session_device_resume_all(session);
251         }
252
253         if (!session || session->started)
254                 seat_send_changed(s, "ActiveSession", NULL);
255
256         seat_save(s);
257
258         if (session) {
259                 session_save(session);
260                 user_save(session->user);
261         }
262
263         if (old_active) {
264                 session_save(old_active);
265                 if (!session || session->user != old_active->user)
266                         user_save(old_active->user);
267         }
268
269         return 0;
270 }
271
272 int 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
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
283                 return -EINVAL;
284         }
285
286         return session_activate(s->positions[num]);
287 }
288
289 int 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
310 int 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
331 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
332         Session *i, *new_active = NULL;
333         int r;
334
335         assert(s);
336         assert(vtnr >= 1);
337
338         if (!seat_has_vts(s))
339                 return -EINVAL;
340
341         log_debug("VT changed to %u", vtnr);
342
343         LIST_FOREACH(sessions_by_seat, i, s->sessions)
344                 if (i->vtnr == vtnr) {
345                         new_active = i;
346                         break;
347                 }
348
349         r = seat_set_active(s, new_active);
350         manager_spawn_autovt(s->manager, vtnr);
351
352         return r;
353 }
354
355 int seat_read_active_vt(Seat *s) {
356         char t[64];
357         ssize_t k;
358         unsigned int vtnr;
359         int r;
360
361         assert(s);
362
363         if (!seat_has_vts(s))
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
382         r = safe_atou(t+3, &vtnr);
383         if (r < 0) {
384                 log_error("Failed to parse VT number %s", t+3);
385                 return r;
386         }
387
388         if (!vtnr) {
389                 log_error("VT number invalid: %s", t+3);
390                 return -EIO;
391         }
392
393         return seat_active_vt_changed(s, vtnr);
394 }
395
396 int seat_start(Seat *s) {
397         assert(s);
398
399         if (s->started)
400                 return 0;
401
402         log_struct(LOG_INFO,
403                    MESSAGE_ID(SD_MESSAGE_SEAT_START),
404                    "SEAT_ID=%s", s->id,
405                    "MESSAGE=New seat %s.", s->id,
406                    NULL);
407
408         /* Initialize VT magic stuff */
409         seat_preallocate_vts(s);
410
411         /* Read current VT */
412         seat_read_active_vt(s);
413
414         s->started = true;
415
416         /* Save seat data */
417         seat_save(s);
418
419         seat_send_signal(s, true);
420
421         return 0;
422 }
423
424 int seat_stop(Seat *s, bool force) {
425         int r = 0;
426
427         assert(s);
428
429         if (s->started)
430                 log_struct(LOG_INFO,
431                            MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
432                            "SEAT_ID=%s", s->id,
433                            "MESSAGE=Removed seat %s.", s->id,
434                            NULL);
435
436         seat_stop_sessions(s, force);
437
438         unlink(s->state_file);
439         seat_add_to_gc_queue(s);
440
441         if (s->started)
442                 seat_send_signal(s, false);
443
444         s->started = false;
445
446         return r;
447 }
448
449 int seat_stop_sessions(Seat *s, bool force) {
450         Session *session;
451         int r = 0, k;
452
453         assert(s);
454
455         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
456                 k = session_stop(session, force);
457                 if (k < 0)
458                         r = k;
459         }
460
461         return r;
462 }
463
464 void seat_evict_position(Seat *s, Session *session) {
465         Session *iter;
466         unsigned int pos = session->pos;
467
468         session->pos = 0;
469
470         if (!pos)
471                 return;
472
473         if (pos < s->position_count && s->positions[pos] == session) {
474                 s->positions[pos] = NULL;
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         }
486 }
487
488 void 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
493         if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
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
503 static 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
516 int seat_attach_session(Seat *s, Session *session) {
517         assert(s);
518         assert(session);
519         assert(!session->seat);
520
521         if (!seat_has_vts(s) != !session->vtnr)
522                 return -EINVAL;
523
524         session->seat = s;
525         LIST_PREPEND(sessions_by_seat, s->sessions, session);
526         seat_assign_position(s, session);
527
528         seat_send_changed(s, "Sessions", NULL);
529
530         /* On seats with VTs, the VT logic defines which session is active. On
531          * seats without VTs, we automatically activate new sessions. */
532         if (!seat_has_vts(s))
533                 seat_set_active(s, session);
534
535         return 0;
536 }
537
538 void 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
553 bool seat_has_vts(Seat *s) {
554         assert(s);
555
556         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
557 }
558
559 bool seat_is_seat0(Seat *s) {
560         assert(s);
561
562         return s->manager->seat0 == s;
563 }
564
565 bool seat_can_multi_session(Seat *s) {
566         assert(s);
567
568         return seat_has_vts(s);
569 }
570
571 bool seat_can_tty(Seat *s) {
572         assert(s);
573
574         return seat_has_vts(s);
575 }
576
577 bool 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
584 bool seat_can_graphical(Seat *s) {
585         assert(s);
586
587         return seat_has_master_device(s);
588 }
589
590 int 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) {
607                                 if (k.monotonic > ts.monotonic)
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;
624 }
625
626 bool seat_check_gc(Seat *s, bool drop_not_started) {
627         assert(s);
628
629         if (drop_not_started && !s->started)
630                 return false;
631
632         if (seat_is_seat0(s))
633                 return true;
634
635         return seat_has_master_device(s);
636 }
637
638 void seat_add_to_gc_queue(Seat *s) {
639         assert(s);
640
641         if (s->in_gc_queue)
642                 return;
643
644         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
645         s->in_gc_queue = true;
646 }
647
648 static 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
657 bool 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
672         if (strlen(name) > 255)
673                 return false;
674
675         return true;
676 }