chiark / gitweb /
logind: send dbus signals when sessions/users/seats come and go
[elogind.git] / src / logind-session.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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include "logind-session.h"
27 #include "strv.h"
28 #include "util.h"
29 #include "cgroup-util.h"
30
31 #define IDLE_THRESHOLD_USEC (5*USEC_PER_MINUTE)
32
33 Session* session_new(Manager *m, User *u, const char *id) {
34         Session *s;
35
36         assert(m);
37         assert(id);
38
39         s = new0(Session, 1);
40         if (!s)
41                 return NULL;
42
43         s->state_file = strappend("/run/systemd/session/", id);
44         if (!s->state_file) {
45                 free(s);
46                 return NULL;
47         }
48
49         s->id = file_name_from_path(s->state_file);
50
51         if (hashmap_put(m->sessions, s->id, s) < 0) {
52                 free(s->id);
53                 free(s);
54                 return NULL;
55         }
56
57         s->manager = m;
58         s->pipe_fd = -1;
59         s->user = u;
60
61         LIST_PREPEND(Session, sessions_by_user, u->sessions, s);
62
63         return s;
64 }
65
66 void session_free(Session *s) {
67         assert(s);
68
69         if (s->in_gc_queue)
70                 LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s);
71
72         if (s->user) {
73                 LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s);
74
75                 if (s->user->display == s)
76                         s->user->display = NULL;
77         }
78
79         if (s->seat)
80                 LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s);
81
82         free(s->cgroup_path);
83         strv_free(s->controllers);
84
85         free(s->tty);
86         free(s->display);
87         free(s->remote_host);
88         free(s->remote_user);
89
90         hashmap_remove(s->manager->sessions, s->id);
91
92         free(s->state_file);
93         free(s);
94 }
95
96 int session_save(Session *s) {
97         FILE *f;
98         int r = 0;
99         char *temp_path;
100
101         assert(s);
102
103         r = safe_mkdir("/run/systemd/session", 0755, 0, 0);
104         if (r < 0)
105                 goto finish;
106
107         r = fopen_temporary(s->state_file, &f, &temp_path);
108         if (r < 0)
109                 goto finish;
110
111         assert(s->user);
112
113         fchmod(fileno(f), 0644);
114
115         fprintf(f,
116                 "# This is private data. Do not parse.\n"
117                 "UID=%lu\n"
118                 "USER=%s\n"
119                 "ACTIVE=%i\n"
120                 "REMOTE=%i\n"
121                 "KILL_PROCESSES=%i\n",
122                 (unsigned long) s->user->uid,
123                 s->user->name,
124                 session_is_active(s),
125                 s->remote,
126                 s->kill_processes);
127
128         if (s->cgroup_path)
129                 fprintf(f,
130                         "CGROUP=%s\n",
131                         s->cgroup_path);
132
133         if (s->seat)
134                 fprintf(f,
135                         "SEAT=%s\n",
136                         s->seat->id);
137
138         if (s->tty)
139                 fprintf(f,
140                         "TTY=%s\n",
141                         s->tty);
142
143         if (s->display)
144                 fprintf(f,
145                         "DISPLAY=%s\n",
146                         s->display);
147
148         if (s->remote_host)
149                 fprintf(f,
150                         "REMOTE_HOST=%s\n",
151                         s->remote_host);
152
153         if (s->remote_user)
154                 fprintf(f,
155                         "REMOTE_USER=%s\n",
156                         s->remote_user);
157
158         if (s->seat && seat_is_vtconsole(s->seat))
159                 fprintf(f,
160                         "VTNR=%i\n",
161                         s->vtnr);
162
163         if (s->leader > 0)
164                 fprintf(f,
165                         "LEADER=%lu\n",
166                         (unsigned long) s->leader);
167
168         if (s->audit_id > 0)
169                 fprintf(f,
170                         "AUDIT=%llu\n",
171                         (unsigned long long) s->audit_id);
172
173         fflush(f);
174
175         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
176                 r = -errno;
177                 unlink(s->state_file);
178                 unlink(temp_path);
179         }
180
181         fclose(f);
182         free(temp_path);
183
184 finish:
185         if (r < 0)
186                 log_error("Failed to save session data for %s: %s", s->id, strerror(-r));
187
188         return r;
189 }
190
191 int session_load(Session *s) {
192         char *remote = NULL,
193                 *kill_processes = NULL,
194                 *seat = NULL,
195                 *vtnr = NULL,
196                 *leader = NULL,
197                 *audit_id = NULL;
198
199         int k, r;
200
201         assert(s);
202
203         r = parse_env_file(s->state_file, NEWLINE,
204                            "REMOTE",         &remote,
205                            "KILL_PROCESSES", &kill_processes,
206                            "CGROUP",         &s->cgroup_path,
207                            "SEAT",           &seat,
208                            "TTY",            &s->tty,
209                            "DISPLAY",        &s->display,
210                            "REMOTE_HOST",    &s->remote_host,
211                            "REMOTE_USER",    &s->remote_user,
212                            "VTNR",           &vtnr,
213                            "LEADER",         &leader,
214                            "AUDIT_ID",       &audit_id,
215                            NULL);
216
217         if (r < 0)
218                 goto finish;
219
220         if (remote) {
221                 k = parse_boolean(remote);
222                 if (k >= 0)
223                         s->remote = k;
224         }
225
226         if (kill_processes) {
227                 k = parse_boolean(kill_processes);
228                 if (k >= 0)
229                         s->kill_processes = k;
230         }
231
232         if (seat) {
233                 Seat *o;
234
235                 o = hashmap_get(s->manager->seats, seat);
236                 if (o)
237                         seat_attach_session(o, s);
238         }
239
240         if (vtnr && s->seat && seat_is_vtconsole(s->seat)) {
241                 int v;
242
243                 k = safe_atoi(vtnr, &v);
244                 if (k >= 0 && v >= 1)
245                         s->vtnr = v;
246         }
247
248         if (leader) {
249                 pid_t pid;
250
251                 k = parse_pid(leader, &pid);
252                 if (k >= 0 && pid >= 1)
253                         s->leader = pid;
254         }
255
256         if (audit_id) {
257                 uint32_t l;
258
259                 k = safe_atou32(audit_id, &l);
260                 if (k >= 0 && l >= l)
261                         s->audit_id = l;
262         }
263
264 finish:
265         free(remote);
266         free(kill_processes);
267         free(seat);
268         free(vtnr);
269         free(leader);
270         free(audit_id);
271
272         return r;
273 }
274
275 int session_activate(Session *s) {
276         int r;
277         Session *old_active;
278
279         assert(s);
280
281         if (s->vtnr < 0)
282                 return -ENOTSUP;
283
284         if (!s->seat)
285                 return -ENOTSUP;
286
287         if (s->seat->active == s)
288                 return 0;
289
290         assert(seat_is_vtconsole(s->seat));
291
292         r = chvt(s->vtnr);
293         if (r < 0)
294                 return r;
295
296         old_active = s->seat->active;
297         s->seat->active = s;
298
299         return seat_apply_acls(s->seat, old_active);
300 }
301
302 bool x11_display_is_local(const char *display) {
303         assert(display);
304
305         return
306                 display[0] == ':' &&
307                 display[1] >= '0' &&
308                 display[1] <= '9';
309 }
310
311 static int session_link_x11_socket(Session *s) {
312         char *t, *f, *c;
313         size_t k;
314
315         assert(s);
316         assert(s->user);
317         assert(s->user->runtime_path);
318
319         if (s->user->display)
320                 return 0;
321
322         if (!s->display || !x11_display_is_local(s->display))
323                 return 0;
324
325         k = strspn(s->display+1, "0123456789");
326         f = new(char, sizeof("/tmp/.X11-unix/X") + k);
327         if (!f) {
328                 log_error("Out of memory");
329                 return -ENOMEM;
330         }
331
332         c = stpcpy(f, "/tmp/.X11-unix/X");
333         memcpy(c, s->display+1, k);
334         c[k] = 0;
335
336         if (access(f, F_OK) < 0) {
337                 log_warning("Session %s has display %s with nonexisting socket %s.", s->id, s->display, f);
338                 free(f);
339                 return -ENOENT;
340         }
341
342         t = strappend(s->user->runtime_path, "/display");
343         if (!t) {
344                 log_error("Out of memory");
345                 free(f);
346                 return -ENOMEM;
347         }
348
349         if (link(f, t) < 0) {
350                 if (errno == EEXIST) {
351                         unlink(t);
352
353                         if (link(f, t) >= 0)
354                                 goto done;
355                 }
356
357                 if (symlink(f, t) < 0) {
358
359                         if (errno == EEXIST) {
360                                 unlink(t);
361
362                                 if (symlink(f, t) >= 0)
363                                         goto done;
364                         }
365
366                         log_error("Failed to link %s to %s: %m", f, t);
367                         free(f);
368                         free(t);
369                         return -errno;
370                 }
371         }
372
373 done:
374         log_info("Linked %s to %s.", f, t);
375         free(f);
376         free(t);
377
378         s->user->display = s;
379
380         return 0;
381 }
382
383 static int session_create_cgroup(Session *s) {
384         char **k;
385         char *p;
386         int r;
387
388         assert(s);
389         assert(s->user);
390         assert(s->user->cgroup_path);
391
392         if (!s->cgroup_path) {
393                 if (asprintf(&p, "%s/%s", s->user->cgroup_path, s->id) < 0) {
394                         log_error("Out of memory");
395                         return -ENOMEM;
396                 }
397         } else
398                 p = s->cgroup_path;
399
400         if (s->leader > 0)
401                 r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, p, s->leader);
402         else
403                 r = cg_create(SYSTEMD_CGROUP_CONTROLLER, p);
404
405         if (r < 0) {
406                 free(p);
407                 s->cgroup_path = NULL;
408                 log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
409                 return r;
410         }
411
412         s->cgroup_path = p;
413
414         STRV_FOREACH(k, s->manager->controllers) {
415                 if (s->leader > 0)
416                         r = cg_create_and_attach(*k, p, s->leader);
417                 else
418                         r = cg_create(*k, p);
419
420                 if (r < 0)
421                         log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r));
422         }
423
424         return 0;
425 }
426
427 int session_start(Session *s) {
428         int r;
429
430         assert(s);
431         assert(s->user);
432
433         /* Create cgroup */
434         r = session_create_cgroup(s);
435         if (r < 0)
436                 return r;
437
438         /* Create X11 symlink */
439         session_link_x11_socket(s);
440
441         /* Save session data */
442         session_save(s);
443
444         dual_timestamp_get(&s->timestamp);
445
446         session_send_signal(s, true);
447
448         return 0;
449 }
450
451 static bool session_shall_kill(Session *s) {
452         assert(s);
453
454         return s->kill_processes;
455 }
456
457 static int session_kill_cgroup(Session *s) {
458         int r;
459         char **k;
460
461         assert(s);
462
463         if (!s->cgroup_path)
464                 return 0;
465
466         cg_trim(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
467
468         if (session_shall_kill(s)) {
469
470                 r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
471                 if (r < 0)
472                         log_error("Failed to kill session cgroup: %s", strerror(-r));
473
474         } else {
475                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
476                 if (r < 0)
477                         log_error("Failed to check session cgroup: %s", strerror(-r));
478                 else if (r > 0) {
479                         r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path);
480                         if (r < 0)
481                                 log_error("Failed to delete session cgroup: %s", strerror(-r));
482                 } else
483                         r = -EBUSY;
484         }
485
486         STRV_FOREACH(k, s->user->manager->controllers)
487                 cg_trim(*k, s->cgroup_path, true);
488
489         free(s->cgroup_path);
490         s->cgroup_path = NULL;
491
492         return r;
493 }
494
495 static int session_unlink_x11_socket(Session *s) {
496         char *t;
497         int r;
498
499         assert(s);
500         assert(s->user);
501
502         if (s->user->display != s)
503                 return 0;
504
505         s->user->display = NULL;
506
507         t = strappend(s->user->runtime_path, "/display");
508         if (!t) {
509                 log_error("Out of memory");
510                 return -ENOMEM;
511         }
512
513         r = unlink(t);
514         free(t);
515
516         return r < 0 ? -errno : 0;
517 }
518
519 int session_stop(Session *s) {
520         int r = 0, k;
521
522         assert(s);
523
524         session_send_signal(s, false);
525
526         /* Kill cgroup */
527         k = session_kill_cgroup(s);
528         if (k < 0)
529                 r = k;
530
531         /* Remove X11 symlink */
532         session_unlink_x11_socket(s);
533
534         unlink(s->state_file);
535         session_add_to_gc_queue(s);
536
537         return r;
538 }
539
540 bool session_is_active(Session *s) {
541         assert(s);
542
543         if (!s->seat)
544                 return true;
545
546         return s->seat->active == s;
547 }
548
549 int session_get_idle_hint(Session *s, dual_timestamp *t) {
550         char *p;
551         struct stat st;
552         usec_t u, n;
553         bool b;
554         int k;
555
556         assert(s);
557
558         if (s->idle_hint) {
559                 if (t)
560                         *t = s->idle_hint_timestamp;
561
562                 return s->idle_hint;
563         }
564
565         if (isempty(s->tty))
566                 goto dont_know;
567
568         if (s->tty[0] != '/') {
569                 p = strappend("/dev/", s->tty);
570                 if (!p)
571                         return -ENOMEM;
572         } else
573                 p = NULL;
574
575         if (!startswith(p ? p : s->tty, "/dev/")) {
576                 free(p);
577                 goto dont_know;
578         }
579
580         k = lstat(p ? p : s->tty, &st);
581         free(p);
582
583         if (k < 0)
584                 goto dont_know;
585
586         u = timespec_load(&st.st_atim);
587         n = now(CLOCK_REALTIME);
588         b = u + IDLE_THRESHOLD_USEC < n;
589
590         if (t)
591                 dual_timestamp_from_realtime(t, u + b ? IDLE_THRESHOLD_USEC : 0);
592
593         return b;
594
595 dont_know:
596         if (t)
597                 *t = s->idle_hint_timestamp;
598
599         return 0;
600 }
601
602 void session_set_idle_hint(Session *s, bool b) {
603         assert(s);
604
605         if (s->idle_hint == b)
606                 return;
607
608         s->idle_hint = b;
609         dual_timestamp_get(&s->idle_hint_timestamp);
610 }
611
612 int session_check_gc(Session *s) {
613         int r;
614
615         assert(s);
616
617         if (s->pipe_fd >= 0) {
618
619                 r = pipe_eof(s->pipe_fd);
620                 if (r < 0)
621                         return r;
622
623                 if (r == 0)
624                         return 1;
625         }
626
627         if (s->cgroup_path) {
628
629                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
630                 if (r < 0)
631                         return r;
632
633                 if (r <= 0)
634                         return 1;
635         }
636
637         return 0;
638 }
639
640 void session_add_to_gc_queue(Session *s) {
641         assert(s);
642
643         if (s->in_gc_queue)
644                 return;
645
646         LIST_PREPEND(Session, gc_queue, s->manager->session_gc_queue, s);
647         s->in_gc_queue = true;
648 }
649
650 static const char* const session_type_table[_SESSION_TYPE_MAX] = {
651         [SESSION_TTY] = "tty",
652         [SESSION_X11] = "x11"
653 };
654
655 DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);