chiark / gitweb /
logind: implement generic multi-session
[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
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 void seat_complete_switch(Seat *s) {
429         Session *session;
430
431         assert(s);
432
433         /* if no session-switch is pending or if it got canceled, do nothing */
434         if (!s->pending_switch)
435                 return;
436
437         session = s->pending_switch;
438         s->pending_switch = NULL;
439
440         seat_set_active(s, session);
441 }
442
443 bool seat_has_vts(Seat *s) {
444         assert(s);
445
446         return seat_is_seat0(s) && s->manager->console_active_fd >= 0;
447 }
448
449 bool seat_is_seat0(Seat *s) {
450         assert(s);
451
452         return s->manager->seat0 == s;
453 }
454
455 bool seat_can_multi_session(Seat *s) {
456         assert(s);
457
458         return seat_has_vts(s);
459 }
460
461 bool seat_can_tty(Seat *s) {
462         assert(s);
463
464         return seat_has_vts(s);
465 }
466
467 bool seat_has_master_device(Seat *s) {
468         assert(s);
469
470         /* device list is ordered by "master" flag */
471         return !!s->devices && s->devices->master;
472 }
473
474 bool seat_can_graphical(Seat *s) {
475         assert(s);
476
477         return seat_has_master_device(s);
478 }
479
480 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
481         Session *session;
482         bool idle_hint = true;
483         dual_timestamp ts = { 0, 0 };
484
485         assert(s);
486
487         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
488                 dual_timestamp k;
489                 int ih;
490
491                 ih = session_get_idle_hint(session, &k);
492                 if (ih < 0)
493                         return ih;
494
495                 if (!ih) {
496                         if (!idle_hint) {
497                                 if (k.monotonic > ts.monotonic)
498                                         ts = k;
499                         } else {
500                                 idle_hint = false;
501                                 ts = k;
502                         }
503                 } else if (idle_hint) {
504
505                         if (k.monotonic > ts.monotonic)
506                                 ts = k;
507                 }
508         }
509
510         if (t)
511                 *t = ts;
512
513         return idle_hint;
514 }
515
516 int seat_check_gc(Seat *s, bool drop_not_started) {
517         assert(s);
518
519         if (drop_not_started && !s->started)
520                 return 0;
521
522         if (seat_is_seat0(s))
523                 return 1;
524
525         return seat_has_master_device(s);
526 }
527
528 void seat_add_to_gc_queue(Seat *s) {
529         assert(s);
530
531         if (s->in_gc_queue)
532                 return;
533
534         LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
535         s->in_gc_queue = true;
536 }
537
538 static bool seat_name_valid_char(char c) {
539         return
540                 (c >= 'a' && c <= 'z') ||
541                 (c >= 'A' && c <= 'Z') ||
542                 (c >= '0' && c <= '9') ||
543                 c == '-' ||
544                 c == '_';
545 }
546
547 bool seat_name_is_valid(const char *name) {
548         const char *p;
549
550         assert(name);
551
552         if (!startswith(name, "seat"))
553                 return false;
554
555         if (!name[4])
556                 return false;
557
558         for (p = name; *p; p++)
559                 if (!seat_name_valid_char(*p))
560                         return false;
561
562         if (strlen(name) > 255)
563                 return false;
564
565         return true;
566 }