chiark / gitweb /
Prep v228: Add remaining updates from upstream (3/3)
[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 /// UNNEEDED by elogind
179 #if 0
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         snprintf(p, sizeof(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 /// elogind does not spawn autovt
375 #if 0
376         manager_spawn_autovt(s->manager, vtnr);
377 #endif // 0
378
379         return r;
380 }
381
382 int seat_read_active_vt(Seat *s) {
383         char t[64];
384         ssize_t k;
385         unsigned int vtnr;
386         int r;
387
388         assert(s);
389
390         if (!seat_has_vts(s))
391                 return 0;
392
393         lseek(s->manager->console_active_fd, SEEK_SET, 0);
394
395         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
396         if (k <= 0) {
397                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
398                 return k < 0 ? -errno : -EIO;
399         }
400
401         t[k] = 0;
402         truncate_nl(t);
403
404         if (!startswith(t, "tty")) {
405                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
406                 return -EIO;
407         }
408
409         r = safe_atou(t+3, &vtnr);
410         if (r < 0) {
411                 log_error("Failed to parse VT number %s", t+3);
412                 return r;
413         }
414
415         if (!vtnr) {
416                 log_error("VT number invalid: %s", t+3);
417                 return -EIO;
418         }
419
420         return seat_active_vt_changed(s, vtnr);
421 }
422
423 int seat_start(Seat *s) {
424         assert(s);
425
426         if (s->started)
427                 return 0;
428
429         log_struct(LOG_INFO,
430                    LOG_MESSAGE_ID(SD_MESSAGE_SEAT_START),
431                    "SEAT_ID=%s", s->id,
432                    LOG_MESSAGE("New seat %s.", s->id),
433                    NULL);
434
435         /* Initialize VT magic stuff */
436 /// elogind does not support autospawning vts
437 #if 0
438         seat_preallocate_vts(s);
439 #endif // 0
440
441         /* Read current VT */
442         seat_read_active_vt(s);
443
444         s->started = true;
445
446         /* Save seat data */
447         seat_save(s);
448
449         seat_send_signal(s, true);
450
451         return 0;
452 }
453
454 int seat_stop(Seat *s, bool force) {
455         int r = 0;
456
457         assert(s);
458
459         if (s->started)
460                 log_struct(LOG_INFO,
461                            LOG_MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
462                            "SEAT_ID=%s", s->id,
463                            LOG_MESSAGE("Removed seat %s.", s->id),
464                            NULL);
465
466         seat_stop_sessions(s, force);
467
468         unlink(s->state_file);
469         seat_add_to_gc_queue(s);
470
471         if (s->started)
472                 seat_send_signal(s, false);
473
474         s->started = false;
475
476         return r;
477 }
478
479 int seat_stop_sessions(Seat *s, bool force) {
480         Session *session;
481         int r = 0, k;
482
483         assert(s);
484
485         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
486                 k = session_stop(session, force);
487                 if (k < 0)
488                         r = k;
489         }
490
491         return r;
492 }
493
494 void seat_evict_position(Seat *s, Session *session) {
495         Session *iter;
496         unsigned int pos = session->position;
497
498         session->position = 0;
499
500         if (pos == 0)
501                 return;
502
503         if (pos < s->position_count && s->positions[pos] == session) {
504                 s->positions[pos] = NULL;
505
506                 /* There might be another session claiming the same
507                  * position (eg., during gdm->session transition), so let's look
508                  * for it and set it on the free slot. */
509                 LIST_FOREACH(sessions_by_seat, iter, s->sessions) {
510                         if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) {
511                                 s->positions[pos] = iter;
512                                 break;
513                         }
514                 }
515         }
516 }
517
518 void seat_claim_position(Seat *s, Session *session, unsigned int pos) {
519         /* with VTs, the position is always the same as the VTnr */
520         if (seat_has_vts(s))
521                 pos = session->vtnr;
522
523         if (!GREEDY_REALLOC0(s->positions, s->position_count, pos + 1))
524                 return;
525
526         seat_evict_position(s, session);
527
528         session->position = pos;
529         if (pos > 0)
530                 s->positions[pos] = session;
531 }
532
533 static void seat_assign_position(Seat *s, Session *session) {
534         unsigned int pos;
535
536         if (session->position > 0)
537                 return;
538
539         for (pos = 1; pos < s->position_count; ++pos)
540                 if (!s->positions[pos])
541                         break;
542
543         seat_claim_position(s, session, pos);
544 }
545
546 int seat_attach_session(Seat *s, Session *session) {
547         assert(s);
548         assert(session);
549         assert(!session->seat);
550
551         if (!seat_has_vts(s) != !session->vtnr)
552                 return -EINVAL;
553
554         session->seat = s;
555         LIST_PREPEND(sessions_by_seat, s->sessions, session);
556         seat_assign_position(s, session);
557
558         seat_send_changed(s, "Sessions", NULL);
559
560         /* On seats with VTs, the VT logic defines which session is active. On
561          * seats without VTs, we automatically activate new sessions. */
562         if (!seat_has_vts(s))
563                 seat_set_active(s, session);
564
565         return 0;
566 }
567
568 void seat_complete_switch(Seat *s) {
569         Session *session;
570
571         assert(s);
572
573         /* if no session-switch is pending or if it got canceled, do nothing */
574         if (!s->pending_switch)
575                 return;
576
577         session = s->pending_switch;
578         s->pending_switch = NULL;
579
580         seat_set_active(s, session);
581 }
582
583 bool seat_has_vts(Seat *s) {
584         assert(s);
585
586         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
587 }
588
589 bool seat_is_seat0(Seat *s) {
590         assert(s);
591
592         return s->manager->seat0 == s;
593 }
594
595 bool seat_can_multi_session(Seat *s) {
596         assert(s);
597
598         return seat_has_vts(s);
599 }
600
601 bool seat_can_tty(Seat *s) {
602         assert(s);
603
604         return seat_has_vts(s);
605 }
606
607 bool seat_has_master_device(Seat *s) {
608         assert(s);
609
610         /* device list is ordered by "master" flag */
611         return !!s->devices && s->devices->master;
612 }
613
614 bool seat_can_graphical(Seat *s) {
615         assert(s);
616
617         return seat_has_master_device(s);
618 }
619
620 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
621         Session *session;
622         bool idle_hint = true;
623         dual_timestamp ts = DUAL_TIMESTAMP_NULL;
624
625         assert(s);
626
627         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
628                 dual_timestamp k;
629                 int ih;
630
631                 ih = session_get_idle_hint(session, &k);
632                 if (ih < 0)
633                         return ih;
634
635                 if (!ih) {
636                         if (!idle_hint) {
637                                 if (k.monotonic > ts.monotonic)
638                                         ts = k;
639                         } else {
640                                 idle_hint = false;
641                                 ts = k;
642                         }
643                 } else if (idle_hint) {
644
645                         if (k.monotonic > ts.monotonic)
646                                 ts = k;
647                 }
648         }
649
650         if (t)
651                 *t = ts;
652
653         return idle_hint;
654 }
655
656 bool seat_check_gc(Seat *s, bool drop_not_started) {
657         assert(s);
658
659         if (drop_not_started && !s->started)
660                 return false;
661
662         if (seat_is_seat0(s))
663                 return true;
664
665         return seat_has_master_device(s);
666 }
667
668 void seat_add_to_gc_queue(Seat *s) {
669         assert(s);
670
671         if (s->in_gc_queue)
672                 return;
673
674         LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
675         s->in_gc_queue = true;
676 }
677
678 static bool seat_name_valid_char(char c) {
679         return
680                 (c >= 'a' && c <= 'z') ||
681                 (c >= 'A' && c <= 'Z') ||
682                 (c >= '0' && c <= '9') ||
683                 c == '-' ||
684                 c == '_';
685 }
686
687 bool seat_name_is_valid(const char *name) {
688         const char *p;
689
690         assert(name);
691
692         if (!startswith(name, "seat"))
693                 return false;
694
695         if (!name[4])
696                 return false;
697
698         for (p = name; *p; p++)
699                 if (!seat_name_valid_char(*p))
700                         return false;
701
702         if (strlen(name) > 255)
703                 return false;
704
705         return true;
706 }