chiark / gitweb /
813ab153e62b94e303ea0d70540560cefa49b45e
[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 <errno.h>
23 #include <fcntl.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "sd-messages.h"
28
29 #include "alloc-util.h"
30 #include "fd-util.h"
31 #include "fileio.h"
32 #include "formats-util.h"
33 #include "logind-acl.h"
34 #include "logind-seat.h"
35 #include "mkdir.h"
36 #include "parse-util.h"
37 #include "string-util.h"
38 #include "terminal-util.h"
39 #include "util.h"
40
41 Seat *seat_new(Manager *m, const char *id) {
42         Seat *s;
43
44         assert(m);
45         assert(id);
46
47         s = new0(Seat, 1);
48         if (!s)
49                 return NULL;
50
51         s->state_file = strappend("/run/systemd/seats/", id);
52         if (!s->state_file) {
53                 free(s);
54                 return NULL;
55         }
56
57         s->id = basename(s->state_file);
58         s->manager = m;
59
60         if (hashmap_put(m->seats, s->id, s) < 0) {
61                 free(s->state_file);
62                 free(s);
63                 return NULL;
64         }
65
66         return s;
67 }
68
69 void seat_free(Seat *s) {
70         assert(s);
71
72         if (s->in_gc_queue)
73                 LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
74
75         while (s->sessions)
76                 session_free(s->sessions);
77
78         assert(!s->active);
79
80         while (s->devices)
81                 device_free(s->devices);
82
83         hashmap_remove(s->manager->seats, s->id);
84
85         free(s->positions);
86         free(s->state_file);
87         free(s);
88 }
89
90 int seat_save(Seat *s) {
91         _cleanup_free_ char *temp_path = NULL;
92         _cleanup_fclose_ FILE *f = NULL;
93         int r;
94
95         assert(s);
96
97         if (!s->started)
98                 return 0;
99
100         r = mkdir_safe_label("/run/systemd/seats", 0755, 0, 0);
101         if (r < 0)
102                 goto fail;
103
104         r = fopen_temporary(s->state_file, &f, &temp_path);
105         if (r < 0)
106                 goto fail;
107
108         fchmod(fileno(f), 0644);
109
110         fprintf(f,
111                 "# This is private data. Do not parse.\n"
112                 "IS_SEAT0=%i\n"
113                 "CAN_MULTI_SESSION=%i\n"
114                 "CAN_TTY=%i\n"
115                 "CAN_GRAPHICAL=%i\n",
116                 seat_is_seat0(s),
117                 seat_can_multi_session(s),
118                 seat_can_tty(s),
119                 seat_can_graphical(s));
120
121         if (s->active) {
122                 assert(s->active->user);
123
124                 fprintf(f,
125                         "ACTIVE=%s\n"
126                         "ACTIVE_UID="UID_FMT"\n",
127                         s->active->id,
128                         s->active->user->uid);
129         }
130
131         if (s->sessions) {
132                 Session *i;
133
134                 fputs("SESSIONS=", f);
135                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
136                         fprintf(f,
137                                 "%s%c",
138                                 i->id,
139                                 i->sessions_by_seat_next ? ' ' : '\n');
140                 }
141
142                 fputs("UIDS=", f);
143                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
144                         fprintf(f,
145                                 UID_FMT"%c",
146                                 i->user->uid,
147                                 i->sessions_by_seat_next ? ' ' : '\n');
148         }
149
150         r = fflush_and_check(f);
151         if (r < 0)
152                 goto fail;
153
154         if (rename(temp_path, s->state_file) < 0) {
155                 r = -errno;
156                 goto fail;
157         }
158
159         return 0;
160
161 fail:
162         (void) unlink(s->state_file);
163
164         if (temp_path)
165                 (void) unlink(temp_path);
166
167         return log_error_errno(r, "Failed to save seat data %s: %m", s->state_file);
168 }
169
170 int seat_load(Seat *s) {
171         assert(s);
172
173         /* There isn't actually anything to read here ... */
174
175         return 0;
176 }
177
178 #if 0 /// UNNEEDED by elogind
179 static int vt_allocate(unsigned int vtnr) {
180         char p[sizeof("/dev/tty") + DECIMAL_STR_MAX(unsigned int)];
181         _cleanup_close_ int fd = -1;
182
183         assert(vtnr >= 1);
184
185         snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
186         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
187         if (fd < 0)
188                 return -errno;
189
190         return 0;
191 }
192
193 int seat_preallocate_vts(Seat *s) {
194         int r = 0;
195         unsigned i;
196
197         assert(s);
198         assert(s->manager);
199
200         log_debug("Preallocating VTs...");
201
202         if (s->manager->n_autovts <= 0)
203                 return 0;
204
205         if (!seat_has_vts(s))
206                 return 0;
207
208         for (i = 1; i <= s->manager->n_autovts; i++) {
209                 int q;
210
211                 q = vt_allocate(i);
212                 if (q < 0) {
213                         log_error_errno(q, "Failed to preallocate VT %u: %m", i);
214                         r = q;
215                 }
216         }
217
218         return r;
219 }
220 #endif // 0
221
222 int seat_apply_acls(Seat *s, Session *old_active) {
223         int r;
224
225         assert(s);
226
227         r = devnode_acl_all(s->manager->udev,
228                             s->id,
229                             false,
230                             !!old_active, old_active ? old_active->user->uid : 0,
231                             !!s->active, s->active ? s->active->user->uid : 0);
232
233         if (r < 0)
234                 log_error_errno(r, "Failed to apply ACLs: %m");
235
236         return r;
237 }
238
239 int seat_set_active(Seat *s, Session *session) {
240         Session *old_active;
241
242         assert(s);
243         assert(!session || session->seat == s);
244
245         if (session == s->active)
246                 return 0;
247
248         old_active = s->active;
249         s->active = session;
250
251         if (old_active) {
252                 session_device_pause_all(old_active);
253                 session_send_changed(old_active, "Active", NULL);
254         }
255
256         seat_apply_acls(s, old_active);
257
258         if (session && session->started) {
259                 session_send_changed(session, "Active", NULL);
260                 session_device_resume_all(session);
261         }
262
263         if (!session || session->started)
264                 seat_send_changed(s, "ActiveSession", NULL);
265
266         seat_save(s);
267
268         if (session) {
269                 session_save(session);
270                 user_save(session->user);
271         }
272
273         if (old_active) {
274                 session_save(old_active);
275                 if (!session || session->user != old_active->user)
276                         user_save(old_active->user);
277         }
278
279         return 0;
280 }
281
282 int seat_switch_to(Seat *s, unsigned int num) {
283         /* Public session positions skip 0 (there is only F1-F12). Maybe it
284          * will get reassigned in the future, so return error for now. */
285         if (num == 0)
286                 return -EINVAL;
287
288         if (num >= s->position_count || !s->positions[num]) {
289                 /* allow switching to unused VTs to trigger auto-activate */
290                 if (seat_has_vts(s) && num < 64)
291                         return chvt(num);
292
293                 return -EINVAL;
294         }
295
296         return session_activate(s->positions[num]);
297 }
298
299 int seat_switch_to_next(Seat *s) {
300         unsigned int start, i;
301
302         if (s->position_count == 0)
303                 return -EINVAL;
304
305         start = 1;
306         if (s->active && s->active->position > 0)
307                 start = s->active->position;
308
309         for (i = start + 1; i < s->position_count; ++i)
310                 if (s->positions[i])
311                         return session_activate(s->positions[i]);
312
313         for (i = 1; i < start; ++i)
314                 if (s->positions[i])
315                         return session_activate(s->positions[i]);
316
317         return -EINVAL;
318 }
319
320 int seat_switch_to_previous(Seat *s) {
321         unsigned int start, i;
322
323         if (s->position_count == 0)
324                 return -EINVAL;
325
326         start = 1;
327         if (s->active && s->active->position > 0)
328                 start = s->active->position;
329
330         for (i = start - 1; i > 0; --i)
331                 if (s->positions[i])
332                         return session_activate(s->positions[i]);
333
334         for (i = s->position_count - 1; i > start; --i)
335                 if (s->positions[i])
336                         return session_activate(s->positions[i]);
337
338         return -EINVAL;
339 }
340
341 int seat_active_vt_changed(Seat *s, unsigned int vtnr) {
342         Session *i, *new_active = NULL;
343         int r;
344
345         assert(s);
346         assert(vtnr >= 1);
347
348         if (!seat_has_vts(s))
349                 return -EINVAL;
350
351         log_debug("VT changed to %u", vtnr);
352
353         /* we might have earlier closing sessions on the same VT, so try to
354          * find a running one first */
355         LIST_FOREACH(sessions_by_seat, i, s->sessions)
356                 if (i->vtnr == vtnr && !i->stopping) {
357                         new_active = i;
358                         break;
359                 }
360
361         if (!new_active) {
362                 /* no running one? then we can't decide which one is the
363                  * active one, let the first one win */
364                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
365                         if (i->vtnr == vtnr) {
366                                 new_active = i;
367                                 break;
368                         }
369         }
370
371         r = seat_set_active(s, new_active);
372
373 #if 0 /// elogind does not spawn autovt
374         manager_spawn_autovt(s->manager, vtnr);
375 #endif // 0
376
377         return r;
378 }
379
380 int seat_read_active_vt(Seat *s) {
381         char t[64];
382         ssize_t k;
383         unsigned int vtnr;
384         int r;
385
386         assert(s);
387
388         if (!seat_has_vts(s))
389                 return 0;
390
391         lseek(s->manager->console_active_fd, SEEK_SET, 0);
392
393         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
394         if (k <= 0) {
395                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
396                 return k < 0 ? -errno : -EIO;
397         }
398
399         t[k] = 0;
400         truncate_nl(t);
401
402         if (!startswith(t, "tty")) {
403                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
404                 return -EIO;
405         }
406
407         r = safe_atou(t+3, &vtnr);
408         if (r < 0) {
409                 log_error("Failed to parse VT number %s", t+3);
410                 return r;
411         }
412
413         if (!vtnr) {
414                 log_error("VT number invalid: %s", t+3);
415                 return -EIO;
416         }
417
418         return seat_active_vt_changed(s, vtnr);
419 }
420
421 int seat_start(Seat *s) {
422         assert(s);
423
424         if (s->started)
425                 return 0;
426
427         log_struct(LOG_INFO,
428                    LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
429                    "SEAT_ID=%s", s->id,
430                    LOG_MESSAGE("New seat %s.", s->id),
431                    NULL);
432
433         /* Initialize VT magic stuff */
434 #if 0 /// elogind does not support autospawning vts
435         seat_preallocate_vts(s);
436 #endif // 0
437
438         /* Read current VT */
439         seat_read_active_vt(s);
440
441         s->started = true;
442
443         /* Save seat data */
444         seat_save(s);
445
446         seat_send_signal(s, true);
447
448         return 0;
449 }
450
451 int seat_stop(Seat *s, bool force) {
452         int r = 0;
453
454         assert(s);
455
456         if (s->started)
457                 log_struct(LOG_INFO,
458                            LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
459                            "SEAT_ID=%s", s->id,
460                            LOG_MESSAGE("Removed seat %s.", s->id),
461                            NULL);
462
463         seat_stop_sessions(s, force);
464
465         unlink(s->state_file);
466         seat_add_to_gc_queue(s);
467
468         if (s->started)
469                 seat_send_signal(s, false);
470
471         s->started = false;
472
473         return r;
474 }
475
476 int seat_stop_sessions(Seat *s, bool force) {
477         Session *session;
478         int r = 0, k;
479
480         assert(s);
481
482         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
483                 k = session_stop(session, force);
484                 if (k < 0)
485                         r = k;
486         }
487
488         return r;
489 }
490
491 void seat_evict_position(Seat *s, Session *session) {
492         Session *iter;
493         unsigned int pos = session->position;
494
495         session->position = 0;
496
497         if (pos == 0)
498                 return;
499
500         if (pos < s->position_count && s->positions[pos] == session) {
501                 s->positions[pos] = NULL;
502
503                 /* There might be another session claiming the same
504                  * position (eg., during gdm->session transition), so let's look
505                  * for it and set it on the free slot. */
506                 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
507                         if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
508                                 s->positions[pos] = iter;
509                                 break;
510                         }
511                 }
512         }
513 }
514
515 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
516         /* with VTs, the position is always the same as the VTnr */
517         if (seat_has_vts(s))
518                 pos = session->vtnr;
519
520         if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
521                 return;
522
523         seat_evict_position(s, session);
524
525         session->position = pos;
526         if (pos > 0)
527                 s->positions[pos] = session;
528 }
529
530 static void seat_assign_position(Seat *s, Session *session) {
531         unsigned int pos;
532
533         if (session->position > 0)
534                 return;
535
536         for (pos = 1; pos < s->position_count; ++pos)
537                 if (!s->positions[pos])
538                         break;
539
540         seat_claim_position(s, session, pos);
541 }
542
543 int seat_attach_session(Seat *s, Session *session) {
544         assert(s);
545         assert(session);
546         assert(!session->seat);
547
548         if (!seat_has_vts(s) != !session->vtnr)
549                 return -EINVAL;
550
551         session->seat = s;
552         LIST_PREPEND(sessions_by_seat, s->sessions, session);
553         seat_assign_position(s, session);
554
555         seat_send_changed(s, "Sessions", NULL);
556
557         /* On seats with VTs, the VT logic defines which session is active. On
558          * seats without VTs, we automatically activate new sessions. */
559         if (!seat_has_vts(s))
560                 seat_set_active(s, session);
561
562         return 0;
563 }
564
565 void seat_complete_switch(Seat *s) {
566         Session *session;
567
568         assert(s);
569
570         /* if no session-switch is pending or if it got canceled, do nothing */
571         if (!s->pending_switch)
572                 return;
573
574         session = s->pending_switch;
575         s->pending_switch = NULL;
576
577         seat_set_active(s, session);
578 }
579
580 bool seat_has_vts(Seat *s) {
581         assert(s);
582
583         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
584 }
585
586 bool seat_is_seat0(Seat *s) {
587         assert(s);
588
589         return s->manager->seat0 == s;
590 }
591
592 bool seat_can_multi_session(Seat *s) {
593         assert(s);
594
595         return seat_has_vts(s);
596 }
597
598 bool seat_can_tty(Seat *s) {
599         assert(s);
600
601         return seat_has_vts(s);
602 }
603
604 bool seat_has_master_device(Seat *s) {
605         assert(s);
606
607         /* device list is ordered by "master" flag */
608         return !!s->devices && s->devices->master;
609 }
610
611 bool seat_can_graphical(Seat *s) {
612         assert(s);
613
614         return seat_has_master_device(s);
615 }
616
617 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
618         Session *session;
619         bool idle_hint = true;
620         dual_timestamp ts = DUAL_TIMESTAMP_NULL;
621
622         assert(s);
623
624         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
625                 dual_timestamp k;
626                 int ih;
627
628                 ih = session_get_idle_hint(session, &k);
629                 if (ih < 0)
630                         return ih;
631
632                 if (!ih) {
633                         if (!idle_hint) {
634                                 if (k.monotonic > ts.monotonic)
635                                         ts = k;
636                         } else {
637                                 idle_hint = false;
638                                 ts = k;
639                         }
640                 } else if (idle_hint) {
641
642                         if (k.monotonic > ts.monotonic)
643                                 ts = k;
644                 }
645         }
646
647         if (t)
648                 *t = ts;
649
650         return idle_hint;
651 }
652
653 bool seat_check_gc(Seat *s, bool drop_not_started) {
654         assert(s);
655
656         if (drop_not_started && !s->started)
657                 return false;
658
659         if (seat_is_seat0(s))
660                 return true;
661
662         return seat_has_master_device(s);
663 }
664
665 void seat_add_to_gc_queue(Seat *s) {
666         assert(s);
667
668         if (s->in_gc_queue)
669                 return;
670
671         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
672         s->in_gc_queue = true;
673 }
674
675 static bool seat_name_valid_char(char c) {
676         return
677                 (c >= 'a' && c <= 'z') ||
678                 (c >= 'A' && c <= 'Z') ||
679                 (c >= '0' && c <= '9') ||
680                 c == '-' ||
681                 c == '_';
682 }
683
684 bool seat_name_is_valid(const char *name) {
685         const char *p;
686
687         assert(name);
688
689         if (!startswith(name, "seat"))
690                 return false;
691
692         if (!name[4])
693                 return false;
694
695         for (p = name; *p; p++)
696                 if (!seat_name_valid_char(*p))
697                         return false;
698
699         if (strlen(name) > 255)
700                 return false;
701
702         return true;
703 }