chiark / gitweb /
relicense to LGPLv2.1 (with exceptions)
[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
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 = file_name_from_path(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(Seat, 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->state_file);
80         free(s);
81 }
82
83 int seat_save(Seat *s) {
84         int r;
85         FILE *f;
86         char *temp_path;
87
88         assert(s);
89
90         if (!s->started)
91                 return 0;
92
93         r = safe_mkdir("/run/systemd/seats", 0755, 0, 0);
94         if (r < 0)
95                 goto finish;
96
97         r = fopen_temporary(s->state_file, &f, &temp_path);
98         if (r < 0)
99                 goto finish;
100
101         fchmod(fileno(f), 0644);
102
103         fprintf(f,
104                 "# This is private data. Do not parse.\n"
105                 "IS_VTCONSOLE=%i\n"
106                 "CAN_MULTI_SESSION=%i\n",
107                 seat_is_vtconsole(s),
108                 seat_can_multi_session(s));
109
110         if (s->active) {
111                 assert(s->active->user);
112
113                 fprintf(f,
114                         "ACTIVE=%s\n"
115                         "ACTIVE_UID=%lu\n",
116                         s->active->id,
117                         (unsigned long) s->active->user->uid);
118         }
119
120         if (s->sessions) {
121                 Session *i;
122
123                 fputs("SESSIONS=", f);
124                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
125                         fprintf(f,
126                                 "%s%c",
127                                 i->id,
128                                 i->sessions_by_seat_next ? ' ' : '\n');
129                 }
130
131                 fputs("UIDS=", f);
132                 LIST_FOREACH(sessions_by_seat, i, s->sessions)
133                         fprintf(f,
134                                 "%lu%c",
135                                 (unsigned long) i->user->uid,
136                                 i->sessions_by_seat_next ? ' ' : '\n');
137         }
138
139         fflush(f);
140
141         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
142                 r = -errno;
143                 unlink(s->state_file);
144                 unlink(temp_path);
145         }
146
147         fclose(f);
148         free(temp_path);
149
150 finish:
151         if (r < 0)
152                 log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
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(int vtnr) {
166         int fd, r;
167         char *p;
168
169         assert(vtnr >= 1);
170
171         if (asprintf(&p, "/dev/tty%i", vtnr) < 0)
172                 return -ENOMEM;
173
174         fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
175         free(p);
176
177         r = fd < 0 ? -errno : 0;
178
179         if (fd >= 0)
180                 close_nointr_nofail(fd);
181
182         return r;
183 }
184
185 int seat_preallocate_vts(Seat *s) {
186         int r = 0;
187         unsigned i;
188
189         assert(s);
190         assert(s->manager);
191
192         log_debug("Preallocating VTs...");
193
194         if (s->manager->n_autovts <= 0)
195                 return 0;
196
197         if (!seat_can_multi_session(s))
198                 return 0;
199
200         for (i = 1; i <= s->manager->n_autovts; i++) {
201                 int q;
202
203                 q = vt_allocate(i);
204                 if (q < 0) {
205                         log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
206                         r = q;
207                 }
208         }
209
210         return r;
211 }
212
213 int seat_apply_acls(Seat *s, Session *old_active) {
214         int r;
215
216         assert(s);
217
218         r = devnode_acl_all(s->manager->udev,
219                             s->id,
220                             false,
221                             !!old_active, old_active ? old_active->user->uid : 0,
222                             !!s->active, s->active ? s->active->user->uid : 0);
223
224         if (r < 0)
225                 log_error("Failed to apply ACLs: %s", strerror(-r));
226
227         return r;
228 }
229
230 int seat_set_active(Seat *s, Session *session) {
231         Session *old_active;
232
233         assert(s);
234         assert(!session || session->seat == s);
235
236         if (session == s->active)
237                 return 0;
238
239         old_active = s->active;
240         s->active = session;
241
242         seat_apply_acls(s, old_active);
243
244         if (session && session->started)
245                 session_send_changed(session, "Active\0");
246
247         if (!session || session->started)
248                 seat_send_changed(s, "ActiveSession\0");
249
250         seat_save(s);
251
252         if (session) {
253                 session_save(session);
254                 user_save(session->user);
255         }
256
257         if (old_active) {
258                 session_save(old_active);
259                 user_save(old_active->user);
260         }
261
262         return 0;
263 }
264
265 int seat_active_vt_changed(Seat *s, int vtnr) {
266         Session *i, *new_active = NULL;
267         int r;
268
269         assert(s);
270         assert(vtnr >= 1);
271
272         if (!seat_can_multi_session(s))
273                 return -EINVAL;
274
275         log_debug("VT changed to %i", vtnr);
276
277         LIST_FOREACH(sessions_by_seat, i, s->sessions)
278                 if (i->vtnr == vtnr) {
279                         new_active = i;
280                         break;
281                 }
282
283         r = seat_set_active(s, new_active);
284         manager_spawn_autovt(s->manager, vtnr);
285
286         return r;
287 }
288
289 int seat_read_active_vt(Seat *s) {
290         char t[64];
291         ssize_t k;
292         int r, vtnr;
293
294         assert(s);
295
296         if (!seat_can_multi_session(s))
297                 return 0;
298
299         lseek(s->manager->console_active_fd, SEEK_SET, 0);
300
301         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
302         if (k <= 0) {
303                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
304                 return k < 0 ? -errno : -EIO;
305         }
306
307         t[k] = 0;
308         truncate_nl(t);
309
310         if (!startswith(t, "tty")) {
311                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
312                 return -EIO;
313         }
314
315         r = safe_atoi(t+3, &vtnr);
316         if (r < 0) {
317                 log_error("Failed to parse VT number %s", t+3);
318                 return r;
319         }
320
321         if (vtnr <= 0) {
322                 log_error("VT number invalid: %s", t+3);
323                 return -EIO;
324         }
325
326         return seat_active_vt_changed(s, vtnr);
327 }
328
329 int seat_start(Seat *s) {
330         assert(s);
331
332         if (s->started)
333                 return 0;
334
335         log_info("New seat %s.", s->id);
336
337         /* Initialize VT magic stuff */
338         seat_preallocate_vts(s);
339
340         /* Read current VT */
341         seat_read_active_vt(s);
342
343         s->started = true;
344
345         /* Save seat data */
346         seat_save(s);
347
348         seat_send_signal(s, true);
349
350         return 0;
351 }
352
353 int seat_stop(Seat *s) {
354         int r = 0;
355
356         assert(s);
357
358         if (s->started)
359                 log_info("Removed seat %s.", s->id);
360
361         seat_stop_sessions(s);
362
363         unlink(s->state_file);
364         seat_add_to_gc_queue(s);
365
366         if (s->started)
367                 seat_send_signal(s, false);
368
369         s->started = false;
370
371         return r;
372 }
373
374 int seat_stop_sessions(Seat *s) {
375         Session *session;
376         int r = 0, k;
377
378         assert(s);
379
380         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
381                 k = session_stop(session);
382                 if (k < 0)
383                         r = k;
384         }
385
386         return r;
387 }
388
389 int seat_attach_session(Seat *s, Session *session) {
390         assert(s);
391         assert(session);
392         assert(!session->seat);
393
394         session->seat = s;
395         LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
396
397         seat_send_changed(s, "Sessions\0");
398
399         /* Note that even if a seat is not multi-session capable it
400          * still might have multiple sessions on it since old, dead
401          * sessions might continue to be tracked until all their
402          * processes are gone. The most recently added session
403          * (i.e. the first in s->sessions) is the one that matters. */
404
405         if (!seat_can_multi_session(s))
406                 seat_set_active(s, session);
407
408         return 0;
409 }
410
411 bool seat_is_vtconsole(Seat *s) {
412         assert(s);
413
414         return s->manager->vtconsole == s;
415 }
416
417 bool seat_can_multi_session(Seat *s) {
418         assert(s);
419
420         if (!seat_is_vtconsole(s))
421                 return false;
422
423         /* If we can't watch which VT is in the foreground, we don't
424          * support VT switching */
425
426         return s->manager->console_active_fd >= 0;
427 }
428
429 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
430         Session *session;
431         bool idle_hint = true;
432         dual_timestamp ts = { 0, 0 };
433
434         assert(s);
435
436         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
437                 dual_timestamp k;
438                 int ih;
439
440                 ih = session_get_idle_hint(session, &k);
441                 if (ih < 0)
442                         return ih;
443
444                 if (!ih) {
445                         if (!idle_hint) {
446                                 if (k.monotonic < ts.monotonic)
447                                         ts = k;
448                         } else {
449                                 idle_hint = false;
450                                 ts = k;
451                         }
452                 } else if (idle_hint) {
453
454                         if (k.monotonic > ts.monotonic)
455                                 ts = k;
456                 }
457         }
458
459         if (t)
460                 *t = ts;
461
462         return idle_hint;
463 }
464
465 int seat_check_gc(Seat *s, bool drop_not_started) {
466         assert(s);
467
468         if (drop_not_started && !s->started)
469                 return 0;
470
471         if (seat_is_vtconsole(s))
472                 return 1;
473
474         return !!s->devices;
475 }
476
477 void seat_add_to_gc_queue(Seat *s) {
478         assert(s);
479
480         if (s->in_gc_queue)
481                 return;
482
483         LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
484         s->in_gc_queue = true;
485 }
486
487 static bool seat_name_valid_char(char c) {
488         return
489                 (c >= 'a' && c <= 'z') ||
490                 (c >= 'A' && c <= 'Z') ||
491                 (c >= '0' && c <= '9') ||
492                 c == '-' ||
493                 c == '_';
494 }
495
496 bool seat_name_is_valid(const char *name) {
497         const char *p;
498
499         assert(name);
500
501         if (!startswith(name, "seat"))
502                 return false;
503
504         if (!name[4])
505                 return false;
506
507         for (p = name; *p; p++)
508                 if (!seat_name_valid_char(*p))
509                         return false;
510
511         if (strlen(name) > 255)
512                 return false;
513
514         return true;
515 }