]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
relicense to LGPLv2.1 (with exceptions)
[thirdparty/systemd.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 }