chiark / gitweb /
755f20c03a8eb3658df78114a078aab871c45c17
[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 <assert.h>
23 #include <errno.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <sys/ioctl.h>
27 #include <linux/vt.h>
28 #include <string.h>
29
30 #include "logind-seat.h"
31 #include "logind-acl.h"
32 #include "util.h"
33 #include "mkdir.h"
34 #include "path-util.h"
35
36 Seat *seat_new(Manager *m, const char *id) {
37         Seat *s;
38
39         assert(m);
40         assert(id);
41
42         s = new0(Seat, 1);
43         if (!s)
44                 return NULL;
45
46         s->state_file = strappend("/run/systemd/seats/", id);
47         if (!s->state_file) {
48                 free(s);
49                 return NULL;
50         }
51
52         s->id = path_get_file_name(s->state_file);
53         s->manager = m;
54
55         if (hashmap_put(m->seats, s->id, s) < 0) {
56                 free(s->state_file);
57                 free(s);
58                 return NULL;
59         }
60
61         return s;
62 }
63
64 void seat_free(Seat *s) {
65         assert(s);
66
67         if (s->in_gc_queue)
68                 LIST_REMOVE(Seat, gc_queue, s->manager->seat_gc_queue, s);
69
70         while (s->sessions)
71                 session_free(s->sessions);
72
73         assert(!s->active);
74
75         while (s->devices)
76                 device_free(s->devices);
77
78         hashmap_remove(s->manager->seats, s->id);
79
80         free(s->state_file);
81         free(s);
82 }
83
84 int seat_save(Seat *s) {
85         int r;
86         FILE *f;
87         char *temp_path;
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_VTCONSOLE=%i\n"
107                 "CAN_MULTI_SESSION=%i\n",
108                 seat_is_vtconsole(s),
109                 seat_can_multi_session(s));
110
111         if (s->active) {
112                 assert(s->active->user);
113
114                 fprintf(f,
115                         "ACTIVE=%s\n"
116                         "ACTIVE_UID=%lu\n",
117                         s->active->id,
118                         (unsigned long) s->active->user->uid);
119         }
120
121         if (s->sessions) {
122                 Session *i;
123
124                 fputs("SESSIONS=", f);
125                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
126                         fprintf(f,
127                                 "%s%c",
128                                 i->id,
129                                 i->sessions_by_seat_next ? ' ' : '\n');
130                 }
131
132                 fputs("UIDS=", f);
133                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
134                         fprintf(f,
135                                 "%lu%c",
136                                 (unsigned long) i->user->uid,
137                                 i->sessions_by_seat_next ? ' ' : '\n');
138         }
139
140         fflush(f);
141
142         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
143                 r = -errno;
144                 unlink(s->state_file);
145                 unlink(temp_path);
146         }
147
148         fclose(f);
149         free(temp_path);
150
151 finish:
152         if (r < 0)
153                 log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
154
155         return r;
156 }
157
158 int seat_load(Seat *s) {
159         assert(s);
160
161         /* There isn't actually anything to read here ... */
162
163         return 0;
164 }
165
166 static int vt_allocate(int vtnr) {
167         int fd, r;
168         char *p;
169
170         assert(vtnr >= 1);
171
172         if (asprintf(&p, "/dev/tty%i", vtnr) < 0)
173                 return -ENOMEM;
174
175         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
176         free(p);
177
178         r = fd < 0 ? -errno : 0;
179
180         if (fd >= 0)
181                 close_nointr_nofail(fd);
182
183         return r;
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_can_multi_session(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("Failed to preallocate VT %i: %s", i, strerror(-q));
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("Failed to apply ACLs: %s", strerror(-r));
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         seat_apply_acls(s, old_active);
244
245         if (session && session->started)
246                 session_send_changed(session, "Active\0");
247
248         if (!session || session->started)
249                 seat_send_changed(s, "ActiveSession\0");
250
251         seat_save(s);
252
253         if (session) {
254                 session_save(session);
255                 user_save(session->user);
256         }
257
258         if (old_active) {
259                 session_save(old_active);
260                 user_save(old_active->user);
261         }
262
263         return 0;
264 }
265
266 int seat_active_vt_changed(Seat *s, int vtnr) {
267         Session *i, *new_active = NULL;
268         int r;
269
270         assert(s);
271         assert(vtnr >= 1);
272
273         if (!seat_can_multi_session(s))
274                 return -EINVAL;
275
276         log_debug("VT changed to %i", vtnr);
277
278         LIST_FOREACH(sessions_by_seat, i, s->sessions)
279                 if (i->vtnr == vtnr) {
280                         new_active = i;
281                         break;
282                 }
283
284         r = seat_set_active(s, new_active);
285         manager_spawn_autovt(s->manager, vtnr);
286
287         return r;
288 }
289
290 int seat_read_active_vt(Seat *s) {
291         char t[64];
292         ssize_t k;
293         int r, vtnr;
294
295         assert(s);
296
297         if (!seat_can_multi_session(s))
298                 return 0;
299
300         lseek(s->manager->console_active_fd, SEEK_SET, 0);
301
302         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
303         if (k <= 0) {
304                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
305                 return k < 0 ? -errno : -EIO;
306         }
307
308         t[k] = 0;
309         truncate_nl(t);
310
311         if (!startswith(t, "tty")) {
312                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
313                 return -EIO;
314         }
315
316         r = safe_atoi(t+3, &vtnr);
317         if (r < 0) {
318                 log_error("Failed to parse VT number %s", t+3);
319                 return r;
320         }
321
322         if (vtnr <= 0) {
323                 log_error("VT number invalid: %s", t+3);
324                 return -EIO;
325         }
326
327         return seat_active_vt_changed(s, vtnr);
328 }
329
330 int seat_start(Seat *s) {
331         assert(s);
332
333         if (s->started)
334                 return 0;
335
336         log_info("New seat %s.", s->id);
337
338         /* Initialize VT magic stuff */
339         seat_preallocate_vts(s);
340
341         /* Read current VT */
342         seat_read_active_vt(s);
343
344         s->started = true;
345
346         /* Save seat data */
347         seat_save(s);
348
349         seat_send_signal(s, true);
350
351         return 0;
352 }
353
354 int seat_stop(Seat *s) {
355         int r = 0;
356
357         assert(s);
358
359         if (s->started)
360                 log_info("Removed seat %s.", s->id);
361
362         seat_stop_sessions(s);
363
364         unlink(s->state_file);
365         seat_add_to_gc_queue(s);
366
367         if (s->started)
368                 seat_send_signal(s, false);
369
370         s->started = false;
371
372         return r;
373 }
374
375 int seat_stop_sessions(Seat *s) {
376         Session *session;
377         int r = 0, k;
378
379         assert(s);
380
381         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
382                 k = session_stop(session);
383                 if (k < 0)
384                         r = k;
385         }
386
387         return r;
388 }
389
390 int seat_attach_session(Seat *s, Session *session) {
391         assert(s);
392         assert(session);
393         assert(!session->seat);
394
395         session->seat = s;
396         LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
397
398         seat_send_changed(s, "Sessions\0");
399
400         /* Note that even if a seat is not multi-session capable it
401          * still might have multiple sessions on it since old, dead
402          * sessions might continue to be tracked until all their
403          * processes are gone. The most recently added session
404          * (i.e. the first in s->sessions) is the one that matters. */
405
406         if (!seat_can_multi_session(s))
407                 seat_set_active(s, session);
408
409         return 0;
410 }
411
412 bool seat_is_vtconsole(Seat *s) {
413         assert(s);
414
415         return s->manager->vtconsole == s;
416 }
417
418 bool seat_can_multi_session(Seat *s) {
419         assert(s);
420
421         if (!seat_is_vtconsole(s))
422                 return false;
423
424         /* If we can't watch which VT is in the foreground, we don't
425          * support VT switching */
426
427         return s->manager->console_active_fd >= 0;
428 }
429
430 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
431         Session *session;
432         bool idle_hint = true;
433         dual_timestamp ts = { 0, 0 };
434
435         assert(s);
436
437         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
438                 dual_timestamp k;
439                 int ih;
440
441                 ih = session_get_idle_hint(session, &k);
442                 if (ih < 0)
443                         return ih;
444
445                 if (!ih) {
446                         if (!idle_hint) {
447                                 if (k.monotonic < ts.monotonic)
448                                         ts = k;
449                         } else {
450                                 idle_hint = false;
451                                 ts = k;
452                         }
453                 } else if (idle_hint) {
454
455                         if (k.monotonic > ts.monotonic)
456                                 ts = k;
457                 }
458         }
459
460         if (t)
461                 *t = ts;
462
463         return idle_hint;
464 }
465
466 int seat_check_gc(Seat *s, bool drop_not_started) {
467         assert(s);
468
469         if (drop_not_started && !s->started)
470                 return 0;
471
472         if (seat_is_vtconsole(s))
473                 return 1;
474
475         return !!s->devices;
476 }
477
478 void seat_add_to_gc_queue(Seat *s) {
479         assert(s);
480
481         if (s->in_gc_queue)
482                 return;
483
484         LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
485         s->in_gc_queue = true;
486 }
487
488 static bool seat_name_valid_char(char c) {
489         return
490                 (c >= 'a' && c <= 'z') ||
491                 (c >= 'A' && c <= 'Z') ||
492                 (c >= '0' && c <= '9') ||
493                 c == '-' ||
494                 c == '_';
495 }
496
497 bool seat_name_is_valid(const char *name) {
498         const char *p;
499
500         assert(name);
501
502         if (!startswith(name, "seat"))
503                 return false;
504
505         if (!name[4])
506                 return false;
507
508         for (p = name; *p; p++)
509                 if (!seat_name_valid_char(*p))
510                         return false;
511
512         if (strlen(name) > 255)
513                 return false;
514
515         return true;
516 }