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