chiark / gitweb /
feebcf4558febcb23e44ccaf0b07f88f1ba0b832
[elogind.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                 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(Session, 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(Seat, 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 }