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