]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
f88738ab1676c769d8d6bbb3b87be6059f75be3d
[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_has_vts(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 if (old_active)
250 session_device_pause_all(old_active);
251
252 seat_apply_acls(s, old_active);
253
254 if (session && session->started) {
255 session_send_changed(session, "Active\0");
256 session_device_resume_all(session);
257 }
258
259 if (!session || session->started)
260 seat_send_changed(s, "ActiveSession\0");
261
262 seat_save(s);
263
264 if (session) {
265 session_save(session);
266 user_save(session->user);
267 }
268
269 if (old_active) {
270 session_save(old_active);
271 if (!session || session->user != old_active->user)
272 user_save(old_active->user);
273 }
274
275 return 0;
276 }
277
278 int seat_active_vt_changed(Seat *s, int vtnr) {
279 Session *i, *new_active = NULL;
280 int r;
281
282 assert(s);
283 assert(vtnr >= 1);
284
285 if (!seat_has_vts(s))
286 return -EINVAL;
287
288 log_debug("VT changed to %i", vtnr);
289
290 LIST_FOREACH(sessions_by_seat, i, s->sessions)
291 if (i->vtnr == vtnr) {
292 new_active = i;
293 break;
294 }
295
296 r = seat_set_active(s, new_active);
297 manager_spawn_autovt(s->manager, vtnr);
298
299 return r;
300 }
301
302 int seat_read_active_vt(Seat *s) {
303 char t[64];
304 ssize_t k;
305 int r, vtnr;
306
307 assert(s);
308
309 if (!seat_has_vts(s))
310 return 0;
311
312 lseek(s->manager->console_active_fd, SEEK_SET, 0);
313
314 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
315 if (k <= 0) {
316 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
317 return k < 0 ? -errno : -EIO;
318 }
319
320 t[k] = 0;
321 truncate_nl(t);
322
323 if (!startswith(t, "tty")) {
324 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
325 return -EIO;
326 }
327
328 r = safe_atoi(t+3, &vtnr);
329 if (r < 0) {
330 log_error("Failed to parse VT number %s", t+3);
331 return r;
332 }
333
334 if (vtnr <= 0) {
335 log_error("VT number invalid: %s", t+3);
336 return -EIO;
337 }
338
339 return seat_active_vt_changed(s, vtnr);
340 }
341
342 int seat_start(Seat *s) {
343 assert(s);
344
345 if (s->started)
346 return 0;
347
348 log_struct(LOG_INFO,
349 MESSAGE_ID(SD_MESSAGE_SEAT_START),
350 "SEAT_ID=%s", s->id,
351 "MESSAGE=New seat %s.", s->id,
352 NULL);
353
354 /* Initialize VT magic stuff */
355 seat_preallocate_vts(s);
356
357 /* Read current VT */
358 seat_read_active_vt(s);
359
360 s->started = true;
361
362 /* Save seat data */
363 seat_save(s);
364
365 seat_send_signal(s, true);
366
367 return 0;
368 }
369
370 int seat_stop(Seat *s) {
371 int r = 0;
372
373 assert(s);
374
375 if (s->started)
376 log_struct(LOG_INFO,
377 MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
378 "SEAT_ID=%s", s->id,
379 "MESSAGE=Removed seat %s.", s->id,
380 NULL);
381
382 seat_stop_sessions(s);
383
384 unlink(s->state_file);
385 seat_add_to_gc_queue(s);
386
387 if (s->started)
388 seat_send_signal(s, false);
389
390 s->started = false;
391
392 return r;
393 }
394
395 int seat_stop_sessions(Seat *s) {
396 Session *session;
397 int r = 0, k;
398
399 assert(s);
400
401 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
402 k = session_stop(session);
403 if (k < 0)
404 r = k;
405 }
406
407 return r;
408 }
409
410 int seat_attach_session(Seat *s, Session *session) {
411 assert(s);
412 assert(session);
413 assert(!session->seat);
414
415 session->seat = s;
416 LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
417
418 seat_send_changed(s, "Sessions\0");
419
420 /* On seats with VTs, the VT logic defines which session is active. On
421 * seats without VTs, we automatically activate the first session. */
422 if (!seat_has_vts(s) && !s->active)
423 seat_set_active(s, session);
424
425 return 0;
426 }
427
428 bool seat_has_vts(Seat *s) {
429 assert(s);
430
431 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
432 }
433
434 bool seat_is_seat0(Seat *s) {
435 assert(s);
436
437 return s->manager->seat0 == s;
438 }
439
440 bool seat_can_multi_session(Seat *s) {
441 assert(s);
442
443 return seat_has_vts(s);
444 }
445
446 bool seat_can_tty(Seat *s) {
447 assert(s);
448
449 return seat_has_vts(s);
450 }
451
452 bool seat_has_master_device(Seat *s) {
453 assert(s);
454
455 /* device list is ordered by "master" flag */
456 return !!s->devices && s->devices->master;
457 }
458
459 bool seat_can_graphical(Seat *s) {
460 assert(s);
461
462 return seat_has_master_device(s);
463 }
464
465 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
466 Session *session;
467 bool idle_hint = true;
468 dual_timestamp ts = { 0, 0 };
469
470 assert(s);
471
472 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
473 dual_timestamp k;
474 int ih;
475
476 ih = session_get_idle_hint(session, &k);
477 if (ih < 0)
478 return ih;
479
480 if (!ih) {
481 if (!idle_hint) {
482 if (k.monotonic > ts.monotonic)
483 ts = k;
484 } else {
485 idle_hint = false;
486 ts = k;
487 }
488 } else if (idle_hint) {
489
490 if (k.monotonic > ts.monotonic)
491 ts = k;
492 }
493 }
494
495 if (t)
496 *t = ts;
497
498 return idle_hint;
499 }
500
501 int seat_check_gc(Seat *s, bool drop_not_started) {
502 assert(s);
503
504 if (drop_not_started && !s->started)
505 return 0;
506
507 if (seat_is_seat0(s))
508 return 1;
509
510 return seat_has_master_device(s);
511 }
512
513 void seat_add_to_gc_queue(Seat *s) {
514 assert(s);
515
516 if (s->in_gc_queue)
517 return;
518
519 LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
520 s->in_gc_queue = true;
521 }
522
523 static bool seat_name_valid_char(char c) {
524 return
525 (c >= 'a' && c <= 'z') ||
526 (c >= 'A' && c <= 'Z') ||
527 (c >= '0' && c <= '9') ||
528 c == '-' ||
529 c == '_';
530 }
531
532 bool seat_name_is_valid(const char *name) {
533 const char *p;
534
535 assert(name);
536
537 if (!startswith(name, "seat"))
538 return false;
539
540 if (!name[4])
541 return false;
542
543 for (p = name; *p; p++)
544 if (!seat_name_valid_char(*p))
545 return false;
546
547 if (strlen(name) > 255)
548 return false;
549
550 return true;
551 }