chiark / gitweb /
126c5b84cc5b4f4ade8dddfb53174c635ddb6754
[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_errno(r, "Failed to save seat data %s: %m", s->state_file);
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_errno(q, "Failed to preallocate VT %u: %m", i);
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_errno(r, "Failed to apply ACLs: %m");
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         /* we might have earlier closing sessions on the same VT, so try to
344          * find a running one first */
345         LIST_FOREACH(sessions_by_seat, i, s->sessions)
346                 if (i->vtnr == vtnr && !i->stopping) {
347                         new_active = i;
348                         break;
349                 }
350
351         if (!new_active) {
352                 /* no running one? then we can't decide which one is the
353                  * active one, let the first one win */
354                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
355                         if (i->vtnr == vtnr) {
356                                 new_active = i;
357                                 break;
358                         }
359         }
360
361         r = seat_set_active(s, new_active);
362         manager_spawn_autovt(s->manager, vtnr);
363
364         return r;
365 }
366
367 int seat_read_active_vt(Seat *s) {
368         char t[64];
369         ssize_t k;
370         unsigned int vtnr;
371         int r;
372
373         assert(s);
374
375         if (!seat_has_vts(s))
376                 return 0;
377
378         lseek(s->manager->console_active_fd, SEEK_SET, 0);
379
380         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
381         if (k <= 0) {
382                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
383                 return k < 0 ? -errno : -EIO;
384         }
385
386         t[k] = 0;
387         truncate_nl(t);
388
389         if (!startswith(t, "tty")) {
390                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
391                 return -EIO;
392         }
393
394         r = safe_atou(t+3, &vtnr);
395         if (r < 0) {
396                 log_error("Failed to parse VT number %s", t+3);
397                 return r;
398         }
399
400         if (!vtnr) {
401                 log_error("VT number invalid: %s", t+3);
402                 return -EIO;
403         }
404
405         return seat_active_vt_changed(s, vtnr);
406 }
407
408 int seat_start(Seat *s) {
409         assert(s);
410
411         if (s->started)
412                 return 0;
413
414         log_struct(LOG_INFO,
415                    LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
416                    "SEAT_ID=%s", s->id,
417                    LOG_MESSAGE("New seat %s.", s->id),
418                    NULL);
419
420         /* Initialize VT magic stuff */
421         seat_preallocate_vts(s);
422
423         /* Read current VT */
424         seat_read_active_vt(s);
425
426         s->started = true;
427
428         /* Save seat data */
429         seat_save(s);
430
431         seat_send_signal(s, true);
432
433         return 0;
434 }
435
436 int seat_stop(Seat *s, bool force) {
437         int r = 0;
438
439         assert(s);
440
441         if (s->started)
442                 log_struct(LOG_INFO,
443                            LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
444                            "SEAT_ID=%s", s->id,
445                            LOG_MESSAGE("Removed seat %s.", s->id),
446                            NULL);
447
448         seat_stop_sessions(s, force);
449
450         unlink(s->state_file);
451         seat_add_to_gc_queue(s);
452
453         if (s->started)
454                 seat_send_signal(s, false);
455
456         s->started = false;
457
458         return r;
459 }
460
461 int seat_stop_sessions(Seat *s, bool force) {
462         Session *session;
463         int r = 0, k;
464
465         assert(s);
466
467         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
468                 k = session_stop(session, force);
469                 if (k < 0)
470                         r = k;
471         }
472
473         return r;
474 }
475
476 void seat_evict_position(Seat *s, Session *session) {
477         Session *iter;
478         unsigned int pos = session->pos;
479
480         session->pos = 0;
481
482         if (!pos)
483                 return;
484
485         if (pos < s->position_count && s->positions[pos] == session) {
486                 s->positions[pos] = NULL;
487
488                 /* There might be another session claiming the same
489                  * position (eg., during gdm->session transition), so lets look
490                  * for it and set it on the free slot. */
491                 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
492                         if (iter->pos == pos) {
493                                 s->positions[pos] = iter;
494                                 break;
495                         }
496                 }
497         }
498 }
499
500 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
501         /* with VTs, the position is always the same as the VTnr */
502         if (seat_has_vts(s))
503                 pos = session->vtnr;
504
505         if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
506                 return;
507
508         seat_evict_position(s, session);
509
510         session->pos = pos;
511         if (pos > 0 && !s->positions[pos])
512                 s->positions[pos] = session;
513 }
514
515 static void seat_assign_position(Seat *s, Session *session) {
516         unsigned int pos;
517
518         if (session->pos > 0)
519                 return;
520
521         for (pos = 1; pos < s->position_count; ++pos)
522                 if (!s->positions[pos])
523                         break;
524
525         seat_claim_position(s, session, pos);
526 }
527
528 int seat_attach_session(Seat *s, Session *session) {
529         assert(s);
530         assert(session);
531         assert(!session->seat);
532
533         if (!seat_has_vts(s) != !session->vtnr)
534                 return -EINVAL;
535
536         session->seat = s;
537         LIST_PREPEND(sessions_by_seat, s->sessions, session);
538         seat_assign_position(s, session);
539
540         seat_send_changed(s, "Sessions", NULL);
541
542         /* On seats with VTs, the VT logic defines which session is active. On
543          * seats without VTs, we automatically activate new sessions. */
544         if (!seat_has_vts(s))
545                 seat_set_active(s, session);
546
547         return 0;
548 }
549
550 void seat_complete_switch(Seat *s) {
551         Session *session;
552
553         assert(s);
554
555         /* if no session-switch is pending or if it got canceled, do nothing */
556         if (!s->pending_switch)
557                 return;
558
559         session = s->pending_switch;
560         s->pending_switch = NULL;
561
562         seat_set_active(s, session);
563 }
564
565 bool seat_has_vts(Seat *s) {
566         assert(s);
567
568         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
569 }
570
571 bool seat_is_seat0(Seat *s) {
572         assert(s);
573
574         return s->manager->seat0 == s;
575 }
576
577 bool seat_can_multi_session(Seat *s) {
578         assert(s);
579
580         return seat_has_vts(s);
581 }
582
583 bool seat_can_tty(Seat *s) {
584         assert(s);
585
586         return seat_has_vts(s);
587 }
588
589 bool seat_has_master_device(Seat *s) {
590         assert(s);
591
592         /* device list is ordered by "master" flag */
593         return !!s->devices && s->devices->master;
594 }
595
596 bool seat_can_graphical(Seat *s) {
597         assert(s);
598
599         return seat_has_master_device(s);
600 }
601
602 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
603         Session *session;
604         bool idle_hint = true;
605         dual_timestamp ts = { 0, 0 };
606
607         assert(s);
608
609         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
610                 dual_timestamp k;
611                 int ih;
612
613                 ih = session_get_idle_hint(session, &k);
614                 if (ih < 0)
615                         return ih;
616
617                 if (!ih) {
618                         if (!idle_hint) {
619                                 if (k.monotonic > ts.monotonic)
620                                         ts = k;
621                         } else {
622                                 idle_hint = false;
623                                 ts = k;
624                         }
625                 } else if (idle_hint) {
626
627                         if (k.monotonic > ts.monotonic)
628                                 ts = k;
629                 }
630         }
631
632         if (t)
633                 *t = ts;
634
635         return idle_hint;
636 }
637
638 bool seat_check_gc(Seat *s, bool drop_not_started) {
639         assert(s);
640
641         if (drop_not_started && !s->started)
642                 return false;
643
644         if (seat_is_seat0(s))
645                 return true;
646
647         return seat_has_master_device(s);
648 }
649
650 void seat_add_to_gc_queue(Seat *s) {
651         assert(s);
652
653         if (s->in_gc_queue)
654                 return;
655
656         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
657         s->in_gc_queue = true;
658 }
659
660 static bool seat_name_valid_char(char c) {
661         return
662                 (c >= 'a' && c <= 'z') ||
663                 (c >= 'A' && c <= 'Z') ||
664                 (c >= '0' && c <= '9') ||
665                 c == '-' ||
666                 c == '_';
667 }
668
669 bool seat_name_is_valid(const char *name) {
670         const char *p;
671
672         assert(name);
673
674         if (!startswith(name, "seat"))
675                 return false;
676
677         if (!name[4])
678                 return false;
679
680         for (p = name; *p; p++)
681                 if (!seat_name_valid_char(*p))
682                         return false;
683
684         if (strlen(name) > 255)
685                 return false;
686
687         return true;
688 }