chiark / gitweb /
eac5a5f26b144de064d61ea0ea0a9183a52c248f
[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 "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 }