]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/logind-seat.c
logind: make VT numbers unsigned
[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 "sd-id128.h"
31 #include "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 _cleanup_free_ char *temp_path = NULL;
88 _cleanup_fclose_ FILE *f = NULL;
89 int r;
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 finish:
155 if (r < 0)
156 log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
157
158 return r;
159 }
160
161 int seat_load(Seat *s) {
162 assert(s);
163
164 /* There isn't actually anything to read here ... */
165
166 return 0;
167 }
168
169 static int vt_allocate(unsigned int vtnr) {
170 _cleanup_free_ char *p = NULL;
171 _cleanup_close_ int fd = -1;
172
173 assert(vtnr >= 1);
174
175 if (asprintf(&p, "/dev/tty%u", vtnr) < 0)
176 return -ENOMEM;
177
178 fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
179 if (fd < 0)
180 return -errno;
181
182 return 0;
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_has_vts(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 if (old_active) {
243 session_device_pause_all(old_active);
244 session_send_changed(old_active, "Active", NULL);
245 }
246
247 seat_apply_acls(s, old_active);
248
249 if (session && session->started) {
250 session_send_changed(session, "Active", NULL);
251 session_device_resume_all(session);
252 }
253
254 if (!session || session->started)
255 seat_send_changed(s, "ActiveSession", NULL);
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, unsigned int vtnr) {
274 Session *i, *new_active = NULL;
275 int r;
276
277 assert(s);
278 assert(vtnr >= 1);
279
280 if (!seat_has_vts(s))
281 return -EINVAL;
282
283 log_debug("VT changed to %u", 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 unsigned int vtnr;
301 int r;
302
303 assert(s);
304
305 if (!seat_has_vts(s))
306 return 0;
307
308 lseek(s->manager->console_active_fd, SEEK_SET, 0);
309
310 k = read(s->manager->console_active_fd, t, sizeof(t)-1);
311 if (k <= 0) {
312 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
313 return k < 0 ? -errno : -EIO;
314 }
315
316 t[k] = 0;
317 truncate_nl(t);
318
319 if (!startswith(t, "tty")) {
320 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
321 return -EIO;
322 }
323
324 r = safe_atou(t+3, &vtnr);
325 if (r < 0) {
326 log_error("Failed to parse VT number %s", t+3);
327 return r;
328 }
329
330 if (!vtnr) {
331 log_error("VT number invalid: %s", t+3);
332 return -EIO;
333 }
334
335 return seat_active_vt_changed(s, vtnr);
336 }
337
338 int seat_start(Seat *s) {
339 assert(s);
340
341 if (s->started)
342 return 0;
343
344 log_struct(LOG_INFO,
345 MESSAGE_ID(SD_MESSAGE_SEAT_START),
346 "SEAT_ID=%s", s->id,
347 "MESSAGE=New seat %s.", s->id,
348 NULL);
349
350 /* Initialize VT magic stuff */
351 seat_preallocate_vts(s);
352
353 /* Read current VT */
354 seat_read_active_vt(s);
355
356 s->started = true;
357
358 /* Save seat data */
359 seat_save(s);
360
361 seat_send_signal(s, true);
362
363 return 0;
364 }
365
366 int seat_stop(Seat *s) {
367 int r = 0;
368
369 assert(s);
370
371 if (s->started)
372 log_struct(LOG_INFO,
373 MESSAGE_ID(SD_MESSAGE_SEAT_STOP),
374 "SEAT_ID=%s", s->id,
375 "MESSAGE=Removed seat %s.", s->id,
376 NULL);
377
378 seat_stop_sessions(s);
379
380 unlink(s->state_file);
381 seat_add_to_gc_queue(s);
382
383 if (s->started)
384 seat_send_signal(s, false);
385
386 s->started = false;
387
388 return r;
389 }
390
391 int seat_stop_sessions(Seat *s) {
392 Session *session;
393 int r = 0, k;
394
395 assert(s);
396
397 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
398 k = session_stop(session);
399 if (k < 0)
400 r = k;
401 }
402
403 return r;
404 }
405
406 int seat_attach_session(Seat *s, Session *session) {
407 assert(s);
408 assert(session);
409 assert(!session->seat);
410
411 session->seat = s;
412 LIST_PREPEND(sessions_by_seat, s->sessions, session);
413
414 seat_send_changed(s, "Sessions", NULL);
415
416 /* On seats with VTs, the VT logic defines which session is active. On
417 * seats without VTs, we automatically activate new sessions. */
418 if (!seat_has_vts(s))
419 seat_set_active(s, session);
420
421 return 0;
422 }
423
424 void seat_complete_switch(Seat *s) {
425 Session *session;
426
427 assert(s);
428
429 /* if no session-switch is pending or if it got canceled, do nothing */
430 if (!s->pending_switch)
431 return;
432
433 session = s->pending_switch;
434 s->pending_switch = NULL;
435
436 seat_set_active(s, session);
437 }
438
439 bool seat_has_vts(Seat *s) {
440 assert(s);
441
442 return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
443 }
444
445 bool seat_is_seat0(Seat *s) {
446 assert(s);
447
448 return s->manager->seat0 == s;
449 }
450
451 bool seat_can_multi_session(Seat *s) {
452 assert(s);
453
454 return seat_has_vts(s);
455 }
456
457 bool seat_can_tty(Seat *s) {
458 assert(s);
459
460 return seat_has_vts(s);
461 }
462
463 bool seat_has_master_device(Seat *s) {
464 assert(s);
465
466 /* device list is ordered by "master" flag */
467 return !!s->devices && s->devices->master;
468 }
469
470 bool seat_can_graphical(Seat *s) {
471 assert(s);
472
473 return seat_has_master_device(s);
474 }
475
476 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
477 Session *session;
478 bool idle_hint = true;
479 dual_timestamp ts = { 0, 0 };
480
481 assert(s);
482
483 LIST_FOREACH(sessions_by_seat, session, s->sessions) {
484 dual_timestamp k;
485 int ih;
486
487 ih = session_get_idle_hint(session, &k);
488 if (ih < 0)
489 return ih;
490
491 if (!ih) {
492 if (!idle_hint) {
493 if (k.monotonic > ts.monotonic)
494 ts = k;
495 } else {
496 idle_hint = false;
497 ts = k;
498 }
499 } else if (idle_hint) {
500
501 if (k.monotonic > ts.monotonic)
502 ts = k;
503 }
504 }
505
506 if (t)
507 *t = ts;
508
509 return idle_hint;
510 }
511
512 bool seat_check_gc(Seat *s, bool drop_not_started) {
513 assert(s);
514
515 if (drop_not_started && !s->started)
516 return false;
517
518 if (seat_is_seat0(s))
519 return true;
520
521 return seat_has_master_device(s);
522 }
523
524 void seat_add_to_gc_queue(Seat *s) {
525 assert(s);
526
527 if (s->in_gc_queue)
528 return;
529
530 LIST_PREPEND(gc_queue, s->manager->seat_gc_queue, s);
531 s->in_gc_queue = true;
532 }
533
534 static bool seat_name_valid_char(char c) {
535 return
536 (c >= 'a' && c <= 'z') ||
537 (c >= 'A' && c <= 'Z') ||
538 (c >= '0' && c <= '9') ||
539 c == '-' ||
540 c == '_';
541 }
542
543 bool seat_name_is_valid(const char *name) {
544 const char *p;
545
546 assert(name);
547
548 if (!startswith(name, "seat"))
549 return false;
550
551 if (!name[4])
552 return false;
553
554 for (p = name; *p; p++)
555 if (!seat_name_valid_char(*p))
556 return false;
557
558 if (strlen(name) > 255)
559 return false;
560
561 return true;
562 }