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