chiark / gitweb /
logind: Ensure the user, seat and session files are updated when the session is closing.
[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_VTCONSOLE=%i\n"
109                 "CAN_MULTI_SESSION=%i\n"
110                 "CAN_TTY=%i\n"
111                 "CAN_GRAPHICAL=%i\n",
112                 seat_is_vtconsole(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_can_multi_session(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                 user_save(old_active->user);
267         }
268
269         return 0;
270 }
271
272 int seat_active_vt_changed(Seat *s, int vtnr) {
273         Session *i, *new_active = NULL;
274         int r;
275
276         assert(s);
277         assert(vtnr >= 1);
278
279         if (!seat_can_multi_session(s))
280                 return -EINVAL;
281
282         log_debug("VT changed to %i", vtnr);
283
284         LIST_FOREACH(sessions_by_seat, i, s->sessions)
285                 if (i->vtnr == vtnr) {
286                         new_active = i;
287                         break;
288                 }
289
290         r = seat_set_active(s, new_active);
291         manager_spawn_autovt(s->manager, vtnr);
292
293         return r;
294 }
295
296 int seat_read_active_vt(Seat *s) {
297         char t[64];
298         ssize_t k;
299         int r, vtnr;
300
301         assert(s);
302
303         if (!seat_can_multi_session(s))
304                 return 0;
305
306         lseek(s->manager->console_active_fd, SEEK_SET, 0);
307
308         k = read(s->manager->console_active_fd, t, sizeof(t)-1);
309         if (k <= 0) {
310                 log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
311                 return k < 0 ? -errno : -EIO;
312         }
313
314         t[k] = 0;
315         truncate_nl(t);
316
317         if (!startswith(t, "tty")) {
318                 log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
319                 return -EIO;
320         }
321
322         r = safe_atoi(t+3, &vtnr);
323         if (r < 0) {
324                 log_error("Failed to parse VT number %s", t+3);
325                 return r;
326         }
327
328         if (vtnr <= 0) {
329                 log_error("VT number invalid: %s", t+3);
330                 return -EIO;
331         }
332
333         return seat_active_vt_changed(s, vtnr);
334 }
335
336 int seat_start(Seat *s) {
337         assert(s);
338
339         if (s->started)
340                 return 0;
341
342         log_struct(LOG_INFO,
343                    "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_SEAT_START),
344                    "SEAT_ID=%s", s->id,
345                    "MESSAGE=New seat %s.", s->id,
346                    NULL);
347
348         /* Initialize VT magic stuff */
349         seat_preallocate_vts(s);
350
351         /* Read current VT */
352         seat_read_active_vt(s);
353
354         s->started = true;
355
356         /* Save seat data */
357         seat_save(s);
358
359         seat_send_signal(s, true);
360
361         return 0;
362 }
363
364 int seat_stop(Seat *s) {
365         int r = 0;
366
367         assert(s);
368
369         if (s->started)
370                 log_struct(LOG_INFO,
371                            "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_SEAT_STOP),
372                            "SEAT_ID=%s", s->id,
373                            "MESSAGE=Removed seat %s.", s->id,
374                            NULL);
375
376         seat_stop_sessions(s);
377
378         unlink(s->state_file);
379         seat_add_to_gc_queue(s);
380
381         if (s->started)
382                 seat_send_signal(s, false);
383
384         s->started = false;
385
386         return r;
387 }
388
389 int seat_stop_sessions(Seat *s) {
390         Session *session;
391         int r = 0, k;
392
393         assert(s);
394
395         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
396                 k = session_stop(session);
397                 if (k < 0)
398                         r = k;
399         }
400
401         return r;
402 }
403
404 int seat_attach_session(Seat *s, Session *session) {
405         assert(s);
406         assert(session);
407         assert(!session->seat);
408
409         session->seat = s;
410         LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
411
412         seat_send_changed(s, "Sessions\0");
413
414         /* Note that even if a seat is not multi-session capable it
415          * still might have multiple sessions on it since old, dead
416          * sessions might continue to be tracked until all their
417          * processes are gone. The most recently added session
418          * (i.e. the first in s->sessions) is the one that matters. */
419
420         if (!seat_can_multi_session(s))
421                 seat_set_active(s, session);
422
423         return 0;
424 }
425
426 bool seat_is_vtconsole(Seat *s) {
427         assert(s);
428
429         return s->manager->vtconsole == s;
430 }
431
432 bool seat_can_multi_session(Seat *s) {
433         assert(s);
434
435         if (!seat_is_vtconsole(s))
436                 return false;
437
438         /* If we can't watch which VT is in the foreground, we don't
439          * support VT switching */
440
441         return s->manager->console_active_fd >= 0;
442 }
443
444 bool seat_can_tty(Seat *s) {
445         assert(s);
446
447         return seat_is_vtconsole(s);
448 }
449
450 bool seat_can_graphical(Seat *s) {
451         assert(s);
452
453         return !!s->devices;
454 }
455
456 int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
457         Session *session;
458         bool idle_hint = true;
459         dual_timestamp ts = { 0, 0 };
460
461         assert(s);
462
463         LIST_FOREACH(sessions_by_seat, session, s->sessions) {
464                 dual_timestamp k;
465                 int ih;
466
467                 ih = session_get_idle_hint(session, &k);
468                 if (ih < 0)
469                         return ih;
470
471                 if (!ih) {
472                         if (!idle_hint) {
473                                 if (k.monotonic < ts.monotonic)
474                                         ts = k;
475                         } else {
476                                 idle_hint = false;
477                                 ts = k;
478                         }
479                 } else if (idle_hint) {
480
481                         if (k.monotonic > ts.monotonic)
482                                 ts = k;
483                 }
484         }
485
486         if (t)
487                 *t = ts;
488
489         return idle_hint;
490 }
491
492 int seat_check_gc(Seat *s, bool drop_not_started) {
493         assert(s);
494
495         if (drop_not_started && !s->started)
496                 return 0;
497
498         if (seat_is_vtconsole(s))
499                 return 1;
500
501         return !!s->devices;
502 }
503
504 void seat_add_to_gc_queue(Seat *s) {
505         assert(s);
506
507         if (s->in_gc_queue)
508                 return;
509
510         LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
511         s->in_gc_queue = true;
512 }
513
514 static bool seat_name_valid_char(char c) {
515         return
516                 (c >= 'a' && c <= 'z') ||
517                 (c >= 'A' && c <= 'Z') ||
518                 (c >= '0' && c <= '9') ||
519                 c == '-' ||
520                 c == '_';
521 }
522
523 bool seat_name_is_valid(const char *name) {
524         const char *p;
525
526         assert(name);
527
528         if (!startswith(name, "seat"))
529                 return false;
530
531         if (!name[4])
532                 return false;
533
534         for (p = name; *p; p++)
535                 if (!seat_name_valid_char(*p))
536                         return false;
537
538         if (strlen(name) > 255)
539                 return false;
540
541         return true;
542 }