chiark / gitweb /
Beginnings of handling suspend/etc within logind
[elogind.git] / src / login / logind-user.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 Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 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   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/mount.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <errno.h>
26
27 #include "util.h"
28 #include "mkdir.h"
29 #include "hashmap.h"
30 #include "fileio.h"
31 #include "path-util.h"
32 #include "special.h"
33 #include "unit-name.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "conf-parser.h"
37 #include "clean-ipc.h"
38 #include "logind-user.h"
39 #include "smack-util.h"
40
41 User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) {
42         User *u;
43
44         assert(m);
45         assert(name);
46
47         u = new0(User, 1);
48         if (!u)
49                 return NULL;
50
51         u->name = strdup(name);
52         if (!u->name)
53                 goto fail;
54
55         if (asprintf(&u->state_file, "/run/systemd/users/"UID_FMT, uid) < 0)
56                 goto fail;
57
58         if (hashmap_put(m->users, UID_TO_PTR(uid), u) < 0)
59                 goto fail;
60
61         u->manager = m;
62         u->uid = uid;
63         u->gid = gid;
64
65         return u;
66
67 fail:
68         free(u->state_file);
69         free(u->name);
70         free(u);
71
72         return NULL;
73 }
74
75 void user_free(User *u) {
76         assert(u);
77
78         if (u->in_gc_queue)
79                 LIST_REMOVE(gc_queue, u->manager->user_gc_queue, u);
80
81         while (u->sessions)
82                 session_free(u->sessions);
83
84         free(u->runtime_path);
85
86         hashmap_remove(u->manager->users, UID_TO_PTR(u->uid));
87
88         free(u->name);
89         free(u->state_file);
90         free(u);
91 }
92
93 int user_save(User *u) {
94         _cleanup_free_ char *temp_path = NULL;
95         _cleanup_fclose_ FILE *f = NULL;
96         int r;
97
98         assert(u);
99         assert(u->state_file);
100
101         if (!u->started)
102                 return 0;
103
104         r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0);
105         if (r < 0)
106                 goto finish;
107
108         r = fopen_temporary(u->state_file, &f, &temp_path);
109         if (r < 0)
110                 goto finish;
111
112         fchmod(fileno(f), 0644);
113
114         fprintf(f,
115                 "# This is private data. Do not parse.\n"
116                 "NAME=%s\n"
117                 "STATE=%s\n",
118                 u->name,
119                 user_state_to_string(user_get_state(u)));
120
121         if (u->runtime_path)
122                 fprintf(f, "RUNTIME=%s\n", u->runtime_path);
123
124         if (u->display)
125                 fprintf(f, "DISPLAY=%s\n", u->display->id);
126
127         if (dual_timestamp_is_set(&u->timestamp))
128                 fprintf(f,
129                         "REALTIME="USEC_FMT"\n"
130                         "MONOTONIC="USEC_FMT"\n",
131                         u->timestamp.realtime,
132                         u->timestamp.monotonic);
133
134         if (u->sessions) {
135                 Session *i;
136                 bool first;
137
138                 fputs("SESSIONS=", f);
139                 first = true;
140                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
141                         if (first)
142                                 first = false;
143                         else
144                                 fputc(' ', f);
145
146                         fputs(i->id, f);
147                 }
148
149                 fputs("\nSEATS=", f);
150                 first = true;
151                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
152                         if (!i->seat)
153                                 continue;
154
155                         if (first)
156                                 first = false;
157                         else
158                                 fputc(' ', f);
159
160                         fputs(i->seat->id, f);
161                 }
162
163                 fputs("\nACTIVE_SESSIONS=", f);
164                 first = true;
165                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
166                         if (!session_is_active(i))
167                                 continue;
168
169                         if (first)
170                                 first = false;
171                         else
172                                 fputc(' ', f);
173
174                         fputs(i->id, f);
175                 }
176
177                 fputs("\nONLINE_SESSIONS=", f);
178                 first = true;
179                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
180                         if (session_get_state(i) == SESSION_CLOSING)
181                                 continue;
182
183                         if (first)
184                                 first = false;
185                         else
186                                 fputc(' ', f);
187
188                         fputs(i->id, f);
189                 }
190
191                 fputs("\nACTIVE_SEATS=", f);
192                 first = true;
193                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
194                         if (!session_is_active(i) || !i->seat)
195                                 continue;
196
197                         if (first)
198                                 first = false;
199                         else
200                                 fputc(' ', f);
201
202                         fputs(i->seat->id, f);
203                 }
204
205                 fputs("\nONLINE_SEATS=", f);
206                 first = true;
207                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
208                         if (session_get_state(i) == SESSION_CLOSING || !i->seat)
209                                 continue;
210
211                         if (first)
212                                 first = false;
213                         else
214                                 fputc(' ', f);
215
216                         fputs(i->seat->id, f);
217                 }
218                 fputc('\n', f);
219         }
220
221         fflush(f);
222
223         if (ferror(f) || rename(temp_path, u->state_file) < 0) {
224                 r = -errno;
225                 unlink(u->state_file);
226                 unlink(temp_path);
227         }
228
229 finish:
230         if (r < 0)
231                 log_error_errno(r, "Failed to save user data %s: %m", u->state_file);
232
233         return r;
234 }
235
236 int user_load(User *u) {
237         _cleanup_free_ char *display = NULL, *realtime = NULL, *monotonic = NULL;
238         Session *s = NULL;
239         int r;
240
241         assert(u);
242
243         r = parse_env_file(u->state_file, NEWLINE,
244                            "RUNTIME",     &u->runtime_path,
245                            "DISPLAY",     &display,
246                            "REALTIME",    &realtime,
247                            "MONOTONIC",   &monotonic,
248                            NULL);
249         if (r < 0) {
250                 if (r == -ENOENT)
251                         return 0;
252
253                 log_error_errno(r, "Failed to read %s: %m", u->state_file);
254                 return r;
255         }
256
257         if (display)
258                 s = hashmap_get(u->manager->sessions, display);
259
260         if (s && s->display && display_is_local(s->display))
261                 u->display = s;
262
263         if (realtime) {
264                 unsigned long long l;
265                 if (sscanf(realtime, "%llu", &l) > 0)
266                         u->timestamp.realtime = l;
267         }
268
269         if (monotonic) {
270                 unsigned long long l;
271                 if (sscanf(monotonic, "%llu", &l) > 0)
272                         u->timestamp.monotonic = l;
273         }
274
275         return r;
276 }
277
278 static int user_mkdir_runtime_path(User *u) {
279         char *p;
280         int r;
281
282         assert(u);
283
284         r = mkdir_safe_label("/run/user", 0755, 0, 0);
285         if (r < 0)
286                 return log_error_errno(r, "Failed to create /run/user: %m");
287
288         if (!u->runtime_path) {
289                 if (asprintf(&p, "/run/user/" UID_FMT, u->uid) < 0)
290                         return log_oom();
291         } else
292                 p = u->runtime_path;
293
294         if (path_is_mount_point(p, false) <= 0) {
295                 _cleanup_free_ char *t = NULL;
296
297                 (void) mkdir(p, 0700);
298
299                 if (mac_smack_use())
300                         r = asprintf(&t, "mode=0700,smackfsroot=*,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu", u->uid, u->gid, u->manager->runtime_dir_size);
301                 else
302                         r = asprintf(&t, "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu", u->uid, u->gid, u->manager->runtime_dir_size);
303                 if (r < 0) {
304                         r = log_oom();
305                         goto fail;
306                 }
307
308                 r = mount("tmpfs", p, "tmpfs", MS_NODEV|MS_NOSUID, t);
309                 if (r < 0) {
310                         if (errno != EPERM) {
311                                 r = log_error_errno(errno, "Failed to mount per-user tmpfs directory %s: %m", p);
312                                 goto fail;
313                         }
314
315                         /* Lacking permissions, maybe
316                          * CAP_SYS_ADMIN-less container? In this case,
317                          * just use a normal directory. */
318
319                         r = chmod_and_chown(p, 0700, u->uid, u->gid);
320                         if (r < 0) {
321                                 log_error_errno(r, "Failed to change runtime directory ownership and mode: %m");
322                                 goto fail;
323                         }
324                 }
325         }
326
327         u->runtime_path = p;
328         return 0;
329
330 fail:
331         if (p) {
332                 /* Try to clean up, but ignore errors */
333                 (void) rmdir(p);
334                 free(p);
335         }
336
337         u->runtime_path = NULL;
338         return r;
339 }
340
341 int user_start(User *u) {
342         int r;
343
344         assert(u);
345
346         if (u->started)
347                 return 0;
348
349         log_debug("New user %s logged in.", u->name);
350
351         /* Make XDG_RUNTIME_DIR */
352         r = user_mkdir_runtime_path(u);
353         if (r < 0)
354                 return r;
355
356         if (!dual_timestamp_is_set(&u->timestamp))
357                 dual_timestamp_get(&u->timestamp);
358
359         u->started = true;
360
361         /* Save new user data */
362         user_save(u);
363
364         user_send_signal(u, true);
365
366         return 0;
367 }
368
369 static int user_remove_runtime_path(User *u) {
370         int r;
371
372         assert(u);
373
374         if (!u->runtime_path)
375                 return 0;
376
377         r = rm_rf(u->runtime_path, false, false, false);
378         if (r < 0)
379                 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
380
381         /* Ignore cases where the directory isn't mounted, as that's
382          * quite possible, if we lacked the permissions to mount
383          * something */
384         r = umount2(u->runtime_path, MNT_DETACH);
385         if (r < 0 && errno != EINVAL && errno != ENOENT)
386                 log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", u->runtime_path);
387
388         r = rm_rf(u->runtime_path, false, true, false);
389         if (r < 0)
390                 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
391
392         free(u->runtime_path);
393         u->runtime_path = NULL;
394
395         return r;
396 }
397
398 int user_stop(User *u, bool force) {
399         Session *s;
400         int r = 0, k;
401         assert(u);
402
403         /* Stop jobs have already been queued */
404         if (u->stopping) {
405                 user_save(u);
406                 return r;
407         }
408
409         LIST_FOREACH(sessions_by_user, s, u->sessions) {
410                 k = session_stop(s, force);
411                 if (k < 0)
412                         r = k;
413         }
414
415         u->stopping = true;
416
417         user_save(u);
418
419         return r;
420 }
421
422 int user_finalize(User *u) {
423         Session *s;
424         int r = 0, k;
425
426         assert(u);
427
428         if (u->started)
429                 log_debug("User %s logged out.", u->name);
430
431         LIST_FOREACH(sessions_by_user, s, u->sessions) {
432                 k = session_finalize(s);
433                 if (k < 0)
434                         r = k;
435         }
436
437         /* Kill XDG_RUNTIME_DIR */
438         k = user_remove_runtime_path(u);
439         if (k < 0)
440                 r = k;
441
442         /* Clean SysV + POSIX IPC objects */
443         if (u->manager->remove_ipc) {
444                 k = clean_ipc(u->uid);
445                 if (k < 0)
446                         r = k;
447         }
448
449         unlink(u->state_file);
450         user_add_to_gc_queue(u);
451
452         if (u->started) {
453                 user_send_signal(u, false);
454                 u->started = false;
455         }
456
457         return r;
458 }
459
460 int user_get_idle_hint(User *u, dual_timestamp *t) {
461         Session *s;
462         bool idle_hint = true;
463         dual_timestamp ts = { 0, 0 };
464
465         assert(u);
466
467         LIST_FOREACH(sessions_by_user, s, u->sessions) {
468                 dual_timestamp k;
469                 int ih;
470
471                 ih = session_get_idle_hint(s, &k);
472                 if (ih < 0)
473                         return ih;
474
475                 if (!ih) {
476                         if (!idle_hint) {
477                                 if (k.monotonic < ts.monotonic)
478                                         ts = k;
479                         } else {
480                                 idle_hint = false;
481                                 ts = k;
482                         }
483                 } else if (idle_hint) {
484
485                         if (k.monotonic > ts.monotonic)
486                                 ts = k;
487                 }
488         }
489
490         if (t)
491                 *t = ts;
492
493         return idle_hint;
494 }
495
496 int user_check_linger_file(User *u) {
497         _cleanup_free_ char *cc = NULL;
498         char *p = NULL;
499
500         cc = cescape(u->name);
501         if (!cc)
502                 return -ENOMEM;
503
504         p = strjoina("/var/lib/systemd/linger/", cc);
505
506         return access(p, F_OK) >= 0;
507 }
508
509 bool user_check_gc(User *u, bool drop_not_started) {
510         assert(u);
511
512         if (drop_not_started && !u->started)
513                 return false;
514
515         if (u->sessions)
516                 return true;
517
518         if (user_check_linger_file(u) > 0)
519                 return true;
520
521         return false;
522 }
523
524 void user_add_to_gc_queue(User *u) {
525         assert(u);
526
527         if (u->in_gc_queue)
528                 return;
529
530         LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u);
531         u->in_gc_queue = true;
532 }
533
534 UserState user_get_state(User *u) {
535         Session *i;
536
537         assert(u);
538
539         if (u->stopping)
540                 return USER_CLOSING;
541
542         if (u->sessions) {
543                 bool all_closing = true;
544
545                 LIST_FOREACH(sessions_by_user, i, u->sessions) {
546                         SessionState state;
547
548                         state = session_get_state(i);
549                         if (state == SESSION_ACTIVE)
550                                 return USER_ACTIVE;
551                         if (state != SESSION_CLOSING)
552                                 all_closing = false;
553                 }
554
555                 return all_closing ? USER_CLOSING : USER_ONLINE;
556         }
557
558         if (user_check_linger_file(u) > 0)
559                 return USER_LINGERING;
560
561         return USER_CLOSING;
562 }
563
564 int user_kill(User *u, int signo) {
565         assert(u);
566
567         /* FIXME: No way to kill a user without systemd.  */
568         return -ESRCH;
569 }
570
571 void user_elect_display(User *u) {
572         Session *graphical = NULL, *text = NULL, *other = NULL, *s;
573
574         assert(u);
575
576         /* This elects a primary session for each user, which we call
577          * the "display". We try to keep the assignment stable, but we
578          * "upgrade" to better choices. */
579
580         LIST_FOREACH(sessions_by_user, s, u->sessions) {
581
582                 if (s->class != SESSION_USER)
583                         continue;
584
585                 if (s->stopping)
586                         continue;
587
588                 if (SESSION_TYPE_IS_GRAPHICAL(s->type))
589                         graphical = s;
590                 else if (s->type == SESSION_TTY)
591                         text = s;
592                 else
593                         other = s;
594         }
595
596         if (graphical &&
597             (!u->display ||
598              u->display->class != SESSION_USER ||
599              u->display->stopping ||
600              !SESSION_TYPE_IS_GRAPHICAL(u->display->type))) {
601                 u->display = graphical;
602                 return;
603         }
604
605         if (text &&
606             (!u->display ||
607              u->display->class != SESSION_USER ||
608              u->display->stopping ||
609              u->display->type != SESSION_TTY)) {
610                 u->display = text;
611                 return;
612         }
613
614         if (other &&
615             (!u->display ||
616              u->display->class != SESSION_USER ||
617              u->display->stopping))
618                 u->display = other;
619 }
620
621 static const char* const user_state_table[_USER_STATE_MAX] = {
622         [USER_OFFLINE] = "offline",
623         [USER_OPENING] = "opening",
624         [USER_LINGERING] = "lingering",
625         [USER_ONLINE] = "online",
626         [USER_ACTIVE] = "active",
627         [USER_CLOSING] = "closing"
628 };
629
630 DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
631
632 int config_parse_tmpfs_size(
633                 const char* unit,
634                 const char *filename,
635                 unsigned line,
636                 const char *section,
637                 unsigned section_line,
638                 const char *lvalue,
639                 int ltype,
640                 const char *rvalue,
641                 void *data,
642                 void *userdata) {
643
644         size_t *sz = data;
645         const char *e;
646         int r;
647
648         assert(filename);
649         assert(lvalue);
650         assert(rvalue);
651         assert(data);
652
653         e = endswith(rvalue, "%");
654         if (e) {
655                 unsigned long ul;
656                 char *f;
657
658                 errno = 0;
659                 ul = strtoul(rvalue, &f, 10);
660                 if (errno != 0 || f != e) {
661                         log_syntax(unit, LOG_ERR, filename, line, errno ? errno : EINVAL, "Failed to parse percentage value, ignoring: %s", rvalue);
662                         return 0;
663                 }
664
665                 if (ul <= 0 || ul >= 100) {
666                         log_syntax(unit, LOG_ERR, filename, line, errno ? errno : EINVAL, "Percentage value out of range, ignoring: %s", rvalue);
667                         return 0;
668                 }
669
670                 *sz = PAGE_ALIGN((size_t) ((physical_memory() * (uint64_t) ul) / (uint64_t) 100));
671         } else {
672                 off_t o;
673
674                 r = parse_size(rvalue, 1024, &o);
675                 if (r < 0 || (off_t) (size_t) o != o) {
676                         log_syntax(unit, LOG_ERR, filename, line, r < 0 ? -r : ERANGE, "Failed to parse size value, ignoring: %s", rvalue);
677                         return 0;
678                 }
679
680                 *sz = PAGE_ALIGN((size_t) o);
681         }
682
683         return 0;
684 }