2017-11-18 17:09:20 +01:00
|
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include <errno.h>
|
2011-05-23 23:55:06 +02:00
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
|
#include "alloc-util.h"
|
2015-11-13 18:25:02 +01:00
|
|
|
|
#include "bus-common-errors.h"
|
2013-11-05 01:10:21 +01:00
|
|
|
|
#include "bus-error.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "bus-util.h"
|
2016-08-19 04:57:53 +02:00
|
|
|
|
#include "cgroup-util.h"
|
2014-03-14 01:38:19 +01:00
|
|
|
|
#include "clean-ipc.h"
|
2018-11-30 22:08:41 +01:00
|
|
|
|
#include "env-file.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "escape.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
|
#include "fd-util.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "fileio.h"
|
2016-11-07 16:14:59 +01:00
|
|
|
|
#include "format-util.h"
|
2015-10-26 21:16:26 +01:00
|
|
|
|
#include "fs-util.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "hashmap.h"
|
2015-06-17 16:29:03 +02:00
|
|
|
|
#include "label.h"
|
2019-03-13 11:35:47 +01:00
|
|
|
|
#include "limits-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
|
#include "logind-user.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "mkdir.h"
|
2015-10-26 16:18:16 +01:00
|
|
|
|
#include "parse-util.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "path-util.h"
|
|
|
|
|
#include "rm-rf.h"
|
2018-10-17 20:40:09 +02:00
|
|
|
|
#include "serialize.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "special.h"
|
2015-11-13 18:25:02 +01:00
|
|
|
|
#include "stdio-util.h"
|
2015-10-26 22:31:05 +01:00
|
|
|
|
#include "string-table.h"
|
2018-08-08 16:04:40 +02:00
|
|
|
|
#include "strv.h"
|
2018-11-30 21:05:27 +01:00
|
|
|
|
#include "tmpfile-util.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "unit-name.h"
|
2015-10-27 00:42:07 +01:00
|
|
|
|
#include "user-util.h"
|
2015-10-23 18:52:53 +02:00
|
|
|
|
#include "util.h"
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-08 15:27:49 +02:00
|
|
|
|
int user_new(User **ret,
|
|
|
|
|
Manager *m,
|
|
|
|
|
uid_t uid,
|
|
|
|
|
gid_t gid,
|
|
|
|
|
const char *name,
|
|
|
|
|
const char *home) {
|
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
_cleanup_(user_freep) User *u = NULL;
|
2015-09-29 11:03:04 +02:00
|
|
|
|
char lu[DECIMAL_STR_MAX(uid_t) + 1];
|
|
|
|
|
int r;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-03 18:53:09 +02:00
|
|
|
|
assert(ret);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
assert(m);
|
|
|
|
|
assert(name);
|
|
|
|
|
|
2018-08-03 18:53:09 +02:00
|
|
|
|
u = new(User, 1);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
if (!u)
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
2018-08-03 18:53:09 +02:00
|
|
|
|
*u = (User) {
|
|
|
|
|
.manager = m,
|
|
|
|
|
.uid = uid,
|
|
|
|
|
.gid = gid,
|
2018-08-07 11:02:00 +02:00
|
|
|
|
.last_session_timestamp = USEC_INFINITY,
|
2018-08-03 18:53:09 +02:00
|
|
|
|
};
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
|
|
|
|
u->name = strdup(name);
|
2013-06-20 03:45:08 +02:00
|
|
|
|
if (!u->name)
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return -ENOMEM;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-08 15:27:49 +02:00
|
|
|
|
u->home = strdup(home);
|
|
|
|
|
if (!u->home)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
2019-03-07 10:31:26 +01:00
|
|
|
|
path_simplify(u->home, true);
|
|
|
|
|
|
2014-04-25 13:45:15 +02:00
|
|
|
|
if (asprintf(&u->state_file, "/run/systemd/users/"UID_FMT, uid) < 0)
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return -ENOMEM;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2015-09-28 12:53:42 +02:00
|
|
|
|
if (asprintf(&u->runtime_path, "/run/user/"UID_FMT, uid) < 0)
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return -ENOMEM;
|
2015-09-28 12:53:42 +02:00
|
|
|
|
|
2018-08-03 18:53:09 +02:00
|
|
|
|
xsprintf(lu, UID_FMT, uid);
|
2015-09-29 11:03:04 +02:00
|
|
|
|
r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice);
|
|
|
|
|
if (r < 0)
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return r;
|
2015-09-29 11:03:04 +02:00
|
|
|
|
|
2015-09-29 11:18:46 +02:00
|
|
|
|
r = unit_name_build("user", lu, ".service", &u->service);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
return r;
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
return r;
|
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
r = hashmap_put(m->users, UID_TO_PTR(uid), u);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
return r;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
r = hashmap_put(m->user_units, u->slice, u);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
return r;
|
2013-06-20 03:45:08 +02:00
|
|
|
|
|
2015-09-29 11:18:46 +02:00
|
|
|
|
r = hashmap_put(m->user_units, u->service, u);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
return r;
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
r = hashmap_put(m->user_units, u->runtime_dir_service, u);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
return r;
|
|
|
|
|
|
2018-08-03 18:53:09 +02:00
|
|
|
|
*ret = TAKE_PTR(u);
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return 0;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
User *user_free(User *u) {
|
|
|
|
|
if (!u)
|
|
|
|
|
return NULL;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2011-05-25 00:55:58 +02:00
|
|
|
|
if (u->in_gc_queue)
|
2013-10-14 06:10:14 +02:00
|
|
|
|
LIST_REMOVE(gc_queue, u->manager->user_gc_queue, u);
|
2011-05-25 00:55:58 +02:00
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
while (u->sessions)
|
|
|
|
|
session_free(u->sessions);
|
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
if (u->service)
|
|
|
|
|
hashmap_remove_value(u->manager->user_units, u->service, u);
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
if (u->runtime_dir_service)
|
|
|
|
|
hashmap_remove_value(u->manager->user_units, u->runtime_dir_service, u);
|
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
if (u->slice)
|
|
|
|
|
hashmap_remove_value(u->manager->user_units, u->slice, u);
|
2013-07-02 01:46:30 +02:00
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
hashmap_remove_value(u->manager->users, UID_TO_PTR(u->uid), u);
|
2015-09-29 11:03:04 +02:00
|
|
|
|
|
2018-08-07 11:02:00 +02:00
|
|
|
|
(void) sd_event_source_unref(u->timer_event_source);
|
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
u->service_job = mfree(u->service_job);
|
2013-07-02 01:46:30 +02:00
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
u->service = mfree(u->service);
|
2018-08-06 21:44:45 +02:00
|
|
|
|
u->runtime_dir_service = mfree(u->runtime_dir_service);
|
2015-09-29 11:10:01 +02:00
|
|
|
|
u->slice = mfree(u->slice);
|
|
|
|
|
u->runtime_path = mfree(u->runtime_path);
|
|
|
|
|
u->state_file = mfree(u->state_file);
|
|
|
|
|
u->name = mfree(u->name);
|
2018-08-08 15:27:49 +02:00
|
|
|
|
u->home = mfree(u->home);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2015-09-29 11:10:01 +02:00
|
|
|
|
return mfree(u);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-17 17:45:49 +02:00
|
|
|
|
static int user_save_internal(User *u) {
|
2013-06-20 03:45:08 +02:00
|
|
|
|
_cleanup_free_ char *temp_path = NULL;
|
|
|
|
|
_cleanup_fclose_ FILE *f = NULL;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
assert(u->state_file);
|
|
|
|
|
|
tree-wide: warn when a directory path already exists but has bad mode/owner/type
When we are attempting to create directory somewhere in the bowels of /var/lib
and get an error that it already exists, it can be quite hard to diagnose what
is wrong (especially for a user who is not aware that the directory must have
the specified owner, and permissions not looser than what was requested). Let's
print a warning in most cases. A warning is appropriate, because such state is
usually a sign of borked installation and needs to be resolved by the adminstrator.
$ build/test-fs-util
Path "/tmp/test-readlink_and_make_absolute" already exists and is not a directory, refusing.
(or)
Directory "/tmp/test-readlink_and_make_absolute" already exists, but has mode 0775 that is too permissive (0755 was requested), refusing.
(or)
Directory "/tmp/test-readlink_and_make_absolute" already exists, but is owned by 1001:1000 (1000:1000 was requested), refusing.
Assertion 'mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0' failed at ../src/test/test-fs-util.c:320, function test_readlink_and_make_absolute(). Aborting.
No functional change except for the new log lines.
2018-03-22 13:03:41 +01:00
|
|
|
|
r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0, MKDIR_WARN_MODE);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
if (r < 0)
|
2015-07-29 20:31:07 +02:00
|
|
|
|
goto fail;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2011-05-25 00:55:58 +02:00
|
|
|
|
r = fopen_temporary(u->state_file, &f, &temp_path);
|
|
|
|
|
if (r < 0)
|
2015-07-29 20:31:07 +02:00
|
|
|
|
goto fail;
|
2011-05-25 00:55:58 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
(void) fchmod(fileno(f), 0644);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
|
|
|
|
fprintf(f,
|
2011-05-25 00:55:58 +02:00
|
|
|
|
"# This is private data. Do not parse.\n"
|
2011-05-23 23:55:06 +02:00
|
|
|
|
"NAME=%s\n"
|
2018-08-06 18:14:11 +02:00
|
|
|
|
"STATE=%s\n" /* friendly user-facing state */
|
|
|
|
|
"STOPPING=%s\n", /* low-level state */
|
2011-05-23 23:55:06 +02:00
|
|
|
|
u->name,
|
2018-08-06 18:14:11 +02:00
|
|
|
|
user_state_to_string(user_get_state(u)),
|
|
|
|
|
yes_no(u->stopping));
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2015-09-28 12:53:42 +02:00
|
|
|
|
/* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */
|
2011-05-23 23:55:06 +02:00
|
|
|
|
if (u->runtime_path)
|
2013-06-20 03:45:08 +02:00
|
|
|
|
fprintf(f, "RUNTIME=%s\n", u->runtime_path);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2013-07-02 01:46:30 +02:00
|
|
|
|
if (u->service_job)
|
|
|
|
|
fprintf(f, "SERVICE_JOB=%s\n", u->service_job);
|
2013-06-20 03:45:08 +02:00
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
if (u->display)
|
2013-06-20 03:45:08 +02:00
|
|
|
|
fprintf(f, "DISPLAY=%s\n", u->display->id);
|
|
|
|
|
|
|
|
|
|
if (dual_timestamp_is_set(&u->timestamp))
|
2011-05-23 23:55:06 +02:00
|
|
|
|
fprintf(f,
|
2014-02-04 01:31:53 +01:00
|
|
|
|
"REALTIME="USEC_FMT"\n"
|
|
|
|
|
"MONOTONIC="USEC_FMT"\n",
|
|
|
|
|
u->timestamp.realtime,
|
|
|
|
|
u->timestamp.monotonic);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-07 11:02:00 +02:00
|
|
|
|
if (u->last_session_timestamp != USEC_INFINITY)
|
|
|
|
|
fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n",
|
|
|
|
|
u->last_session_timestamp);
|
|
|
|
|
|
2011-07-22 21:01:15 +02:00
|
|
|
|
if (u->sessions) {
|
|
|
|
|
Session *i;
|
2012-05-22 16:46:11 +02:00
|
|
|
|
bool first;
|
2011-07-22 21:01:15 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs("SESSIONS=", f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
first = true;
|
2011-07-22 21:01:15 +02:00
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
2012-05-22 16:46:11 +02:00
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc(' ', f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs(i->id, f);
|
2011-07-22 21:01:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs("\nSEATS=", f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
first = true;
|
2011-07-22 21:01:15 +02:00
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
2012-05-22 16:46:11 +02:00
|
|
|
|
if (!i->seat)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc(' ', f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs(i->seat->id, f);
|
2011-07-22 21:01:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs("\nACTIVE_SESSIONS=", f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
first = true;
|
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
|
|
|
|
if (!session_is_active(i))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc(' ', f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs(i->id, f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
}
|
2011-07-22 21:01:15 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs("\nONLINE_SESSIONS=", f);
|
2012-09-04 00:57:58 +02:00
|
|
|
|
first = true;
|
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
|
|
|
|
if (session_get_state(i) == SESSION_CLOSING)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc(' ', f);
|
2012-09-04 00:57:58 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs(i->id, f);
|
2012-09-04 00:57:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs("\nACTIVE_SEATS=", f);
|
2012-05-22 16:46:11 +02:00
|
|
|
|
first = true;
|
2011-07-22 21:01:15 +02:00
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
2012-05-22 16:46:11 +02:00
|
|
|
|
if (!session_is_active(i) || !i->seat)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc(' ', f);
|
2012-09-04 00:47:02 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs(i->seat->id, f);
|
2011-07-22 21:01:15 +02:00
|
|
|
|
}
|
2012-09-04 00:57:58 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs("\nONLINE_SEATS=", f);
|
2012-09-04 00:57:58 +02:00
|
|
|
|
first = true;
|
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
|
|
|
|
if (session_get_state(i) == SESSION_CLOSING || !i->seat)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc(' ', f);
|
2012-09-04 00:57:58 +02:00
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputs(i->seat->id, f);
|
2012-09-04 00:57:58 +02:00
|
|
|
|
}
|
2017-12-11 19:50:30 +01:00
|
|
|
|
fputc('\n', f);
|
2011-07-22 21:01:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-29 20:31:07 +02:00
|
|
|
|
r = fflush_and_check(f);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
goto fail;
|
2011-05-25 00:55:58 +02:00
|
|
|
|
|
2015-07-29 20:31:07 +02:00
|
|
|
|
if (rename(temp_path, u->state_file) < 0) {
|
2011-05-23 23:55:06 +02:00
|
|
|
|
r = -errno;
|
2015-07-29 20:31:07 +02:00
|
|
|
|
goto fail;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-29 20:31:07 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
(void) unlink(u->state_file);
|
|
|
|
|
|
|
|
|
|
if (temp_path)
|
|
|
|
|
(void) unlink(temp_path);
|
2011-05-25 00:55:58 +02:00
|
|
|
|
|
2015-07-29 20:31:07 +02:00
|
|
|
|
return log_error_errno(r, "Failed to save user data %s: %m", u->state_file);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-17 17:45:49 +02:00
|
|
|
|
int user_save(User *u) {
|
|
|
|
|
assert(u);
|
|
|
|
|
|
|
|
|
|
if (!u->started)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2018-08-03 18:53:09 +02:00
|
|
|
|
return user_save_internal(u);
|
2015-06-17 17:45:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
int user_load(User *u) {
|
2018-08-07 11:02:00 +02:00
|
|
|
|
_cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
|
2013-06-20 03:45:08 +02:00
|
|
|
|
int r;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
|
2018-11-12 14:04:47 +01:00
|
|
|
|
r = parse_env_file(NULL, u->state_file,
|
2018-08-07 11:02:00 +02:00
|
|
|
|
"SERVICE_JOB", &u->service_job,
|
|
|
|
|
"STOPPING", &stopping,
|
|
|
|
|
"REALTIME", &realtime,
|
|
|
|
|
"MONOTONIC", &monotonic,
|
2018-11-12 14:18:03 +01:00
|
|
|
|
"LAST_SESSION_TIMESTAMP", &last_session_timestamp);
|
2018-08-03 19:04:35 +02:00
|
|
|
|
if (r == -ENOENT)
|
|
|
|
|
return 0;
|
|
|
|
|
if (r < 0)
|
2016-07-23 02:27:45 +02:00
|
|
|
|
return log_error_errno(r, "Failed to read %s: %m", u->state_file);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-06 18:14:11 +02:00
|
|
|
|
if (stopping) {
|
|
|
|
|
r = parse_boolean(stopping);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping);
|
|
|
|
|
else
|
|
|
|
|
u->stopping = r;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-15 23:26:34 +01:00
|
|
|
|
if (realtime)
|
2018-10-17 20:40:09 +02:00
|
|
|
|
(void) deserialize_usec(realtime, &u->timestamp.realtime);
|
2016-02-15 23:26:34 +01:00
|
|
|
|
if (monotonic)
|
2018-10-17 20:40:09 +02:00
|
|
|
|
(void) deserialize_usec(monotonic, &u->timestamp.monotonic);
|
2018-08-07 11:02:00 +02:00
|
|
|
|
if (last_session_timestamp)
|
2018-10-17 20:40:09 +02:00
|
|
|
|
(void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp);
|
2013-06-20 03:45:08 +02:00
|
|
|
|
|
2018-08-06 18:14:11 +02:00
|
|
|
|
return 0;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
static void user_start_service(User *u) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
2013-07-02 01:46:30 +02:00
|
|
|
|
int r;
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
assert(u);
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly
|
|
|
|
|
* start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by
|
|
|
|
|
* user@.service and the session scopes as dependencies. */
|
|
|
|
|
|
2015-09-29 11:18:46 +02:00
|
|
|
|
u->service_job = mfree(u->service_job);
|
2013-07-02 01:46:30 +02:00
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
r = manager_start_unit(u->manager, u->service, &error, &u->service_job);
|
2017-10-15 21:24:32 +02:00
|
|
|
|
if (r < 0)
|
2019-02-22 15:50:55 +01:00
|
|
|
|
log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING, r,
|
|
|
|
|
"Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int user_start(User *u) {
|
|
|
|
|
assert(u);
|
|
|
|
|
|
2015-09-29 11:36:18 +02:00
|
|
|
|
if (u->started && !u->stopping)
|
2011-06-21 21:46:13 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. We have to clear
|
|
|
|
|
* that flag before queing the start-jobs again. If they succeed, the user object can be re-used just fine
|
|
|
|
|
* (pid1 takes care of job-ordering and proper restart), but if they fail, we want to force another user_stop()
|
|
|
|
|
* so possibly pending units are stopped. */
|
2015-09-29 11:36:18 +02:00
|
|
|
|
u->stopping = false;
|
|
|
|
|
|
2017-12-09 19:30:17 +01:00
|
|
|
|
if (!u->started)
|
2017-12-07 13:12:13 +01:00
|
|
|
|
log_debug("Starting services for new user %s.", u->name);
|
2015-09-29 11:36:18 +02:00
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up
|
|
|
|
|
* systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */
|
2015-06-17 17:45:49 +02:00
|
|
|
|
user_save_internal(u);
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* Start user@UID.service */
|
|
|
|
|
user_start_service(u);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2015-09-29 11:36:18 +02:00
|
|
|
|
if (!u->started) {
|
|
|
|
|
if (!dual_timestamp_is_set(&u->timestamp))
|
|
|
|
|
dual_timestamp_get(&u->timestamp);
|
|
|
|
|
user_send_signal(u, true);
|
|
|
|
|
u->started = true;
|
|
|
|
|
}
|
2011-06-21 21:46:13 +02:00
|
|
|
|
|
2011-06-28 03:52:22 +02:00
|
|
|
|
/* Save new user data */
|
|
|
|
|
user_save(u);
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
static void user_stop_service(User *u) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
2013-07-02 01:46:30 +02:00
|
|
|
|
int r;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
|
|
|
|
assert(u);
|
2018-08-06 21:44:45 +02:00
|
|
|
|
assert(u->service);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* The reverse of user_start_service(). Note that we only stop user@UID.service here, and let StopWhenUnneeded=
|
|
|
|
|
* deal with the slice and the user-runtime-dir@.service instance. */
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
u->service_job = mfree(u->service_job);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
r = manager_stop_unit(u->manager, u->service, &error, &u->service_job);
|
2018-08-07 03:10:12 +02:00
|
|
|
|
if (r < 0)
|
2018-08-06 21:44:45 +02:00
|
|
|
|
log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
|
2013-07-02 01:46:30 +02:00
|
|
|
|
}
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2014-02-09 02:29:56 +01:00
|
|
|
|
int user_stop(User *u, bool force) {
|
2011-05-23 23:55:06 +02:00
|
|
|
|
Session *s;
|
2018-08-06 21:44:45 +02:00
|
|
|
|
int r = 0;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
assert(u);
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* This is called whenever we begin with tearing down a user record. It's called in two cases: explicit API
|
|
|
|
|
* request to do so via the bus (in which case 'force' is true) and automatically due to GC, if there's no
|
|
|
|
|
* session left pinning it (in which case 'force' is false). Note that this just initiates tearing down of the
|
|
|
|
|
* user, the User object will remain in memory until user_finalize() is called, see below. */
|
|
|
|
|
|
|
|
|
|
if (!u->started)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if (u->stopping) { /* Stop jobs have already been queued */
|
2014-02-13 18:31:43 +01:00
|
|
|
|
user_save(u);
|
2018-08-06 21:44:45 +02:00
|
|
|
|
return 0;
|
2014-02-13 18:31:43 +01:00
|
|
|
|
}
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
LIST_FOREACH(sessions_by_user, s, u->sessions) {
|
2018-08-06 21:44:45 +02:00
|
|
|
|
int k;
|
|
|
|
|
|
2014-02-09 02:29:56 +01:00
|
|
|
|
k = session_stop(s, force);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
if (k < 0)
|
|
|
|
|
r = k;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
user_stop_service(u);
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2014-02-06 18:32:14 +01:00
|
|
|
|
u->stopping = true;
|
|
|
|
|
|
2013-08-13 17:59:28 +02:00
|
|
|
|
user_save(u);
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int user_finalize(User *u) {
|
|
|
|
|
Session *s;
|
|
|
|
|
int r = 0, k;
|
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
/* Called when the user is really ready to be freed, i.e. when all unit stop jobs and suchlike for it are
|
|
|
|
|
* done. This is called as a result of an earlier user_done() when all jobs are completed. */
|
|
|
|
|
|
2013-08-13 17:59:28 +02:00
|
|
|
|
if (u->started)
|
|
|
|
|
log_debug("User %s logged out.", u->name);
|
|
|
|
|
|
|
|
|
|
LIST_FOREACH(sessions_by_user, s, u->sessions) {
|
|
|
|
|
k = session_finalize(s);
|
|
|
|
|
if (k < 0)
|
|
|
|
|
r = k;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-01 19:24:40 +02:00
|
|
|
|
/* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
|
|
|
|
|
* are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
|
|
|
|
|
* system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such
|
|
|
|
|
* cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because
|
|
|
|
|
* a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up,
|
|
|
|
|
* and do it only for normal users. */
|
2017-12-02 12:59:21 +01:00
|
|
|
|
if (u->manager->remove_ipc && !uid_is_system(u->uid)) {
|
2016-08-01 19:24:40 +02:00
|
|
|
|
k = clean_ipc_by_uid(u->uid);
|
2014-03-14 01:38:19 +01:00
|
|
|
|
if (k < 0)
|
|
|
|
|
r = k;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-06 19:04:49 +02:00
|
|
|
|
(void) unlink(u->state_file);
|
2011-05-25 00:58:55 +02:00
|
|
|
|
user_add_to_gc_queue(u);
|
|
|
|
|
|
2013-08-13 17:59:28 +02:00
|
|
|
|
if (u->started) {
|
2011-06-24 19:42:45 +02:00
|
|
|
|
user_send_signal(u, false);
|
2013-08-13 17:59:28 +02:00
|
|
|
|
u->started = false;
|
|
|
|
|
}
|
2011-06-21 21:46:13 +02:00
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-17 15:59:18 +02:00
|
|
|
|
int user_get_idle_hint(User *u, dual_timestamp *t) {
|
|
|
|
|
Session *s;
|
|
|
|
|
bool idle_hint = true;
|
2015-06-16 01:08:12 +02:00
|
|
|
|
dual_timestamp ts = DUAL_TIMESTAMP_NULL;
|
2011-06-17 15:59:18 +02:00
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
|
|
|
|
|
LIST_FOREACH(sessions_by_user, s, u->sessions) {
|
|
|
|
|
dual_timestamp k;
|
|
|
|
|
int ih;
|
|
|
|
|
|
|
|
|
|
ih = session_get_idle_hint(s, &k);
|
|
|
|
|
if (ih < 0)
|
|
|
|
|
return ih;
|
|
|
|
|
|
|
|
|
|
if (!ih) {
|
|
|
|
|
if (!idle_hint) {
|
|
|
|
|
if (k.monotonic < ts.monotonic)
|
|
|
|
|
ts = k;
|
|
|
|
|
} else {
|
|
|
|
|
idle_hint = false;
|
|
|
|
|
ts = k;
|
|
|
|
|
}
|
|
|
|
|
} else if (idle_hint) {
|
|
|
|
|
|
|
|
|
|
if (k.monotonic > ts.monotonic)
|
|
|
|
|
ts = k;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (t)
|
|
|
|
|
*t = ts;
|
|
|
|
|
|
|
|
|
|
return idle_hint;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-05 01:27:27 +01:00
|
|
|
|
int user_check_linger_file(User *u) {
|
2013-11-05 01:10:21 +01:00
|
|
|
|
_cleanup_free_ char *cc = NULL;
|
|
|
|
|
char *p = NULL;
|
2012-05-31 19:47:52 +02:00
|
|
|
|
|
2013-11-05 01:10:21 +01:00
|
|
|
|
cc = cescape(u->name);
|
|
|
|
|
if (!cc)
|
2012-05-31 19:47:52 +02:00
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
2015-02-03 02:05:59 +01:00
|
|
|
|
p = strjoina("/var/lib/systemd/linger/", cc);
|
2018-08-08 16:03:11 +02:00
|
|
|
|
if (access(p, F_OK) < 0) {
|
|
|
|
|
if (errno != ENOENT)
|
|
|
|
|
return -errno;
|
2012-05-31 19:47:52 +02:00
|
|
|
|
|
2018-08-08 16:03:11 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2012-05-31 19:47:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-08 16:04:40 +02:00
|
|
|
|
static bool user_unit_active(User *u) {
|
|
|
|
|
const char *i;
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
assert(u->service);
|
|
|
|
|
assert(u->runtime_dir_service);
|
|
|
|
|
assert(u->slice);
|
|
|
|
|
|
|
|
|
|
FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) {
|
|
|
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
|
|
|
|
|
|
|
r = manager_unit_is_active(u->manager, i, &error);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", u->service, bus_error_message(&error, r));
|
|
|
|
|
if (r != 0)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-15 13:14:35 +01:00
|
|
|
|
bool user_may_gc(User *u, bool drop_not_started) {
|
2018-08-06 19:00:49 +02:00
|
|
|
|
int r;
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
assert(u);
|
|
|
|
|
|
2011-06-29 03:48:16 +02:00
|
|
|
|
if (drop_not_started && !u->started)
|
2018-02-15 13:14:35 +01:00
|
|
|
|
return true;
|
2011-06-29 00:06:04 +02:00
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
if (u->sessions)
|
2018-02-15 13:14:35 +01:00
|
|
|
|
return false;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-07 11:02:00 +02:00
|
|
|
|
if (u->last_session_timestamp != USEC_INFINITY) {
|
|
|
|
|
/* All sessions have been closed. Let's see if we shall leave the user record around for a bit */
|
|
|
|
|
|
|
|
|
|
if (u->manager->user_stop_delay == USEC_INFINITY)
|
|
|
|
|
return false; /* Leave it around forever! */
|
|
|
|
|
if (u->manager->user_stop_delay > 0 &&
|
|
|
|
|
now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, u->manager->user_stop_delay))
|
|
|
|
|
return false; /* Leave it around for a bit longer. */
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-08 16:04:40 +02:00
|
|
|
|
/* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check
|
|
|
|
|
* if any of the three units that we maintain for this user is still around. If none of them is,
|
|
|
|
|
* there's no need to keep this user around even if lingering is enabled. */
|
|
|
|
|
if (user_check_linger_file(u) > 0 && user_unit_active(u))
|
2018-02-15 13:14:35 +01:00
|
|
|
|
return false;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2018-08-06 19:00:49 +02:00
|
|
|
|
/* Check if our job is still pending */
|
|
|
|
|
if (u->service_job) {
|
|
|
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
2013-11-05 01:10:21 +01:00
|
|
|
|
|
2018-08-06 19:00:49 +02:00
|
|
|
|
r = manager_job_is_active(u->manager, u->service_job, &error);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, bus_error_message(&error, r));
|
|
|
|
|
if (r != 0)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Note that we don't care if the three units we manage for each user object are up or not, as we are managing
|
|
|
|
|
* their state rather than tracking it. */
|
2013-08-13 17:59:28 +02:00
|
|
|
|
|
2018-02-15 13:14:35 +01:00
|
|
|
|
return true;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-05-25 00:55:58 +02:00
|
|
|
|
void user_add_to_gc_queue(User *u) {
|
|
|
|
|
assert(u);
|
|
|
|
|
|
|
|
|
|
if (u->in_gc_queue)
|
|
|
|
|
return;
|
|
|
|
|
|
2013-10-14 06:10:14 +02:00
|
|
|
|
LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u);
|
2011-05-25 00:55:58 +02:00
|
|
|
|
u->in_gc_queue = true;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
UserState user_get_state(User *u) {
|
|
|
|
|
Session *i;
|
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
|
2014-02-06 18:32:14 +01:00
|
|
|
|
if (u->stopping)
|
|
|
|
|
return USER_CLOSING;
|
|
|
|
|
|
2018-08-06 21:44:45 +02:00
|
|
|
|
if (!u->started || u->service_job)
|
2013-08-13 17:59:28 +02:00
|
|
|
|
return USER_OPENING;
|
2012-09-04 00:47:01 +02:00
|
|
|
|
|
2014-02-06 18:32:14 +01:00
|
|
|
|
if (u->sessions) {
|
|
|
|
|
bool all_closing = true;
|
|
|
|
|
|
|
|
|
|
LIST_FOREACH(sessions_by_user, i, u->sessions) {
|
2014-02-08 20:51:57 +01:00
|
|
|
|
SessionState state;
|
|
|
|
|
|
|
|
|
|
state = session_get_state(i);
|
|
|
|
|
if (state == SESSION_ACTIVE)
|
2014-02-06 18:32:14 +01:00
|
|
|
|
return USER_ACTIVE;
|
2014-02-08 20:51:57 +01:00
|
|
|
|
if (state != SESSION_CLOSING)
|
2014-02-06 18:32:14 +01:00
|
|
|
|
all_closing = false;
|
|
|
|
|
}
|
2011-05-23 23:55:06 +02:00
|
|
|
|
|
2012-09-04 00:47:01 +02:00
|
|
|
|
return all_closing ? USER_CLOSING : USER_ONLINE;
|
2014-02-06 18:32:14 +01:00
|
|
|
|
}
|
2012-05-31 19:47:52 +02:00
|
|
|
|
|
2018-08-08 16:04:40 +02:00
|
|
|
|
if (user_check_linger_file(u) > 0 && user_unit_active(u))
|
2012-05-31 19:47:52 +02:00
|
|
|
|
return USER_LINGERING;
|
|
|
|
|
|
|
|
|
|
return USER_CLOSING;
|
2011-05-23 23:55:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-07-13 19:58:35 +02:00
|
|
|
|
int user_kill(User *u, int signo) {
|
|
|
|
|
assert(u);
|
|
|
|
|
|
2013-07-02 01:46:30 +02:00
|
|
|
|
return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL);
|
2011-07-13 19:58:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-10 01:27:32 +02:00
|
|
|
|
static bool elect_display_filter(Session *s) {
|
2018-08-06 19:02:29 +02:00
|
|
|
|
/* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
assert(s);
|
|
|
|
|
|
2018-08-06 19:02:29 +02:00
|
|
|
|
return s->class == SESSION_USER && s->started && !s->stopping;
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-10 01:27:32 +02:00
|
|
|
|
static int elect_display_compare(Session *s1, Session *s2) {
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
/* Indexed by SessionType. Lower numbers mean more preferred. */
|
|
|
|
|
const int type_ranks[_SESSION_TYPE_MAX] = {
|
|
|
|
|
[SESSION_UNSPECIFIED] = 0,
|
|
|
|
|
[SESSION_TTY] = -2,
|
|
|
|
|
[SESSION_X11] = -3,
|
|
|
|
|
[SESSION_WAYLAND] = -3,
|
|
|
|
|
[SESSION_MIR] = -3,
|
|
|
|
|
[SESSION_WEB] = -1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Calculate the partial order relationship between s1 and s2,
|
|
|
|
|
* returning < 0 if s1 is preferred as the user’s ‘primary session’,
|
|
|
|
|
* 0 if s1 and s2 are equally preferred or incomparable, or > 0 if s2
|
|
|
|
|
* is preferred.
|
|
|
|
|
*
|
|
|
|
|
* s1 or s2 may be NULL. */
|
2015-06-12 11:37:11 +02:00
|
|
|
|
if (!s1 && !s2)
|
|
|
|
|
return 0;
|
|
|
|
|
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
if ((s1 == NULL) != (s2 == NULL))
|
|
|
|
|
return (s1 == NULL) - (s2 == NULL);
|
|
|
|
|
|
|
|
|
|
if (s1->stopping != s2->stopping)
|
|
|
|
|
return s1->stopping - s2->stopping;
|
|
|
|
|
|
|
|
|
|
if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER))
|
|
|
|
|
return (s1->class != SESSION_USER) - (s2->class != SESSION_USER);
|
|
|
|
|
|
|
|
|
|
if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID))
|
|
|
|
|
return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID);
|
|
|
|
|
|
|
|
|
|
if (s1->type != s2->type)
|
|
|
|
|
return type_ranks[s1->type] - type_ranks[s2->type];
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-19 02:03:20 +02:00
|
|
|
|
void user_elect_display(User *u) {
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
Session *s;
|
2014-05-19 02:03:20 +02:00
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
|
2018-08-06 19:02:29 +02:00
|
|
|
|
/* This elects a primary session for each user, which we call the "display". We try to keep the assignment
|
|
|
|
|
* stable, but we "upgrade" to better choices. */
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
log_debug("Electing new display for user %s", u->name);
|
2014-05-19 02:03:20 +02:00
|
|
|
|
|
|
|
|
|
LIST_FOREACH(sessions_by_user, s, u->sessions) {
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
if (!elect_display_filter(s)) {
|
|
|
|
|
log_debug("Ignoring session %s", s->id);
|
2014-05-19 02:03:20 +02:00
|
|
|
|
continue;
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
}
|
2014-05-19 02:03:20 +02:00
|
|
|
|
|
logind: Fix user_elect_display() to be more stable
The previous implementation of user_elect_display() could easily end up
overwriting the user’s valid graphical session with a new TTY session.
For example, consider the situation where there is one session:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
it is initially elected as the user’s display (i.e. u->display = c1).
If another session is started, on a different VT, the sessions_by_user
list becomes:
c1, type = SESSION_X11, !stopping, class = SESSION_USER
c2, type = SESSION_TTY, !stopping, class = SESSION_USER
In the previous code, graphical = c1 and text = c2, as expected.
However, neither graphical nor text fulfil the conditions for setting
u->display = graphical (because neither is better than u->display), so
the code falls through to check the text variable. The conditions for
this match, as u->display->type != SESSION_TTY (it’s actually
SESSION_X11). Hence u->display is set to c2, which is incorrect, because
session c1 is still valid.
Refactor user_elect_display() to use a more explicit filter and
pre-order comparison over the sessions. This can be demonstrated to be
stable and only ever ‘upgrade’ the session to a more graphical one.
https://bugs.freedesktop.org/show_bug.cgi?id=90769
2015-05-29 11:49:21 +02:00
|
|
|
|
if (elect_display_compare(s, u->display) < 0) {
|
|
|
|
|
log_debug("Choosing session %s in preference to %s", s->id, u->display ? u->display->id : "-");
|
|
|
|
|
u->display = s;
|
|
|
|
|
}
|
2014-08-14 02:59:02 +02:00
|
|
|
|
}
|
2014-05-19 02:03:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 11:02:00 +02:00
|
|
|
|
static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) {
|
|
|
|
|
User *u = userdata;
|
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
user_add_to_gc_queue(u);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void user_update_last_session_timer(User *u) {
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
assert(u);
|
|
|
|
|
|
|
|
|
|
if (u->sessions) {
|
|
|
|
|
/* There are sessions, turn off the timer */
|
|
|
|
|
u->last_session_timestamp = USEC_INFINITY;
|
|
|
|
|
u->timer_event_source = sd_event_source_unref(u->timer_event_source);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->last_session_timestamp != USEC_INFINITY)
|
|
|
|
|
return; /* Timer already started */
|
|
|
|
|
|
|
|
|
|
u->last_session_timestamp = now(CLOCK_MONOTONIC);
|
|
|
|
|
|
|
|
|
|
assert(!u->timer_event_source);
|
|
|
|
|
|
|
|
|
|
if (u->manager->user_stop_delay == 0 || u->manager->user_stop_delay == USEC_INFINITY)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) {
|
|
|
|
|
log_debug("Not allocating user stop timeout, since we are already exiting.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r = sd_event_add_time(u->manager->event,
|
|
|
|
|
&u->timer_event_source,
|
|
|
|
|
CLOCK_MONOTONIC,
|
|
|
|
|
usec_add(u->last_session_timestamp, u->manager->user_stop_delay), 0,
|
|
|
|
|
user_stop_timeout_callback, u);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m");
|
|
|
|
|
|
|
|
|
|
if (DEBUG_LOGGING) {
|
|
|
|
|
char s[FORMAT_TIMESPAN_MAX];
|
|
|
|
|
|
|
|
|
|
log_debug("Last session of user '%s' logged out, terminating user context in %s.",
|
|
|
|
|
u->name,
|
|
|
|
|
format_timespan(s, sizeof(s), u->manager->user_stop_delay, USEC_PER_MSEC));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-23 23:55:06 +02:00
|
|
|
|
static const char* const user_state_table[_USER_STATE_MAX] = {
|
|
|
|
|
[USER_OFFLINE] = "offline",
|
2013-07-02 01:46:30 +02:00
|
|
|
|
[USER_OPENING] = "opening",
|
2011-05-23 23:55:06 +02:00
|
|
|
|
[USER_LINGERING] = "lingering",
|
|
|
|
|
[USER_ONLINE] = "online",
|
2012-05-31 19:47:52 +02:00
|
|
|
|
[USER_ACTIVE] = "active",
|
|
|
|
|
[USER_CLOSING] = "closing"
|
2011-05-23 23:55:06 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
|
2014-03-04 19:20:21 +01:00
|
|
|
|
|
|
|
|
|
int config_parse_tmpfs_size(
|
|
|
|
|
const char* unit,
|
|
|
|
|
const char *filename,
|
|
|
|
|
unsigned line,
|
|
|
|
|
const char *section,
|
|
|
|
|
unsigned section_line,
|
|
|
|
|
const char *lvalue,
|
|
|
|
|
int ltype,
|
|
|
|
|
const char *rvalue,
|
|
|
|
|
void *data,
|
|
|
|
|
void *userdata) {
|
|
|
|
|
|
2018-05-17 04:33:13 +02:00
|
|
|
|
uint64_t *sz = data;
|
2014-03-04 19:20:21 +01:00
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
assert(filename);
|
|
|
|
|
assert(lvalue);
|
|
|
|
|
assert(rvalue);
|
|
|
|
|
assert(data);
|
|
|
|
|
|
2016-06-08 19:25:38 +02:00
|
|
|
|
/* First, try to parse as percentage */
|
2018-07-02 18:52:42 +02:00
|
|
|
|
r = parse_permille(rvalue);
|
|
|
|
|
if (r > 0 && r < 1000)
|
|
|
|
|
*sz = physical_memory_scale(r, 1000U);
|
2016-06-08 19:25:38 +02:00
|
|
|
|
else {
|
2015-09-10 18:16:18 +02:00
|
|
|
|
uint64_t k;
|
2014-03-04 19:20:21 +01:00
|
|
|
|
|
2016-06-08 19:25:38 +02:00
|
|
|
|
/* If the passed argument was not a percentage, or out of range, parse as byte size */
|
|
|
|
|
|
2015-09-10 18:16:18 +02:00
|
|
|
|
r = parse_size(rvalue, 1024, &k);
|
2018-06-28 07:05:39 +02:00
|
|
|
|
if (r >= 0 && (k <= 0 || (uint64_t) (size_t) k != k))
|
|
|
|
|
r = -ERANGE;
|
|
|
|
|
if (r < 0) {
|
|
|
|
|
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue);
|
2014-03-04 19:20:21 +01:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-10 18:16:18 +02:00
|
|
|
|
*sz = PAGE_ALIGN((size_t) k);
|
2014-03-04 19:20:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2016-07-19 17:19:58 +02:00
|
|
|
|
|
2017-12-07 22:25:26 +01:00
|
|
|
|
int config_parse_compat_user_tasks_max(
|
|
|
|
|
const char *unit,
|
2016-07-19 17:19:58 +02:00
|
|
|
|
const char *filename,
|
|
|
|
|
unsigned line,
|
|
|
|
|
const char *section,
|
|
|
|
|
unsigned section_line,
|
|
|
|
|
const char *lvalue,
|
|
|
|
|
int ltype,
|
|
|
|
|
const char *rvalue,
|
|
|
|
|
void *data,
|
|
|
|
|
void *userdata) {
|
|
|
|
|
|
|
|
|
|
assert(filename);
|
|
|
|
|
assert(lvalue);
|
|
|
|
|
assert(rvalue);
|
|
|
|
|
assert(data);
|
|
|
|
|
|
2017-12-07 22:25:26 +01:00
|
|
|
|
log_syntax(unit, LOG_NOTICE, filename, line, 0,
|
|
|
|
|
"Support for option %s= has been removed.",
|
|
|
|
|
lvalue);
|
2018-07-16 20:10:29 +02:00
|
|
|
|
log_info("Hint: try creating /etc/systemd/system/user-.slice.d/50-limits.conf with:\n"
|
2017-12-07 22:25:26 +01:00
|
|
|
|
" [Slice]\n"
|
|
|
|
|
" TasksMax=%s",
|
|
|
|
|
rvalue);
|
2016-07-19 17:19:58 +02:00
|
|
|
|
return 0;
|
|
|
|
|
}
|