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