]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
20263082 | 2 | |
20263082 | 3 | #include <unistd.h> |
6ad1d1ed DDM |
4 | #include "sd-bus.h" |
5 | #include "sd-event.h" | |
20263082 | 6 | |
b5efdb8a | 7 | #include "alloc-util.h" |
90558f31 | 8 | #include "bus-common-errors.h" |
cc377381 | 9 | #include "bus-error.h" |
b7147168 | 10 | #include "bus-locator.h" |
4f5dd394 | 11 | #include "bus-util.h" |
66cdd0f2 | 12 | #include "clean-ipc.h" |
686d13b9 | 13 | #include "env-file.h" |
6ad1d1ed | 14 | #include "errno-util.h" |
4f5dd394 | 15 | #include "escape.h" |
3ffd4af2 | 16 | #include "fd-util.h" |
f97b34a6 | 17 | #include "format-util.h" |
f4f15635 | 18 | #include "fs-util.h" |
4f5dd394 | 19 | #include "hashmap.h" |
eefc66aa | 20 | #include "limits-util.h" |
6ad1d1ed | 21 | #include "logind-session.h" |
214c93c8 | 22 | #include "logind.h" |
6ecda0fb | 23 | #include "logind-dbus.h" |
214c93c8 | 24 | #include "logind-seat.h" |
ed5033fd | 25 | #include "logind-user.h" |
1cf40697 | 26 | #include "logind-user-dbus.h" |
35cd0ba5 | 27 | #include "mkdir-label.h" |
6bedfcbb | 28 | #include "parse-util.h" |
ed5033fd | 29 | #include "percent-util.h" |
d68c645b | 30 | #include "serialize.h" |
4f5dd394 | 31 | #include "special.h" |
90558f31 | 32 | #include "stdio-util.h" |
8b43440b | 33 | #include "string-table.h" |
6ad1d1ed | 34 | #include "string-util.h" |
4e5b605a | 35 | #include "strv.h" |
e4de7287 | 36 | #include "tmpfile-util.h" |
8e1ac16b | 37 | #include "uid-classification.h" |
4f5dd394 | 38 | #include "unit-name.h" |
6ad1d1ed | 39 | #include "user-record.h" |
ee104e11 | 40 | #include "user-util.h" |
20263082 | 41 | |
2454cee3 | 42 | int user_new(Manager *m, UserRecord *ur, User **ret) { |
157f5057 | 43 | _cleanup_(user_freep) User *u = NULL; |
6230bf75 | 44 | char lu[DECIMAL_STR_MAX(uid_t) + 1]; |
6230bf75 | 45 | int r; |
20263082 LP |
46 | |
47 | assert(m); | |
22c902fa | 48 | assert(ur); |
2454cee3 | 49 | assert(ret); |
22c902fa LP |
50 | |
51 | if (!ur->user_name) | |
52 | return -EINVAL; | |
53 | ||
54 | if (!uid_is_valid(ur->uid)) | |
55 | return -EINVAL; | |
20263082 | 56 | |
8c29a457 | 57 | u = new(User, 1); |
20263082 | 58 | if (!u) |
157f5057 DH |
59 | return -ENOMEM; |
60 | ||
8c29a457 LP |
61 | *u = (User) { |
62 | .manager = m, | |
22c902fa | 63 | .user_record = user_record_ref(ur), |
9afe9efb | 64 | .last_session_timestamp = USEC_INFINITY, |
d2a4c379 | 65 | .gc_mode = USER_GC_BY_ANY, |
8c29a457 | 66 | }; |
20263082 | 67 | |
22c902fa | 68 | if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0) |
d5ac9d06 LP |
69 | return -ENOMEM; |
70 | ||
22c902fa | 71 | if (asprintf(&u->runtime_path, "/run/user/" UID_FMT, ur->uid) < 0) |
157f5057 | 72 | return -ENOMEM; |
f9e4283d | 73 | |
22c902fa | 74 | xsprintf(lu, UID_FMT, ur->uid); |
6230bf75 DH |
75 | r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice); |
76 | if (r < 0) | |
157f5057 | 77 | return r; |
6230bf75 | 78 | |
e2a42c0c | 79 | r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_unit); |
b690ef12 DH |
80 | if (r < 0) |
81 | return r; | |
82 | ||
e2a42c0c | 83 | r = unit_name_build("user", lu, ".service", &u->service_manager_unit); |
25a1ab4e LP |
84 | if (r < 0) |
85 | return r; | |
86 | ||
22c902fa | 87 | r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u); |
157f5057 DH |
88 | if (r < 0) |
89 | return r; | |
20263082 | 90 | |
157f5057 DH |
91 | r = hashmap_put(m->user_units, u->slice, u); |
92 | if (r < 0) | |
93 | return r; | |
9444b1f2 | 94 | |
e2a42c0c | 95 | r = hashmap_put(m->user_units, u->runtime_dir_unit, u); |
b690ef12 DH |
96 | if (r < 0) |
97 | return r; | |
98 | ||
e2a42c0c | 99 | r = hashmap_put(m->user_units, u->service_manager_unit, u); |
25a1ab4e LP |
100 | if (r < 0) |
101 | return r; | |
102 | ||
8c29a457 | 103 | *ret = TAKE_PTR(u); |
157f5057 | 104 | return 0; |
20263082 LP |
105 | } |
106 | ||
157f5057 DH |
107 | User *user_free(User *u) { |
108 | if (!u) | |
109 | return NULL; | |
20263082 | 110 | |
14c3baca | 111 | if (u->in_gc_queue) |
71fda00f | 112 | LIST_REMOVE(gc_queue, u->manager->user_gc_queue, u); |
14c3baca | 113 | |
20263082 LP |
114 | while (u->sessions) |
115 | session_free(u->sessions); | |
116 | ||
e2a42c0c | 117 | sd_event_source_unref(u->timer_event_source); |
157f5057 | 118 | |
e2a42c0c MY |
119 | if (u->service_manager_unit) { |
120 | (void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u); | |
121 | free(u->service_manager_job); | |
122 | free(u->service_manager_unit); | |
123 | } | |
25a1ab4e | 124 | |
e2a42c0c MY |
125 | if (u->runtime_dir_unit) { |
126 | (void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u); | |
127 | free(u->runtime_dir_job); | |
128 | free(u->runtime_dir_unit); | |
129 | } | |
130 | ||
131 | if (u->slice) { | |
74e75a05 | 132 | (void) hashmap_remove_value(u->manager->user_units, u->slice, u); |
e2a42c0c MY |
133 | free(u->slice); |
134 | } | |
fb6becb4 | 135 | |
74e75a05 | 136 | (void) hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u); |
6230bf75 | 137 | |
74e75a05 MY |
138 | free(u->runtime_path); |
139 | free(u->state_file); | |
22c902fa LP |
140 | |
141 | user_record_unref(u->user_record); | |
20263082 | 142 | |
157f5057 | 143 | return mfree(u); |
20263082 LP |
144 | } |
145 | ||
71161305 | 146 | static int user_save_internal(User *u) { |
20263082 LP |
147 | int r; |
148 | ||
149 | assert(u); | |
150 | assert(u->state_file); | |
151 | ||
37c1d5e9 | 152 | r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0, MKDIR_WARN_MODE); |
20263082 | 153 | if (r < 0) |
019c8ea2 | 154 | return log_error_errno(r, "Failed to create /run/systemd/users/: %m"); |
20263082 | 155 | |
019c8ea2 LP |
156 | _cleanup_(unlink_and_freep) char *temp_path = NULL; |
157 | _cleanup_fclose_ FILE *f = NULL; | |
158 | r = fopen_tmpfile_linkable(u->state_file, O_WRONLY|O_CLOEXEC, &temp_path, &f); | |
14c3baca | 159 | if (r < 0) |
019c8ea2 | 160 | return log_error_errno(r, "Failed to create state file '%s': %m", u->state_file); |
14c3baca | 161 | |
019c8ea2 LP |
162 | if (fchmod(fileno(f), 0644) < 0) |
163 | return log_error_errno(errno, "Failed to set access mode for state file '%s' to 0644: %m", u->state_file); | |
20263082 LP |
164 | |
165 | fprintf(f, | |
14c3baca | 166 | "# This is private data. Do not parse.\n" |
20263082 | 167 | "NAME=%s\n" |
d865bc02 | 168 | "STATE=%s\n" /* friendly user-facing state */ |
d2a4c379 LP |
169 | "STOPPING=%s\n" /* low-level state */ |
170 | "GC_MODE=%s\n", | |
22c902fa | 171 | u->user_record->user_name, |
d865bc02 | 172 | user_state_to_string(user_get_state(u)), |
d2a4c379 LP |
173 | yes_no(u->stopping), |
174 | user_gc_mode_to_string(u->gc_mode)); | |
20263082 | 175 | |
f9e4283d | 176 | /* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */ |
232f4e12 LP |
177 | env_file_fputs_assignment(f, "RUNTIME=", u->runtime_path); |
178 | env_file_fputs_assignment(f, "RUNTIME_DIR_JOB=", u->runtime_dir_job); | |
179 | env_file_fputs_assignment(f, "SERVICE_JOB=", u->service_manager_job); | |
20263082 | 180 | if (u->display) |
b179ae57 | 181 | env_file_fputs_assignment(f, "DISPLAY=", u->display->id); |
9444b1f2 LP |
182 | |
183 | if (dual_timestamp_is_set(&u->timestamp)) | |
20263082 | 184 | fprintf(f, |
90b2de37 ZJS |
185 | "REALTIME="USEC_FMT"\n" |
186 | "MONOTONIC="USEC_FMT"\n", | |
187 | u->timestamp.realtime, | |
188 | u->timestamp.monotonic); | |
20263082 | 189 | |
9afe9efb LP |
190 | if (u->last_session_timestamp != USEC_INFINITY) |
191 | fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n", | |
192 | u->last_session_timestamp); | |
193 | ||
034a2a52 | 194 | if (u->sessions) { |
9b958eff | 195 | bool first; |
034a2a52 | 196 | |
c3c79aeb | 197 | fputs("SESSIONS=", f); |
9b958eff | 198 | first = true; |
034a2a52 | 199 | LIST_FOREACH(sessions_by_user, i, u->sessions) { |
9b958eff LP |
200 | if (first) |
201 | first = false; | |
202 | else | |
0d536673 | 203 | fputc(' ', f); |
9b958eff | 204 | |
0d536673 | 205 | fputs(i->id, f); |
034a2a52 LP |
206 | } |
207 | ||
c3c79aeb | 208 | fputs("\nSEATS=", f); |
9b958eff | 209 | first = true; |
034a2a52 | 210 | LIST_FOREACH(sessions_by_user, i, u->sessions) { |
9b958eff LP |
211 | if (!i->seat) |
212 | continue; | |
213 | ||
214 | if (first) | |
215 | first = false; | |
216 | else | |
0d536673 | 217 | fputc(' ', f); |
9b958eff | 218 | |
0d536673 | 219 | fputs(i->seat->id, f); |
034a2a52 LP |
220 | } |
221 | ||
c3c79aeb | 222 | fputs("\nACTIVE_SESSIONS=", f); |
9b958eff LP |
223 | first = true; |
224 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
225 | if (!session_is_active(i)) | |
226 | continue; | |
227 | ||
228 | if (first) | |
229 | first = false; | |
230 | else | |
0d536673 | 231 | fputc(' ', f); |
9b958eff | 232 | |
0d536673 | 233 | fputs(i->id, f); |
9b958eff | 234 | } |
034a2a52 | 235 | |
c3c79aeb | 236 | fputs("\nONLINE_SESSIONS=", f); |
2dc8f41a CG |
237 | first = true; |
238 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
239 | if (session_get_state(i) == SESSION_CLOSING) | |
240 | continue; | |
241 | ||
242 | if (first) | |
243 | first = false; | |
244 | else | |
0d536673 | 245 | fputc(' ', f); |
2dc8f41a | 246 | |
0d536673 | 247 | fputs(i->id, f); |
2dc8f41a CG |
248 | } |
249 | ||
c3c79aeb | 250 | fputs("\nACTIVE_SEATS=", f); |
9b958eff | 251 | first = true; |
034a2a52 | 252 | LIST_FOREACH(sessions_by_user, i, u->sessions) { |
9b958eff LP |
253 | if (!session_is_active(i) || !i->seat) |
254 | continue; | |
255 | ||
256 | if (first) | |
257 | first = false; | |
258 | else | |
0d536673 | 259 | fputc(' ', f); |
47acb2f1 | 260 | |
0d536673 | 261 | fputs(i->seat->id, f); |
034a2a52 | 262 | } |
2dc8f41a | 263 | |
c3c79aeb | 264 | fputs("\nONLINE_SEATS=", f); |
2dc8f41a CG |
265 | first = true; |
266 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
267 | if (session_get_state(i) == SESSION_CLOSING || !i->seat) | |
268 | continue; | |
269 | ||
270 | if (first) | |
271 | first = false; | |
272 | else | |
0d536673 | 273 | fputc(' ', f); |
2dc8f41a | 274 | |
0d536673 | 275 | fputs(i->seat->id, f); |
2dc8f41a | 276 | } |
c3c79aeb | 277 | fputc('\n', f); |
034a2a52 LP |
278 | } |
279 | ||
019c8ea2 | 280 | r = flink_tmpfile(f, temp_path, u->state_file, LINK_TMPFILE_REPLACE); |
dacd6cee | 281 | if (r < 0) |
019c8ea2 | 282 | return log_error_errno(r, "Failed to move '%s' into place: %m", u->state_file); |
20263082 | 283 | |
019c8ea2 | 284 | temp_path = mfree(temp_path); /* disarm auto-destroy: temporary file does not exist anymore */ |
dacd6cee | 285 | return 0; |
20263082 LP |
286 | } |
287 | ||
71161305 SM |
288 | int user_save(User *u) { |
289 | assert(u); | |
290 | ||
291 | if (!u->started) | |
292 | return 0; | |
293 | ||
8c29a457 | 294 | return user_save_internal(u); |
71161305 SM |
295 | } |
296 | ||
20263082 | 297 | int user_load(User *u) { |
d2a4c379 | 298 | _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL; |
9444b1f2 | 299 | int r; |
20263082 LP |
300 | |
301 | assert(u); | |
302 | ||
aa8fbc74 | 303 | r = parse_env_file(NULL, u->state_file, |
e2a42c0c MY |
304 | "RUNTIME_DIR_JOB", &u->runtime_dir_job, |
305 | "SERVICE_JOB", &u->service_manager_job, | |
9afe9efb LP |
306 | "STOPPING", &stopping, |
307 | "REALTIME", &realtime, | |
308 | "MONOTONIC", &monotonic, | |
d2a4c379 LP |
309 | "LAST_SESSION_TIMESTAMP", &last_session_timestamp, |
310 | "GC_MODE", &gc_mode); | |
1c8280fd LP |
311 | if (r == -ENOENT) |
312 | return 0; | |
313 | if (r < 0) | |
d710aaf7 | 314 | return log_error_errno(r, "Failed to read %s: %m", u->state_file); |
20263082 | 315 | |
d865bc02 LP |
316 | if (stopping) { |
317 | r = parse_boolean(stopping); | |
318 | if (r < 0) | |
319 | log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping); | |
e2a42c0c | 320 | else { |
d865bc02 | 321 | u->stopping = r; |
e2a42c0c MY |
322 | if (u->stopping && !u->runtime_dir_job) |
323 | log_debug("User '%s' is stopping, but no job is being tracked.", u->user_record->user_name); | |
324 | } | |
d865bc02 LP |
325 | } |
326 | ||
b895a735 | 327 | if (realtime) |
d68c645b | 328 | (void) deserialize_usec(realtime, &u->timestamp.realtime); |
b895a735 | 329 | if (monotonic) |
d68c645b | 330 | (void) deserialize_usec(monotonic, &u->timestamp.monotonic); |
9afe9efb | 331 | if (last_session_timestamp) |
d68c645b | 332 | (void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp); |
9444b1f2 | 333 | |
d2a4c379 LP |
334 | u->gc_mode = user_gc_mode_from_string(gc_mode); |
335 | if (u->gc_mode < 0) | |
336 | u->gc_mode = USER_GC_BY_PIN; | |
337 | ||
d865bc02 | 338 | return 0; |
20263082 LP |
339 | } |
340 | ||
e2a42c0c MY |
341 | static int user_start_runtime_dir(User *u) { |
342 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
343 | int r; | |
344 | ||
345 | assert(u); | |
346 | assert(!u->stopping); | |
347 | assert(u->manager); | |
348 | assert(u->runtime_dir_unit); | |
349 | ||
350 | u->runtime_dir_job = mfree(u->runtime_dir_job); | |
351 | ||
352 | r = manager_start_unit(u->manager, u->runtime_dir_unit, &error, &u->runtime_dir_job); | |
353 | if (r < 0) | |
354 | return log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_ERR, | |
355 | r, "Failed to start user service '%s': %s", | |
356 | u->runtime_dir_unit, bus_error_message(&error, r)); | |
357 | ||
358 | return 0; | |
359 | } | |
360 | ||
e4174159 | 361 | static bool user_wants_service_manager(const User *u) { |
5099a50d LP |
362 | assert(u); |
363 | ||
364 | LIST_FOREACH(sessions_by_user, s, u->sessions) | |
365 | if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class)) | |
366 | return true; | |
367 | ||
e4174159 MY |
368 | if (user_check_linger_file(u) > 0) |
369 | return true; | |
370 | ||
5099a50d LP |
371 | return false; |
372 | } | |
373 | ||
e2a42c0c | 374 | int user_start_service_manager(User *u) { |
4afd3348 | 375 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
fb6becb4 LP |
376 | int r; |
377 | ||
20263082 | 378 | assert(u); |
e2a42c0c MY |
379 | assert(!u->stopping); |
380 | assert(u->manager); | |
381 | assert(u->service_manager_unit); | |
20263082 | 382 | |
e2a42c0c MY |
383 | if (u->service_manager_started) |
384 | return 1; | |
25a1ab4e | 385 | |
e2a42c0c MY |
386 | /* Only start user service manager if there's at least one session which wants it */ |
387 | if (!user_wants_service_manager(u)) | |
388 | return 0; | |
5099a50d | 389 | |
e2a42c0c | 390 | u->service_manager_job = mfree(u->service_manager_job); |
5099a50d | 391 | |
e2a42c0c MY |
392 | r = manager_start_unit(u->manager, u->service_manager_unit, &error, &u->service_manager_job); |
393 | if (r < 0) { | |
394 | if (sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED)) | |
395 | return 0; | |
fb6becb4 | 396 | |
e2a42c0c MY |
397 | return log_error_errno(r, "Failed to start user service '%s': %s", |
398 | u->service_manager_unit, bus_error_message(&error, r)); | |
399 | } | |
400 | ||
401 | return (u->service_manager_started = true); | |
20263082 LP |
402 | } |
403 | ||
e8e4b7a0 | 404 | static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { |
99534007 | 405 | _cleanup_(user_record_unrefp) UserRecord *ur = ASSERT_PTR(userdata); |
80c8c786 YW |
406 | const sd_bus_error *e; |
407 | int r; | |
e8e4b7a0 LP |
408 | |
409 | assert(m); | |
e8e4b7a0 | 410 | |
80c8c786 YW |
411 | e = sd_bus_message_get_error(m); |
412 | if (e) { | |
413 | r = sd_bus_error_get_errno(e); | |
414 | log_warning_errno(r, | |
e8e4b7a0 LP |
415 | "Failed to update slice of %s, ignoring: %s", |
416 | ur->user_name, | |
80c8c786 | 417 | bus_error_message(e, r)); |
e8e4b7a0 LP |
418 | |
419 | return 0; | |
420 | } | |
421 | ||
422 | log_debug("Successfully set slice parameters of %s.", ur->user_name); | |
423 | return 0; | |
424 | } | |
425 | ||
426 | static int user_update_slice(User *u) { | |
427 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
428 | int r; | |
429 | ||
430 | assert(u); | |
431 | ||
432 | if (u->user_record->tasks_max == UINT64_MAX && | |
433 | u->user_record->memory_high == UINT64_MAX && | |
434 | u->user_record->memory_max == UINT64_MAX && | |
435 | u->user_record->cpu_weight == UINT64_MAX && | |
436 | u->user_record->io_weight == UINT64_MAX) | |
437 | return 0; | |
438 | ||
b7147168 | 439 | r = bus_message_new_method_call(u->manager->bus, &m, bus_systemd_mgr, "SetUnitProperties"); |
e8e4b7a0 LP |
440 | if (r < 0) |
441 | return bus_log_create_error(r); | |
442 | ||
443 | r = sd_bus_message_append(m, "sb", u->slice, true); | |
444 | if (r < 0) | |
445 | return bus_log_create_error(r); | |
446 | ||
447 | r = sd_bus_message_open_container(m, 'a', "(sv)"); | |
448 | if (r < 0) | |
449 | return bus_log_create_error(r); | |
450 | ||
b0a94df9 ZJS |
451 | const struct { |
452 | const char *name; | |
453 | uint64_t value; | |
454 | } settings[] = { | |
455 | { "TasksMax", u->user_record->tasks_max }, | |
456 | { "MemoryMax", u->user_record->memory_max }, | |
457 | { "MemoryHigh", u->user_record->memory_high }, | |
458 | { "CPUWeight", u->user_record->cpu_weight }, | |
459 | { "IOWeight", u->user_record->io_weight }, | |
460 | }; | |
e8e4b7a0 | 461 | |
85471164 | 462 | FOREACH_ELEMENT(st, settings) { |
d38c0b10 LP |
463 | if (st->value == UINT64_MAX) |
464 | continue; | |
465 | ||
466 | r = sd_bus_message_append(m, "(sv)", st->name, "t", st->value); | |
467 | if (r < 0) | |
468 | return bus_log_create_error(r); | |
469 | } | |
e8e4b7a0 LP |
470 | |
471 | r = sd_bus_message_close_container(m); | |
472 | if (r < 0) | |
473 | return bus_log_create_error(r); | |
474 | ||
475 | r = sd_bus_call_async(u->manager->bus, NULL, m, update_slice_callback, u->user_record, 0); | |
476 | if (r < 0) | |
477 | return log_error_errno(r, "Failed to change user slice properties: %m"); | |
478 | ||
479 | /* Ref the user record pointer, so that the slot keeps it pinned */ | |
480 | user_record_ref(u->user_record); | |
481 | ||
482 | return 0; | |
483 | } | |
484 | ||
513cf7da | 485 | int user_start(User *u) { |
e2a42c0c MY |
486 | int r; |
487 | ||
20263082 LP |
488 | assert(u); |
489 | ||
e2a42c0c MY |
490 | if (u->service_manager_started) { |
491 | /* Everything is up. No action needed. */ | |
492 | assert(u->started && !u->stopping); | |
9418f147 | 493 | return 0; |
e2a42c0c | 494 | } |
9418f147 | 495 | |
e2a42c0c MY |
496 | if (!u->started || u->stopping) { |
497 | /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. | |
498 | * We have to clear that flag before queueing the start-jobs again. If they succeed, the | |
753c896f | 499 | * user object can be reused just fine (pid1 takes care of job-ordering and proper restart), |
e2a42c0c MY |
500 | * but if they fail, we want to force another user_stop() so possibly pending units are |
501 | * stopped. */ | |
502 | u->stopping = false; | |
a832ab6f | 503 | |
e2a42c0c MY |
504 | if (!u->started) |
505 | log_debug("Tracking new user %s.", u->user_record->user_name); | |
506 | ||
507 | /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it | |
508 | * while starting up systemd --user. We need to do user_save_internal() because we have not | |
509 | * "officially" started yet. */ | |
510 | user_save_internal(u); | |
a832ab6f | 511 | |
e2a42c0c MY |
512 | /* Set slice parameters */ |
513 | (void) user_update_slice(u); | |
71161305 | 514 | |
e2a42c0c MY |
515 | (void) user_start_runtime_dir(u); |
516 | } | |
e8e4b7a0 | 517 | |
e2a42c0c MY |
518 | /* Start user@UID.service if needed. */ |
519 | r = user_start_service_manager(u); | |
520 | if (r < 0) | |
521 | return r; | |
20263082 | 522 | |
a832ab6f DH |
523 | if (!u->started) { |
524 | if (!dual_timestamp_is_set(&u->timestamp)) | |
fa5a0251 | 525 | dual_timestamp_now(&u->timestamp); |
e2a42c0c | 526 | |
a832ab6f DH |
527 | user_send_signal(u, true); |
528 | u->started = true; | |
529 | } | |
9418f147 | 530 | |
7f7bb946 LP |
531 | /* Save new user data */ |
532 | user_save(u); | |
533 | ||
20263082 LP |
534 | return 0; |
535 | } | |
536 | ||
1a42ce09 | 537 | static void user_stop_service(User *u, bool force) { |
4afd3348 | 538 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
fb6becb4 | 539 | int r; |
20263082 | 540 | |
20263082 | 541 | assert(u); |
e2a42c0c MY |
542 | assert(u->manager); |
543 | assert(u->runtime_dir_unit); | |
544 | ||
545 | /* Note that we only stop user-runtime-dir@.service here, and let BindsTo= deal with the user@.service | |
546 | * instance. However, we still need to clear service_manager_job here, so that if the stop is | |
547 | * interrupted, the new sessions won't be confused by leftovers. */ | |
20263082 | 548 | |
e2a42c0c MY |
549 | u->service_manager_job = mfree(u->service_manager_job); |
550 | u->service_manager_started = false; | |
20263082 | 551 | |
e2a42c0c | 552 | u->runtime_dir_job = mfree(u->runtime_dir_job); |
20263082 | 553 | |
e2a42c0c | 554 | r = manager_stop_unit(u->manager, u->runtime_dir_unit, force ? "replace" : "fail", &error, &u->runtime_dir_job); |
fb2367ed | 555 | if (r < 0) |
e2a42c0c MY |
556 | log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", |
557 | u->runtime_dir_unit, bus_error_message(&error, r)); | |
fb6becb4 | 558 | } |
20263082 | 559 | |
9bb69af4 | 560 | int user_stop(User *u, bool force) { |
25a1ab4e | 561 | int r = 0; |
03677889 | 562 | |
20263082 LP |
563 | assert(u); |
564 | ||
25a1ab4e LP |
565 | /* This is called whenever we begin with tearing down a user record. It's called in two cases: explicit API |
566 | * request to do so via the bus (in which case 'force' is true) and automatically due to GC, if there's no | |
567 | * session left pinning it (in which case 'force' is false). Note that this just initiates tearing down of the | |
568 | * user, the User object will remain in memory until user_finalize() is called, see below. */ | |
569 | ||
570 | if (!u->started) | |
571 | return 0; | |
572 | ||
573 | if (u->stopping) { /* Stop jobs have already been queued */ | |
b58b227a | 574 | user_save(u); |
25a1ab4e | 575 | return 0; |
b58b227a DH |
576 | } |
577 | ||
f6e4b1e0 MY |
578 | LIST_FOREACH(sessions_by_user, s, u->sessions) |
579 | RET_GATHER(r, session_stop(s, force)); | |
20263082 | 580 | |
1a42ce09 | 581 | user_stop_service(u, force); |
20263082 | 582 | |
5f41d1f1 LP |
583 | u->stopping = true; |
584 | ||
405e0255 LP |
585 | user_save(u); |
586 | ||
587 | return r; | |
588 | } | |
589 | ||
590 | int user_finalize(User *u) { | |
f6e4b1e0 | 591 | int r = 0; |
405e0255 LP |
592 | |
593 | assert(u); | |
594 | ||
25a1ab4e LP |
595 | /* Called when the user is really ready to be freed, i.e. when all unit stop jobs and suchlike for it are |
596 | * done. This is called as a result of an earlier user_done() when all jobs are completed. */ | |
597 | ||
405e0255 | 598 | if (u->started) |
210d4cde | 599 | log_debug("User %s exited.", u->user_record->user_name); |
405e0255 | 600 | |
f6e4b1e0 MY |
601 | LIST_FOREACH(sessions_by_user, s, u->sessions) |
602 | RET_GATHER(r, session_finalize(s)); | |
405e0255 | 603 | |
00d9ef85 LP |
604 | /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs |
605 | * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to | |
606 | * system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such | |
607 | * cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because | |
608 | * a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up, | |
609 | * and do it only for normal users. */ | |
f6e4b1e0 MY |
610 | if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) |
611 | RET_GATHER(r, clean_ipc_by_uid(u->user_record->uid)); | |
66cdd0f2 | 612 | |
75bbdf47 | 613 | (void) unlink(u->state_file); |
d2f92cdf LP |
614 | user_add_to_gc_queue(u); |
615 | ||
405e0255 | 616 | if (u->started) { |
ed18b08b | 617 | user_send_signal(u, false); |
405e0255 LP |
618 | u->started = false; |
619 | } | |
9418f147 | 620 | |
20263082 LP |
621 | return r; |
622 | } | |
623 | ||
a185c5aa | 624 | int user_get_idle_hint(User *u, dual_timestamp *t) { |
a185c5aa | 625 | bool idle_hint = true; |
5cb14b37 | 626 | dual_timestamp ts = DUAL_TIMESTAMP_NULL; |
a185c5aa LP |
627 | |
628 | assert(u); | |
629 | ||
630 | LIST_FOREACH(sessions_by_user, s, u->sessions) { | |
631 | dual_timestamp k; | |
632 | int ih; | |
633 | ||
b4f01bc1 LP |
634 | if (!SESSION_CLASS_CAN_IDLE(s->class)) |
635 | continue; | |
636 | ||
a185c5aa LP |
637 | ih = session_get_idle_hint(s, &k); |
638 | if (ih < 0) | |
639 | return ih; | |
640 | ||
641 | if (!ih) { | |
642 | if (!idle_hint) { | |
643 | if (k.monotonic < ts.monotonic) | |
644 | ts = k; | |
645 | } else { | |
646 | idle_hint = false; | |
647 | ts = k; | |
648 | } | |
649 | } else if (idle_hint) { | |
650 | ||
651 | if (k.monotonic > ts.monotonic) | |
652 | ts = k; | |
653 | } | |
654 | } | |
655 | ||
656 | if (t) | |
657 | *t = ts; | |
658 | ||
659 | return idle_hint; | |
660 | } | |
661 | ||
194dba5e | 662 | int user_check_linger_file(const User *u) { |
cc377381 | 663 | _cleanup_free_ char *cc = NULL; |
194dba5e MY |
664 | const char *p; |
665 | ||
666 | assert(u); | |
667 | assert(u->user_record); | |
e96cd586 | 668 | |
22c902fa | 669 | cc = cescape(u->user_record->user_name); |
cc377381 | 670 | if (!cc) |
e96cd586 LP |
671 | return -ENOMEM; |
672 | ||
63c372cb | 673 | p = strjoina("/var/lib/systemd/linger/", cc); |
6996df9b LP |
674 | if (access(p, F_OK) < 0) { |
675 | if (errno != ENOENT) | |
676 | return -errno; | |
e96cd586 | 677 | |
6996df9b LP |
678 | return false; |
679 | } | |
680 | ||
681 | return true; | |
e96cd586 LP |
682 | } |
683 | ||
4e5b605a | 684 | static bool user_unit_active(User *u) { |
4e5b605a LP |
685 | int r; |
686 | ||
4e5b605a | 687 | assert(u->slice); |
e2a42c0c MY |
688 | assert(u->runtime_dir_unit); |
689 | assert(u->service_manager_unit); | |
4e5b605a | 690 | |
e2a42c0c | 691 | FOREACH_STRING(i, u->slice, u->runtime_dir_unit, u->service_manager_unit) { |
4e5b605a LP |
692 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
693 | ||
694 | r = manager_unit_is_active(u->manager, i, &error); | |
695 | if (r < 0) | |
bbd26200 | 696 | log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", i, bus_error_message(&error, r)); |
4e5b605a LP |
697 | if (r != 0) |
698 | return true; | |
699 | } | |
700 | ||
701 | return false; | |
702 | } | |
703 | ||
d510589f LP |
704 | static usec_t user_get_stop_delay(User *u) { |
705 | assert(u); | |
706 | ||
707 | if (u->user_record->stop_delay_usec != UINT64_MAX) | |
708 | return u->user_record->stop_delay_usec; | |
709 | ||
710 | if (user_record_removable(u->user_record) > 0) | |
711 | return 0; /* For removable users lower the stop delay to zero */ | |
712 | ||
713 | return u->manager->user_stop_delay; | |
714 | } | |
715 | ||
5099a50d LP |
716 | static bool user_pinned_by_sessions(User *u) { |
717 | assert(u); | |
718 | ||
719 | /* Returns true if at least one session exists that shall keep the user tracking alive. That | |
720 | * generally means one session that isn't the service manager still exists. */ | |
721 | ||
d2a4c379 | 722 | switch (u->gc_mode) { |
5099a50d | 723 | |
d2a4c379 LP |
724 | case USER_GC_BY_ANY: |
725 | return u->sessions; | |
726 | ||
727 | case USER_GC_BY_PIN: | |
728 | LIST_FOREACH(sessions_by_user, i, u->sessions) | |
729 | if (SESSION_CLASS_PIN_USER(i->class)) | |
730 | return true; | |
731 | ||
732 | return false; | |
733 | ||
734 | default: | |
735 | assert_not_reached(); | |
736 | } | |
5099a50d LP |
737 | } |
738 | ||
5c093a23 | 739 | bool user_may_gc(User *u, bool drop_not_started) { |
bd26aee1 LP |
740 | int r; |
741 | ||
20263082 LP |
742 | assert(u); |
743 | ||
4a4b033f | 744 | if (drop_not_started && !u->started) |
5c093a23 | 745 | return true; |
932e3ee7 | 746 | |
5099a50d | 747 | if (user_pinned_by_sessions(u)) |
5c093a23 | 748 | return false; |
20263082 | 749 | |
9afe9efb | 750 | if (u->last_session_timestamp != USEC_INFINITY) { |
d510589f LP |
751 | usec_t user_stop_delay; |
752 | ||
9afe9efb LP |
753 | /* All sessions have been closed. Let's see if we shall leave the user record around for a bit */ |
754 | ||
d510589f LP |
755 | user_stop_delay = user_get_stop_delay(u); |
756 | ||
757 | if (user_stop_delay == USEC_INFINITY) | |
9afe9efb | 758 | return false; /* Leave it around forever! */ |
d510589f LP |
759 | if (user_stop_delay > 0 && |
760 | now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, user_stop_delay)) | |
9afe9efb LP |
761 | return false; /* Leave it around for a bit longer. */ |
762 | } | |
763 | ||
4e5b605a LP |
764 | /* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check |
765 | * if any of the three units that we maintain for this user is still around. If none of them is, | |
766 | * there's no need to keep this user around even if lingering is enabled. */ | |
767 | if (user_check_linger_file(u) > 0 && user_unit_active(u)) | |
5c093a23 | 768 | return false; |
20263082 | 769 | |
bd26aee1 | 770 | /* Check if our job is still pending */ |
e2a42c0c MY |
771 | const char *j; |
772 | FOREACH_ARGUMENT(j, u->runtime_dir_job, u->service_manager_job) { | |
bd26aee1 | 773 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
cc377381 | 774 | |
e2a42c0c MY |
775 | if (!j) |
776 | continue; | |
777 | ||
778 | r = manager_job_is_active(u->manager, j, &error); | |
bd26aee1 | 779 | if (r < 0) |
e2a42c0c MY |
780 | log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", |
781 | j, bus_error_message(&error, r)); | |
bd26aee1 LP |
782 | if (r != 0) |
783 | return false; | |
784 | } | |
785 | ||
e2a42c0c MY |
786 | /* Note that we don't care if the three units we manage for each user object are up or not, as we are |
787 | * managing their state rather than tracking it. */ | |
405e0255 | 788 | |
5c093a23 | 789 | return true; |
20263082 LP |
790 | } |
791 | ||
14c3baca LP |
792 | void user_add_to_gc_queue(User *u) { |
793 | assert(u); | |
794 | ||
795 | if (u->in_gc_queue) | |
796 | return; | |
797 | ||
71fda00f | 798 | LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u); |
14c3baca LP |
799 | u->in_gc_queue = true; |
800 | } | |
801 | ||
20263082 | 802 | UserState user_get_state(User *u) { |
20263082 LP |
803 | assert(u); |
804 | ||
5f41d1f1 LP |
805 | if (u->stopping) |
806 | return USER_CLOSING; | |
807 | ||
e2a42c0c | 808 | if (!u->started || u->runtime_dir_job) |
405e0255 | 809 | return USER_OPENING; |
c9caad80 | 810 | |
56f4ab29 MY |
811 | /* USER_GC_BY_PIN: Only pinning sessions count. None -> closing |
812 | * USER_GC_BY_ANY: 'manager' sessions also count. However, if lingering is enabled, 'lingering' state | |
813 | * shall be preferred. 'online' if the manager is manually started by user. */ | |
814 | ||
815 | bool has_pinning = false, all_closing = true; | |
5099a50d | 816 | LIST_FOREACH(sessions_by_user, i, u->sessions) { |
56f4ab29 | 817 | bool pinned = SESSION_CLASS_PIN_USER(i->class); |
5f41d1f1 | 818 | |
56f4ab29 | 819 | if (u->gc_mode == USER_GC_BY_PIN && !pinned) |
5099a50d | 820 | continue; |
00555a2e | 821 | |
56f4ab29 MY |
822 | has_pinning = has_pinning || pinned; |
823 | ||
824 | SessionState state = session_get_state(i); | |
825 | if (state == SESSION_ACTIVE && pinned) | |
5099a50d LP |
826 | return USER_ACTIVE; |
827 | if (state != SESSION_CLOSING) | |
828 | all_closing = false; | |
5f41d1f1 | 829 | } |
e96cd586 | 830 | |
56f4ab29 | 831 | if (!has_pinning && user_check_linger_file(u) > 0 && user_unit_active(u)) |
e96cd586 LP |
832 | return USER_LINGERING; |
833 | ||
56f4ab29 | 834 | return all_closing ? USER_CLOSING : USER_ONLINE; |
20263082 LP |
835 | } |
836 | ||
de07ab16 | 837 | int user_kill(User *u, int signo) { |
de07ab16 LP |
838 | assert(u); |
839 | ||
fb6becb4 | 840 | return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL); |
de07ab16 LP |
841 | } |
842 | ||
cde40acc | 843 | static bool elect_display_filter(Session *s) { |
04857cd8 | 844 | /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */ |
7ffeb45c PW |
845 | assert(s); |
846 | ||
b4f01bc1 | 847 | return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping; |
7ffeb45c PW |
848 | } |
849 | ||
cde40acc | 850 | static int elect_display_compare(Session *s1, Session *s2) { |
7ffeb45c | 851 | /* Indexed by SessionType. Lower numbers mean more preferred. */ |
469df514 | 852 | static const int type_ranks[_SESSION_TYPE_MAX] = { |
7ffeb45c PW |
853 | [SESSION_UNSPECIFIED] = 0, |
854 | [SESSION_TTY] = -2, | |
855 | [SESSION_X11] = -3, | |
856 | [SESSION_WAYLAND] = -3, | |
857 | [SESSION_MIR] = -3, | |
858 | [SESSION_WEB] = -1, | |
859 | }; | |
860 | ||
861 | /* Calculate the partial order relationship between s1 and s2, | |
862 | * returning < 0 if s1 is preferred as the user’s ‘primary session’, | |
863 | * 0 if s1 and s2 are equally preferred or incomparable, or > 0 if s2 | |
864 | * is preferred. | |
865 | * | |
866 | * s1 or s2 may be NULL. */ | |
b9460fdc RC |
867 | if (!s1 && !s2) |
868 | return 0; | |
869 | ||
7ffeb45c PW |
870 | if ((s1 == NULL) != (s2 == NULL)) |
871 | return (s1 == NULL) - (s2 == NULL); | |
872 | ||
873 | if (s1->stopping != s2->stopping) | |
874 | return s1->stopping - s2->stopping; | |
875 | ||
876 | if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER)) | |
877 | return (s1->class != SESSION_USER) - (s2->class != SESSION_USER); | |
878 | ||
59afe07c LP |
879 | if ((s1->class != SESSION_USER_EARLY) != (s2->class != SESSION_USER_EARLY)) |
880 | return (s1->class != SESSION_USER_EARLY) - (s2->class != SESSION_USER_EARLY); | |
881 | ||
7ffeb45c PW |
882 | if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID)) |
883 | return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID); | |
884 | ||
885 | if (s1->type != s2->type) | |
886 | return type_ranks[s1->type] - type_ranks[s2->type]; | |
887 | ||
888 | return 0; | |
889 | } | |
890 | ||
952d3260 | 891 | void user_elect_display(User *u) { |
952d3260 LP |
892 | assert(u); |
893 | ||
04857cd8 LP |
894 | /* This elects a primary session for each user, which we call the "display". We try to keep the assignment |
895 | * stable, but we "upgrade" to better choices. */ | |
22c902fa | 896 | log_debug("Electing new display for user %s", u->user_record->user_name); |
952d3260 LP |
897 | |
898 | LIST_FOREACH(sessions_by_user, s, u->sessions) { | |
7ffeb45c PW |
899 | if (!elect_display_filter(s)) { |
900 | log_debug("Ignoring session %s", s->id); | |
952d3260 | 901 | continue; |
7ffeb45c | 902 | } |
952d3260 | 903 | |
7ffeb45c PW |
904 | if (elect_display_compare(s, u->display) < 0) { |
905 | log_debug("Choosing session %s in preference to %s", s->id, u->display ? u->display->id : "-"); | |
906 | u->display = s; | |
907 | } | |
e9e74f28 | 908 | } |
952d3260 LP |
909 | } |
910 | ||
9afe9efb | 911 | static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) { |
99534007 | 912 | User *u = ASSERT_PTR(userdata); |
9afe9efb | 913 | |
9afe9efb LP |
914 | user_add_to_gc_queue(u); |
915 | ||
916 | return 0; | |
917 | } | |
918 | ||
919 | void user_update_last_session_timer(User *u) { | |
d510589f | 920 | usec_t user_stop_delay; |
9afe9efb LP |
921 | int r; |
922 | ||
923 | assert(u); | |
924 | ||
5099a50d | 925 | if (user_pinned_by_sessions(u)) { |
9afe9efb LP |
926 | /* There are sessions, turn off the timer */ |
927 | u->last_session_timestamp = USEC_INFINITY; | |
928 | u->timer_event_source = sd_event_source_unref(u->timer_event_source); | |
929 | return; | |
930 | } | |
931 | ||
932 | if (u->last_session_timestamp != USEC_INFINITY) | |
933 | return; /* Timer already started */ | |
934 | ||
935 | u->last_session_timestamp = now(CLOCK_MONOTONIC); | |
936 | ||
937 | assert(!u->timer_event_source); | |
938 | ||
d510589f | 939 | user_stop_delay = user_get_stop_delay(u); |
0da36375 | 940 | if (!timestamp_is_set(user_stop_delay)) |
9afe9efb LP |
941 | return; |
942 | ||
943 | if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) { | |
944 | log_debug("Not allocating user stop timeout, since we are already exiting."); | |
945 | return; | |
946 | } | |
947 | ||
948 | r = sd_event_add_time(u->manager->event, | |
949 | &u->timer_event_source, | |
950 | CLOCK_MONOTONIC, | |
d510589f | 951 | usec_add(u->last_session_timestamp, user_stop_delay), 0, |
9afe9efb LP |
952 | user_stop_timeout_callback, u); |
953 | if (r < 0) | |
954 | log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m"); | |
955 | ||
5291f26d | 956 | if (DEBUG_LOGGING) |
9afe9efb | 957 | log_debug("Last session of user '%s' logged out, terminating user context in %s.", |
22c902fa | 958 | u->user_record->user_name, |
5291f26d | 959 | FORMAT_TIMESPAN(user_stop_delay, USEC_PER_MSEC)); |
9afe9efb LP |
960 | } |
961 | ||
20263082 | 962 | static const char* const user_state_table[_USER_STATE_MAX] = { |
082dd188 LP |
963 | [USER_OFFLINE] = "offline", |
964 | [USER_OPENING] = "opening", | |
20263082 | 965 | [USER_LINGERING] = "lingering", |
082dd188 LP |
966 | [USER_ONLINE] = "online", |
967 | [USER_ACTIVE] = "active", | |
968 | [USER_CLOSING] = "closing" | |
20263082 LP |
969 | }; |
970 | ||
971 | DEFINE_STRING_TABLE_LOOKUP(user_state, UserState); | |
1c231f56 | 972 | |
d2a4c379 LP |
973 | static const char* const user_gc_mode_table[_USER_GC_MODE_MAX] = { |
974 | [USER_GC_BY_PIN] = "pin", | |
975 | [USER_GC_BY_ANY] = "any", | |
976 | }; | |
977 | ||
978 | DEFINE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode); | |
979 | ||
1c231f56 | 980 | int config_parse_tmpfs_size( |
4ae15892 | 981 | const char *unit, |
1c231f56 LP |
982 | const char *filename, |
983 | unsigned line, | |
984 | const char *section, | |
985 | unsigned section_line, | |
986 | const char *lvalue, | |
987 | int ltype, | |
988 | const char *rvalue, | |
989 | void *data, | |
990 | void *userdata) { | |
991 | ||
99534007 | 992 | uint64_t *sz = ASSERT_PTR(data); |
1c231f56 LP |
993 | int r; |
994 | ||
995 | assert(filename); | |
996 | assert(lvalue); | |
997 | assert(rvalue); | |
1c231f56 | 998 | |
9184ca48 | 999 | /* First, try to parse as percentage */ |
fe845b5e LP |
1000 | r = parse_permyriad(rvalue); |
1001 | if (r > 0) | |
1002 | *sz = physical_memory_scale(r, 10000U); | |
9184ca48 | 1003 | else { |
59f448cf | 1004 | uint64_t k; |
1c231f56 | 1005 | |
9184ca48 LP |
1006 | /* If the passed argument was not a percentage, or out of range, parse as byte size */ |
1007 | ||
59f448cf | 1008 | r = parse_size(rvalue, 1024, &k); |
1e5f4e8b YW |
1009 | if (r >= 0 && (k <= 0 || (uint64_t) (size_t) k != k)) |
1010 | r = -ERANGE; | |
1011 | if (r < 0) { | |
196d41bc | 1012 | log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue); |
1c231f56 LP |
1013 | return 0; |
1014 | } | |
1015 | ||
59f448cf | 1016 | *sz = PAGE_ALIGN((size_t) k); |
1c231f56 LP |
1017 | } |
1018 | ||
1019 | return 0; | |
1020 | } | |
c06eec15 | 1021 | |
28414939 ZJS |
1022 | int config_parse_compat_user_tasks_max( |
1023 | const char *unit, | |
c06eec15 LP |
1024 | const char *filename, |
1025 | unsigned line, | |
1026 | const char *section, | |
1027 | unsigned section_line, | |
1028 | const char *lvalue, | |
1029 | int ltype, | |
1030 | const char *rvalue, | |
1031 | void *data, | |
1032 | void *userdata) { | |
1033 | ||
c06eec15 LP |
1034 | assert(filename); |
1035 | assert(lvalue); | |
1036 | assert(rvalue); | |
c06eec15 | 1037 | |
28414939 ZJS |
1038 | log_syntax(unit, LOG_NOTICE, filename, line, 0, |
1039 | "Support for option %s= has been removed.", | |
1040 | lvalue); | |
948f7ce4 | 1041 | log_info("Hint: try creating /etc/systemd/system/user-.slice.d/50-limits.conf with:\n" |
28414939 ZJS |
1042 | " [Slice]\n" |
1043 | " TasksMax=%s", | |
1044 | rvalue); | |
c06eec15 LP |
1045 | return 0; |
1046 | } |