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