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