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