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