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