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