chiark / gitweb /
logind: implement GC
[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 Session* session_new(Manager *m, User *u, const char *id) {
32         Session *s;
33
34         assert(m);
35         assert(id);
36
37         s = new0(Session, 1);
38         if (!s)
39                 return NULL;
40
41         s->state_file = strappend("/run/systemd/session/", id);
42         if (!s->state_file) {
43                 free(s);
44                 return NULL;
45         }
46
47         s->id = file_name_from_path(s->state_file);
48
49         if (hashmap_put(m->sessions, s->id, s) < 0) {
50                 free(s->id);
51                 free(s);
52                 return NULL;
53         }
54
55         s->manager = m;
56         s->pipe_fd = -1;
57         s->user = u;
58
59         LIST_PREPEND(Session, sessions_by_user, u->sessions, s);
60
61         return s;
62 }
63
64 void session_free(Session *s) {
65         assert(s);
66
67         if (s->in_gc_queue)
68                 LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s);
69
70         if (s->user) {
71                 LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s);
72
73                 if (s->user->display == s)
74                         s->user->display = NULL;
75         }
76
77         if (s->seat)
78                 LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s);
79
80         free(s->cgroup_path);
81         strv_free(s->controllers);
82
83         free(s->tty);
84         free(s->display);
85         free(s->remote_host);
86
87         hashmap_remove(s->manager->sessions, s->id);
88
89         if (s->state_file) {
90                 unlink(s->state_file);
91                 free(s->state_file);
92         }
93
94         free(s);
95 }
96
97 int session_save(Session *s) {
98         FILE *f;
99         int r = 0;
100         char *temp_path;
101
102         assert(s);
103
104         r = safe_mkdir("/run/systemd/session", 0755, 0, 0);
105         if (r < 0)
106                 goto finish;
107
108         r = fopen_temporary(s->state_file, &f, &temp_path);
109         if (r < 0)
110                 goto finish;
111
112         assert(s->user);
113
114         fchmod(fileno(f), 0644);
115
116         fprintf(f,
117                 "# This is private data. Do not parse.\n"
118                 "UID=%lu\n"
119                 "USER=%s\n"
120                 "ACTIVE=%i\n"
121                 "REMOTE=%i\n"
122                 "KILL_PROCESSES=%i\n",
123                 (unsigned long) s->user->uid,
124                 s->user->name,
125                 session_is_active(s),
126                 s->remote,
127                 s->kill_processes);
128
129         if (s->cgroup_path)
130                 fprintf(f,
131                         "CGROUP=%s\n",
132                         s->cgroup_path);
133
134         if (s->seat)
135                 fprintf(f,
136                         "SEAT=%s\n",
137                         s->seat->id);
138
139         if (s->tty)
140                 fprintf(f,
141                         "TTY=%s\n",
142                         s->tty);
143
144         if (s->display)
145                 fprintf(f,
146                         "DISPLAY=%s\n",
147                         s->display);
148
149         if (s->remote_host)
150                 fprintf(f,
151                         "REMOTE_HOST=%s\n",
152                         s->remote_host);
153
154         if (s->seat && s->seat->manager->vtconsole == s->seat)
155                 fprintf(f,
156                         "VTNR=%i\n",
157                         s->vtnr);
158
159         if (s->leader > 0)
160                 fprintf(f,
161                         "LEADER=%lu\n",
162                         (unsigned long) s->leader);
163
164         if (s->audit_id > 0)
165                 fprintf(f,
166                         "AUDIT=%llu\n",
167                         (unsigned long long) s->audit_id);
168
169         fflush(f);
170
171         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
172                 r = -errno;
173                 unlink(s->state_file);
174                 unlink(temp_path);
175         }
176
177         fclose(f);
178         free(temp_path);
179
180 finish:
181         if (r < 0)
182                 log_error("Failed to save session data for %s: %s", s->id, strerror(-r));
183
184         return r;
185 }
186
187 int session_load(Session *s) {
188         assert(s);
189
190         return 0;
191 }
192
193 int session_activate(Session *s) {
194         int r;
195         Session *old_active;
196
197         assert(s);
198
199         if (s->vtnr < 0)
200                 return -ENOTSUP;
201
202         if (!s->seat)
203                 return -ENOTSUP;
204
205         if (s->seat->active == s)
206                 return 0;
207
208         assert(s->manager->vtconsole == s->seat);
209
210         r = chvt(s->vtnr);
211         if (r < 0)
212                 return r;
213
214         old_active = s->seat->active;
215         s->seat->active = s;
216
217         return seat_apply_acls(s->seat, old_active);
218 }
219
220 bool x11_display_is_local(const char *display) {
221         assert(display);
222
223         return
224                 display[0] == ':' &&
225                 display[1] >= '0' &&
226                 display[1] <= '9';
227 }
228
229 static int session_link_x11_socket(Session *s) {
230         char *t, *f, *c;
231         size_t k;
232
233         assert(s);
234         assert(s->user);
235         assert(s->user->runtime_path);
236
237         if (s->user->display)
238                 return 0;
239
240         if (!s->display || !x11_display_is_local(s->display))
241                 return 0;
242
243         k = strspn(s->display+1, "0123456789");
244         f = new(char, sizeof("/tmp/.X11-unix/X") + k);
245         if (!f) {
246                 log_error("Out of memory");
247                 return -ENOMEM;
248         }
249
250         c = stpcpy(f, "/tmp/.X11-unix/X");
251         memcpy(c, s->display+1, k);
252         c[k] = 0;
253
254         if (access(f, F_OK) < 0) {
255                 log_warning("Session %s has display %s with nonexisting socket %s.", s->id, s->display, f);
256                 free(f);
257                 return -ENOENT;
258         }
259
260         t = strappend(s->user->runtime_path, "/display");
261         if (!t) {
262                 log_error("Out of memory");
263                 free(f);
264                 return -ENOMEM;
265         }
266
267         if (link(f, t) < 0) {
268                 if (errno == EEXIST) {
269                         unlink(t);
270
271                         if (link(f, t) >= 0)
272                                 goto done;
273                 }
274
275                 if (symlink(f, t) < 0) {
276
277                         if (errno == EEXIST) {
278                                 unlink(t);
279
280                                 if (symlink(f, t) >= 0)
281                                         goto done;
282                         }
283
284                         log_error("Failed to link %s to %s: %m", f, t);
285                         free(f);
286                         free(t);
287                         return -errno;
288                 }
289         }
290
291 done:
292         log_info("Linked %s to %s.", f, t);
293         free(f);
294         free(t);
295
296         s->user->display = s;
297
298         return 0;
299 }
300
301 static int session_create_cgroup(Session *s) {
302         char **k;
303         char *p;
304         int r;
305
306         assert(s);
307         assert(s->user);
308         assert(s->user->cgroup_path);
309
310         if (!s->cgroup_path) {
311                 if (asprintf(&p, "%s/%s", s->user->cgroup_path, s->id) < 0) {
312                         log_error("Out of memory");
313                         return -ENOMEM;
314                 }
315         } else
316                 p = s->cgroup_path;
317
318         if (s->leader > 0)
319                 r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, p, s->leader);
320         else
321                 r = cg_create(SYSTEMD_CGROUP_CONTROLLER, p);
322
323         if (r < 0) {
324                 free(p);
325                 s->cgroup_path = NULL;
326                 log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
327                 return r;
328         }
329
330         s->cgroup_path = p;
331
332         STRV_FOREACH(k, s->manager->controllers) {
333                 if (s->leader > 0)
334                         r = cg_create_and_attach(*k, p, s->leader);
335                 else
336                         r = cg_create(*k, p);
337
338                 if (r < 0)
339                         log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r));
340         }
341
342         return 0;
343 }
344
345 int session_start(Session *s) {
346         int r;
347
348         assert(s);
349         assert(s->user);
350
351         /* Create cgroup */
352         r = session_create_cgroup(s);
353         if (r < 0)
354                 return r;
355
356         /* Create X11 symlink */
357         session_link_x11_socket(s);
358
359         /* Save session data */
360         session_save(s);
361
362         dual_timestamp_get(&s->timestamp);
363
364         return 0;
365 }
366
367 static bool session_shall_kill(Session *s) {
368         assert(s);
369
370         return s->kill_processes;
371 }
372
373 static int session_kill_cgroup(Session *s) {
374         int r;
375         char **k;
376
377         assert(s);
378
379         if (!s->cgroup_path)
380                 return 0;
381
382         cg_trim(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
383
384         if (session_shall_kill(s)) {
385
386                 r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
387                 if (r < 0)
388                         log_error("Failed to kill session cgroup: %s", strerror(-r));
389
390         } else {
391                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
392                 if (r < 0)
393                         log_error("Failed to check session cgroup: %s", strerror(-r));
394                 else if (r > 0) {
395                         r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path);
396                         if (r < 0)
397                                 log_error("Failed to delete session cgroup: %s", strerror(-r));
398                 } else
399                         r = -EBUSY;
400         }
401
402         STRV_FOREACH(k, s->user->manager->controllers)
403                 cg_trim(*k, s->cgroup_path, true);
404
405         free(s->cgroup_path);
406         s->cgroup_path = NULL;
407
408         return r;
409 }
410
411 static int session_unlink_x11_socket(Session *s) {
412         char *t;
413         int r;
414
415         assert(s);
416         assert(s->user);
417
418         if (s->user->display != s)
419                 return 0;
420
421         s->user->display = NULL;
422
423         t = strappend(s->user->runtime_path, "/display");
424         if (!t) {
425                 log_error("Out of memory");
426                 return -ENOMEM;
427         }
428
429         r = unlink(t);
430         free(t);
431
432         return r < 0 ? -errno : 0;
433 }
434
435 int session_stop(Session *s) {
436         int r = 0, k;
437
438         assert(s);
439
440         /* Kill cgroup */
441         k = session_kill_cgroup(s);
442         if (k < 0)
443                 r = k;
444
445         /* Remove X11 symlink */
446         session_unlink_x11_socket(s);
447
448         session_save(s);
449
450         return r;
451 }
452
453 bool session_is_active(Session *s) {
454         assert(s);
455
456         if (!s->seat)
457                 return true;
458
459         return s->seat->active == s;
460 }
461
462 int session_check_gc(Session *s) {
463         int r;
464
465         assert(s);
466
467         if (s->pipe_fd >= 0) {
468
469                 r = pipe_eof(s->pipe_fd);
470                 if (r < 0)
471                         return r;
472
473                 if (r == 0)
474                         return 1;
475         }
476
477         if (s->cgroup_path) {
478
479                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
480                 if (r < 0)
481                         return r;
482
483                 if (r <= 0)
484                         return 1;
485         }
486
487         return 0;
488 }
489
490 void session_add_to_gc_queue(Session *s) {
491         assert(s);
492
493         if (s->in_gc_queue)
494                 return;
495
496         LIST_PREPEND(Session, gc_queue, s->manager->session_gc_queue, s);
497         s->in_gc_queue = true;
498 }
499
500 static const char* const session_type_table[_SESSION_TYPE_MAX] = {
501         [SESSION_TERMINAL] = "terminal",
502         [SESSION_X11] = "x11"
503 };
504
505 DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);