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