chiark / gitweb /
logind: implement idle hint logic
[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         return 0;
447 }
448
449 static bool session_shall_kill(Session *s) {
450         assert(s);
451
452         return s->kill_processes;
453 }
454
455 static int session_kill_cgroup(Session *s) {
456         int r;
457         char **k;
458
459         assert(s);
460
461         if (!s->cgroup_path)
462                 return 0;
463
464         cg_trim(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
465
466         if (session_shall_kill(s)) {
467
468                 r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
469                 if (r < 0)
470                         log_error("Failed to kill session cgroup: %s", strerror(-r));
471
472         } else {
473                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
474                 if (r < 0)
475                         log_error("Failed to check session cgroup: %s", strerror(-r));
476                 else if (r > 0) {
477                         r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path);
478                         if (r < 0)
479                                 log_error("Failed to delete session cgroup: %s", strerror(-r));
480                 } else
481                         r = -EBUSY;
482         }
483
484         STRV_FOREACH(k, s->user->manager->controllers)
485                 cg_trim(*k, s->cgroup_path, true);
486
487         free(s->cgroup_path);
488         s->cgroup_path = NULL;
489
490         return r;
491 }
492
493 static int session_unlink_x11_socket(Session *s) {
494         char *t;
495         int r;
496
497         assert(s);
498         assert(s->user);
499
500         if (s->user->display != s)
501                 return 0;
502
503         s->user->display = NULL;
504
505         t = strappend(s->user->runtime_path, "/display");
506         if (!t) {
507                 log_error("Out of memory");
508                 return -ENOMEM;
509         }
510
511         r = unlink(t);
512         free(t);
513
514         return r < 0 ? -errno : 0;
515 }
516
517 int session_stop(Session *s) {
518         int r = 0, k;
519
520         assert(s);
521
522         /* Kill cgroup */
523         k = session_kill_cgroup(s);
524         if (k < 0)
525                 r = k;
526
527         /* Remove X11 symlink */
528         session_unlink_x11_socket(s);
529
530         unlink(s->state_file);
531         session_add_to_gc_queue(s);
532
533         return r;
534 }
535
536 bool session_is_active(Session *s) {
537         assert(s);
538
539         if (!s->seat)
540                 return true;
541
542         return s->seat->active == s;
543 }
544
545 int session_get_idle_hint(Session *s, dual_timestamp *t) {
546         char *p;
547         struct stat st;
548         usec_t u, n;
549         bool b;
550         int k;
551
552         assert(s);
553
554         if (s->idle_hint) {
555                 if (t)
556                         *t = s->idle_hint_timestamp;
557
558                 return s->idle_hint;
559         }
560
561         if (isempty(s->tty))
562                 goto dont_know;
563
564         if (s->tty[0] != '/') {
565                 p = strappend("/dev/", s->tty);
566                 if (!p)
567                         return -ENOMEM;
568         } else
569                 p = NULL;
570
571         if (!startswith(p ? p : s->tty, "/dev/")) {
572                 free(p);
573                 goto dont_know;
574         }
575
576         k = lstat(p ? p : s->tty, &st);
577         free(p);
578
579         if (k < 0)
580                 goto dont_know;
581
582         u = timespec_load(&st.st_atim);
583         n = now(CLOCK_REALTIME);
584         b = u + IDLE_THRESHOLD_USEC < n;
585
586         if (t)
587                 dual_timestamp_from_realtime(t, u + b ? IDLE_THRESHOLD_USEC : 0);
588
589         return b;
590
591 dont_know:
592         if (t)
593                 *t = s->idle_hint_timestamp;
594
595         return 0;
596 }
597
598 int session_check_gc(Session *s) {
599         int r;
600
601         assert(s);
602
603         if (s->pipe_fd >= 0) {
604
605                 r = pipe_eof(s->pipe_fd);
606                 if (r < 0)
607                         return r;
608
609                 if (r == 0)
610                         return 1;
611         }
612
613         if (s->cgroup_path) {
614
615                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
616                 if (r < 0)
617                         return r;
618
619                 if (r <= 0)
620                         return 1;
621         }
622
623         return 0;
624 }
625
626 void session_add_to_gc_queue(Session *s) {
627         assert(s);
628
629         if (s->in_gc_queue)
630                 return;
631
632         LIST_PREPEND(Session, gc_queue, s->manager->session_gc_queue, s);
633         s->in_gc_queue = true;
634 }
635
636 static const char* const session_type_table[_SESSION_TYPE_MAX] = {
637         [SESSION_TTY] = "tty",
638         [SESSION_X11] = "x11"
639 };
640
641 DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);