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