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