chiark / gitweb /
b8f18c47a33bc715327e39369730a756c9430593
[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=%lu\n",
124                         s->active->id,
125                         (unsigned long) 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                                 "%lu%c",
143                                 (unsigned long) 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 for %s: %s", s->id, 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         _cleanup_free_ char *p = NULL;
172         _cleanup_close_ int fd = -1;
173
174         assert(vtnr >= 1);
175
176         if (asprintf(&p, "/dev/tty%u", vtnr) < 0)
177                 return -ENOMEM;
178
179         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
180         if (fd < 0)
181                 return -errno;
182
183         return 0;
184 }
185
186 int seat_preallocate_vts(Seat *s) {
187         int r = 0;
188         unsigned i;
189
190         assert(s);
191         assert(s->manager);
192
193         log_debug("Preallocating VTs...");
194
195         if (s->manager->n_autovts <= 0)
196                 return 0;
197
198         if (!seat_has_vts(s))
199                 return 0;
200
201         for (i = 1; i <= s->manager->n_autovts; i++) {
202                 int q;
203
204                 q = vt_allocate(i);
205                 if (q < 0) {
206                         log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
207                         r = q;
208                 }
209         }
210
211         return r;
212 }
213
214 int seat_apply_acls(Seat *s, Session *old_active) {
215         int r;
216
217         assert(s);
218
219         r = devnode_acl_all(s->manager->udev,
220                             s->id,
221                             false,
222                             !!old_active, old_active ? old_active->user->uid : 0,
223                             !!s->active, s->active ? s->active->user->uid : 0);
224
225         if (r < 0)
226                 log_error("Failed to apply ACLs: %s", strerror(-r));
227
228         return r;
229 }
230
231 int seat_set_active(Seat *s, Session *session) {
232         Session *old_active;
233
234         assert(s);
235         assert(!session || session->seat == s);
236
237         if (session == s->active)
238                 return 0;
239
240         old_active = s->active;
241         s->active = session;
242
243         if (old_active) {
244                 session_device_pause_all(old_active);
245                 session_send_changed(old_active, "Active", NULL);
246         }
247
248         seat_apply_acls(s, old_active);
249
250         if (session && session->started) {
251                 session_send_changed(session, "Active", NULL);
252                 session_device_resume_all(session);
253         }
254
255         if (!session || session->started)
256                 seat_send_changed(s, "ActiveSession", NULL);
257
258         seat_save(s);
259
260         if (session) {
261                 session_save(session);
262                 user_save(session->user);
263         }
264
265         if (old_active) {
266                 session_save(old_active);
267                 if (!session || session->user != old_active->user)
268                         user_save(old_active->user);
269         }
270
271         return 0;
272 }
273
274 int seat_switch_to(Seat *s, unsigned int num) {
275         /* Public session positions skip 0 (there is only F1-F12). Maybe it
276          * will get reassigned in the future, so return error for now. */
277         if (!num)
278                 return -EINVAL;
279
280         if (num >= s->position_count || !s->positions[num])
281                 return -EINVAL;
282
283         return session_activate(s->positions[num]);
284 }
285
286 int seat_switch_to_next(Seat *s) {
287         unsigned int start, i;
288
289         if (!s->position_count)
290                 return -EINVAL;
291
292         start = 1;
293         if (s->active && s->active->pos > 0)
294                 start = s->active->pos;
295
296         for (i = start + 1; i < s->position_count; ++i)
297                 if (s->positions[i])
298                         return session_activate(s->positions[i]);
299
300         for (i = 1; i < start; ++i)
301                 if (s->positions[i])
302                         return session_activate(s->positions[i]);
303
304         return -EINVAL;
305 }
306
307 int seat_switch_to_previous(Seat *s) {
308         unsigned int start, i;
309
310         if (!s->position_count)
311                 return -EINVAL;
312
313         start = 1;
314         if (s->active && s->active->pos > 0)
315                 start = s->active->pos;
316
317         for (i = start - 1; i > 0; --i)
318                 if (s->positions[i])
319                         return session_activate(s->positions[i]);
320
321         for (i = s->position_count - 1; i > start; --i)
322                 if (s->positions[i])
323                         return session_activate(s->positions[i]);
324
325         return -EINVAL;
326 }
327
328 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
329         Session *i, *new_active = NULL;
330         int r;
331
332         assert(s);
333         assert(vtnr >= 1);
334
335         if (!seat_has_vts(s))
336                 return -EINVAL;
337
338         log_debug("VT changed to %u", vtnr);
339
340         LIST_FOREACH(sessions_by_seat, i, s->sessions)
341                 if (i->vtnr == vtnr) {
342                         new_active = i;
343                         break;
344                 }
345
346         r = seat_set_active(s, new_active);
347         manager_spawn_autovt(s->manager, vtnr);
348
349         return r;
350 }
351
352 int seat_read_active_vt(Seat *s) {
353         char t[64];
354         ssize_t k;
355         unsigned int vtnr;
356         int r;
357
358         assert(s);
359
360         if (!seat_has_vts(s))
361                 return 0;
362
363         lseek(s->manager->console_active_fd, SEEK_SET, 0);
364
365         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
366         if (k <= 0) {
367                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
368                 return k < 0 ? -errno : -EIO;
369         }
370
371         t[k] = 0;
372         truncate_nl(t);
373
374         if (!startswith(t, "tty")) {
375                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
376                 return -EIO;
377         }
378
379         r = safe_atou(t+3, &vtnr);
380         if (r < 0) {
381                 log_error("Failed to parse VT number %s", t+3);
382                 return r;
383         }
384
385         if (!vtnr) {
386                 log_error("VT number invalid: %s", t+3);
387                 return -EIO;
388         }
389
390         return seat_active_vt_changed(s, vtnr);
391 }
392
393 int seat_start(Seat *s) {
394         assert(s);
395
396         if (s->started)
397                 return 0;
398
399         log_struct(LOG_INFO,
400                    MESSAGE_ID(SD_MESSAGE_SEAT_START),
401                    "SEAT_ID=%s", s->id,
402                    "MESSAGE=New seat %s.", s->id,
403                    NULL);
404
405         /* Initialize VT magic stuff */
406         seat_preallocate_vts(s);
407
408         /* Read current VT */
409         seat_read_active_vt(s);
410
411         s->started = true;
412
413         /* Save seat data */
414         seat_save(s);
415
416         seat_send_signal(s, true);
417
418         return 0;
419 }
420
421 int seat_stop(Seat *s) {
422         int r = 0;
423
424         assert(s);
425
426         if (s->started)
427                 log_struct(LOG_INFO,
428                            MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
429                            "SEAT_ID=%s", s->id,
430                            "MESSAGE=Removed seat %s.", s->id,
431                            NULL);
432
433         seat_stop_sessions(s);
434
435         unlink(s->state_file);
436         seat_add_to_gc_queue(s);
437
438         if (s->started)
439                 seat_send_signal(s, false);
440
441         s->started = false;
442
443         return r;
444 }
445
446 int seat_stop_sessions(Seat *s) {
447         Session *session;
448         int r = 0, k;
449
450         assert(s);
451
452         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
453                 k = session_stop(session);
454                 if (k < 0)
455                         r = k;
456         }
457
458         return r;
459 }
460
461 void seat_evict_position(Seat *s, Session *session) {
462         unsigned int pos = session->pos;
463
464         session->pos = 0;
465
466         if (!pos)
467                 return;
468
469         if (pos < s->position_count && s->positions[pos] == session)
470                 s->positions[pos] = NULL;
471 }
472
473 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
474         /* with VTs, the position is always the same as the VTnr */
475         if (seat_has_vts(s))
476                 pos = session->vtnr;
477
478         if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
479                 return;
480
481         seat_evict_position(s, session);
482
483         session->pos = pos;
484         if (pos > 0 && !s->positions[pos])
485                 s->positions[pos] = session;
486 }
487
488 static void seat_assign_position(Seat *s, Session *session) {
489         unsigned int pos;
490
491         if (session->pos > 0)
492                 return;
493
494         for (pos = 1; pos < s->position_count; ++pos)
495                 if (!s->positions[pos])
496                         break;
497
498         seat_claim_position(s, session, pos);
499 }
500
501 int seat_attach_session(Seat *s, Session *session) {
502         assert(s);
503         assert(session);
504         assert(!session->seat);
505
506         if (!seat_has_vts(s) != !session->vtnr)
507                 return -EINVAL;
508
509         session->seat = s;
510         LIST_PREPEND(sessions_by_seat, s->sessions, session);
511         seat_assign_position(s, session);
512
513         seat_send_changed(s, "Sessions", NULL);
514
515         /* On seats with VTs, the VT logic defines which session is active. On
516          * seats without VTs, we automatically activate new sessions. */
517         if (!seat_has_vts(s))
518                 seat_set_active(s, session);
519
520         return 0;
521 }
522
523 void seat_complete_switch(Seat *s) {
524         Session *session;
525
526         assert(s);
527
528         /* if no session-switch is pending or if it got canceled, do nothing */
529         if (!s->pending_switch)
530                 return;
531
532         session = s->pending_switch;
533         s->pending_switch = NULL;
534
535         seat_set_active(s, session);
536 }
537
538 bool seat_has_vts(Seat *s) {
539         assert(s);
540
541         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
542 }
543
544 bool seat_is_seat0(Seat *s) {
545         assert(s);
546
547         return s->manager->seat0 == s;
548 }
549
550 bool seat_can_multi_session(Seat *s) {
551         assert(s);
552
553         return seat_has_vts(s);
554 }
555
556 bool seat_can_tty(Seat *s) {
557         assert(s);
558
559         return seat_has_vts(s);
560 }
561
562 bool seat_has_master_device(Seat *s) {
563         assert(s);
564
565         /* device list is ordered by "master" flag */
566         return !!s->devices && s->devices->master;
567 }
568
569 bool seat_can_graphical(Seat *s) {
570         assert(s);
571
572         return seat_has_master_device(s);
573 }
574
575 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
576         Session *session;
577         bool idle_hint = true;
578         dual_timestamp ts = { 0, 0 };
579
580         assert(s);
581
582         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
583                 dual_timestamp k;
584                 int ih;
585
586                 ih = session_get_idle_hint(session, &k);
587                 if (ih < 0)
588                         return ih;
589
590                 if (!ih) {
591                         if (!idle_hint) {
592                                 if (k.monotonic > ts.monotonic)
593                                         ts = k;
594                         } else {
595                                 idle_hint = false;
596                                 ts = k;
597                         }
598                 } else if (idle_hint) {
599
600                         if (k.monotonic > ts.monotonic)
601                                 ts = k;
602                 }
603         }
604
605         if (t)
606                 *t = ts;
607
608         return idle_hint;
609 }
610
611 bool seat_check_gc(Seat *s, bool drop_not_started) {
612         assert(s);
613
614         if (drop_not_started && !s->started)
615                 return false;
616
617         if (seat_is_seat0(s))
618                 return true;
619
620         return seat_has_master_device(s);
621 }
622
623 void seat_add_to_gc_queue(Seat *s) {
624         assert(s);
625
626         if (s->in_gc_queue)
627                 return;
628
629         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
630         s->in_gc_queue = true;
631 }
632
633 static bool seat_name_valid_char(char c) {
634         return
635                 (c >= 'a' && c <= 'z') ||
636                 (c >= 'A' && c <= 'Z') ||
637                 (c >= '0' && c <= '9') ||
638                 c == '-' ||
639                 c == '_';
640 }
641
642 bool seat_name_is_valid(const char *name) {
643         const char *p;
644
645         assert(name);
646
647         if (!startswith(name, "seat"))
648                 return false;
649
650         if (!name[4])
651                 return false;
652
653         for (p = name; *p; p++)
654                 if (!seat_name_valid_char(*p))
655                         return false;
656
657         if (strlen(name) > 255)
658                 return false;
659
660         return true;
661 }