]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <unistd.h> | |
4 | #include "sd-bus.h" | |
5 | #include "sd-event.h" | |
6 | ||
7 | #include "alloc-util.h" | |
8 | #include "bus-common-errors.h" | |
9 | #include "bus-error.h" | |
10 | #include "bus-locator.h" | |
11 | #include "bus-util.h" | |
12 | #include "clean-ipc.h" | |
13 | #include "env-file.h" | |
14 | #include "errno-util.h" | |
15 | #include "escape.h" | |
16 | #include "fd-util.h" | |
17 | #include "format-util.h" | |
18 | #include "fs-util.h" | |
19 | #include "hashmap.h" | |
20 | #include "limits-util.h" | |
21 | #include "logind-session.h" | |
22 | #include "logind.h" | |
23 | #include "logind-dbus.h" | |
24 | #include "logind-seat.h" | |
25 | #include "logind-user.h" | |
26 | #include "logind-user-dbus.h" | |
27 | #include "mkdir-label.h" | |
28 | #include "parse-util.h" | |
29 | #include "percent-util.h" | |
30 | #include "serialize.h" | |
31 | #include "special.h" | |
32 | #include "stdio-util.h" | |
33 | #include "string-table.h" | |
34 | #include "string-util.h" | |
35 | #include "strv.h" | |
36 | #include "tmpfile-util.h" | |
37 | #include "uid-classification.h" | |
38 | #include "unit-name.h" | |
39 | #include "user-record.h" | |
40 | #include "user-util.h" | |
41 | ||
42 | int user_new(Manager *m, UserRecord *ur, User **ret) { | |
43 | _cleanup_(user_freep) User *u = NULL; | |
44 | char lu[DECIMAL_STR_MAX(uid_t) + 1]; | |
45 | int r; | |
46 | ||
47 | assert(m); | |
48 | assert(ur); | |
49 | assert(ret); | |
50 | ||
51 | if (!ur->user_name) | |
52 | return -EINVAL; | |
53 | ||
54 | if (!uid_is_valid(ur->uid)) | |
55 | return -EINVAL; | |
56 | ||
57 | u = new(User, 1); | |
58 | if (!u) | |
59 | return -ENOMEM; | |
60 | ||
61 | *u = (User) { | |
62 | .manager = m, | |
63 | .user_record = user_record_ref(ur), | |
64 | .last_session_timestamp = USEC_INFINITY, | |
65 | .gc_mode = USER_GC_BY_ANY, | |
66 | }; | |
67 | ||
68 | if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0) | |
69 | return -ENOMEM; | |
70 | ||
71 | if (asprintf(&u->runtime_path, "/run/user/" UID_FMT, ur->uid) < 0) | |
72 | return -ENOMEM; | |
73 | ||
74 | xsprintf(lu, UID_FMT, ur->uid); | |
75 | r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice); | |
76 | if (r < 0) | |
77 | return r; | |
78 | ||
79 | r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_unit); | |
80 | if (r < 0) | |
81 | return r; | |
82 | ||
83 | r = unit_name_build("user", lu, ".service", &u->service_manager_unit); | |
84 | if (r < 0) | |
85 | return r; | |
86 | ||
87 | r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u); | |
88 | if (r < 0) | |
89 | return r; | |
90 | ||
91 | r = hashmap_put(m->user_units, u->slice, u); | |
92 | if (r < 0) | |
93 | return r; | |
94 | ||
95 | r = hashmap_put(m->user_units, u->runtime_dir_unit, u); | |
96 | if (r < 0) | |
97 | return r; | |
98 | ||
99 | r = hashmap_put(m->user_units, u->service_manager_unit, u); | |
100 | if (r < 0) | |
101 | return r; | |
102 | ||
103 | *ret = TAKE_PTR(u); | |
104 | return 0; | |
105 | } | |
106 | ||
107 | User *user_free(User *u) { | |
108 | if (!u) | |
109 | return NULL; | |
110 | ||
111 | if (u->in_gc_queue) | |
112 | LIST_REMOVE(gc_queue, u->manager->user_gc_queue, u); | |
113 | ||
114 | while (u->sessions) | |
115 | session_free(u->sessions); | |
116 | ||
117 | sd_event_source_unref(u->timer_event_source); | |
118 | ||
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 | } | |
124 | ||
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) { | |
132 | (void) hashmap_remove_value(u->manager->user_units, u->slice, u); | |
133 | free(u->slice); | |
134 | } | |
135 | ||
136 | (void) hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u); | |
137 | ||
138 | free(u->runtime_path); | |
139 | free(u->state_file); | |
140 | ||
141 | user_record_unref(u->user_record); | |
142 | ||
143 | return mfree(u); | |
144 | } | |
145 | ||
146 | static int user_save_internal(User *u) { | |
147 | int r; | |
148 | ||
149 | assert(u); | |
150 | assert(u->state_file); | |
151 | ||
152 | r = mkdir_safe_label("/run/systemd/users", 0755, 0, 0, MKDIR_WARN_MODE); | |
153 | if (r < 0) | |
154 | return log_error_errno(r, "Failed to create /run/systemd/users/: %m"); | |
155 | ||
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); | |
159 | if (r < 0) | |
160 | return log_error_errno(r, "Failed to create state file '%s': %m", u->state_file); | |
161 | ||
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); | |
164 | ||
165 | fprintf(f, | |
166 | "# This is private data. Do not parse.\n" | |
167 | "NAME=%s\n" | |
168 | "STATE=%s\n" /* friendly user-facing state */ | |
169 | "STOPPING=%s\n" /* low-level state */ | |
170 | "GC_MODE=%s\n", | |
171 | u->user_record->user_name, | |
172 | user_state_to_string(user_get_state(u)), | |
173 | yes_no(u->stopping), | |
174 | user_gc_mode_to_string(u->gc_mode)); | |
175 | ||
176 | /* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */ | |
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); | |
180 | if (u->display) | |
181 | env_file_fputs_assignment(f, "DISPLAY=", u->display->id); | |
182 | ||
183 | if (dual_timestamp_is_set(&u->timestamp)) | |
184 | fprintf(f, | |
185 | "REALTIME="USEC_FMT"\n" | |
186 | "MONOTONIC="USEC_FMT"\n", | |
187 | u->timestamp.realtime, | |
188 | u->timestamp.monotonic); | |
189 | ||
190 | if (u->last_session_timestamp != USEC_INFINITY) | |
191 | fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n", | |
192 | u->last_session_timestamp); | |
193 | ||
194 | if (u->sessions) { | |
195 | bool first; | |
196 | ||
197 | fputs("SESSIONS=", f); | |
198 | first = true; | |
199 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
200 | if (first) | |
201 | first = false; | |
202 | else | |
203 | fputc(' ', f); | |
204 | ||
205 | fputs(i->id, f); | |
206 | } | |
207 | ||
208 | fputs("\nSEATS=", f); | |
209 | first = true; | |
210 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
211 | if (!i->seat) | |
212 | continue; | |
213 | ||
214 | if (first) | |
215 | first = false; | |
216 | else | |
217 | fputc(' ', f); | |
218 | ||
219 | fputs(i->seat->id, f); | |
220 | } | |
221 | ||
222 | fputs("\nACTIVE_SESSIONS=", f); | |
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 | |
231 | fputc(' ', f); | |
232 | ||
233 | fputs(i->id, f); | |
234 | } | |
235 | ||
236 | fputs("\nONLINE_SESSIONS=", f); | |
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 | |
245 | fputc(' ', f); | |
246 | ||
247 | fputs(i->id, f); | |
248 | } | |
249 | ||
250 | fputs("\nACTIVE_SEATS=", f); | |
251 | first = true; | |
252 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
253 | if (!session_is_active(i) || !i->seat) | |
254 | continue; | |
255 | ||
256 | if (first) | |
257 | first = false; | |
258 | else | |
259 | fputc(' ', f); | |
260 | ||
261 | fputs(i->seat->id, f); | |
262 | } | |
263 | ||
264 | fputs("\nONLINE_SEATS=", f); | |
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 | |
273 | fputc(' ', f); | |
274 | ||
275 | fputs(i->seat->id, f); | |
276 | } | |
277 | fputc('\n', f); | |
278 | } | |
279 | ||
280 | r = flink_tmpfile(f, temp_path, u->state_file, LINK_TMPFILE_REPLACE); | |
281 | if (r < 0) | |
282 | return log_error_errno(r, "Failed to move '%s' into place: %m", u->state_file); | |
283 | ||
284 | temp_path = mfree(temp_path); /* disarm auto-destroy: temporary file does not exist anymore */ | |
285 | return 0; | |
286 | } | |
287 | ||
288 | int user_save(User *u) { | |
289 | assert(u); | |
290 | ||
291 | if (!u->started) | |
292 | return 0; | |
293 | ||
294 | return user_save_internal(u); | |
295 | } | |
296 | ||
297 | int user_load(User *u) { | |
298 | _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL; | |
299 | int r; | |
300 | ||
301 | assert(u); | |
302 | ||
303 | r = parse_env_file(NULL, u->state_file, | |
304 | "RUNTIME_DIR_JOB", &u->runtime_dir_job, | |
305 | "SERVICE_JOB", &u->service_manager_job, | |
306 | "STOPPING", &stopping, | |
307 | "REALTIME", &realtime, | |
308 | "MONOTONIC", &monotonic, | |
309 | "LAST_SESSION_TIMESTAMP", &last_session_timestamp, | |
310 | "GC_MODE", &gc_mode); | |
311 | if (r == -ENOENT) | |
312 | return 0; | |
313 | if (r < 0) | |
314 | return log_error_errno(r, "Failed to read %s: %m", u->state_file); | |
315 | ||
316 | if (stopping) { | |
317 | r = parse_boolean(stopping); | |
318 | if (r < 0) | |
319 | log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping); | |
320 | else { | |
321 | u->stopping = r; | |
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 | } | |
325 | } | |
326 | ||
327 | if (realtime) | |
328 | (void) deserialize_usec(realtime, &u->timestamp.realtime); | |
329 | if (monotonic) | |
330 | (void) deserialize_usec(monotonic, &u->timestamp.monotonic); | |
331 | if (last_session_timestamp) | |
332 | (void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp); | |
333 | ||
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 | ||
338 | return 0; | |
339 | } | |
340 | ||
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 | ||
361 | static bool user_wants_service_manager(const User *u) { | |
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 | ||
368 | if (user_check_linger_file(u) > 0) | |
369 | return true; | |
370 | ||
371 | return false; | |
372 | } | |
373 | ||
374 | int user_start_service_manager(User *u) { | |
375 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
376 | int r; | |
377 | ||
378 | assert(u); | |
379 | assert(!u->stopping); | |
380 | assert(u->manager); | |
381 | assert(u->service_manager_unit); | |
382 | ||
383 | if (u->service_manager_started) | |
384 | return 1; | |
385 | ||
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; | |
389 | ||
390 | u->service_manager_job = mfree(u->service_manager_job); | |
391 | ||
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; | |
396 | ||
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); | |
402 | } | |
403 | ||
404 | static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { | |
405 | _cleanup_(user_record_unrefp) UserRecord *ur = ASSERT_PTR(userdata); | |
406 | const sd_bus_error *e; | |
407 | int r; | |
408 | ||
409 | assert(m); | |
410 | ||
411 | e = sd_bus_message_get_error(m); | |
412 | if (e) { | |
413 | r = sd_bus_error_get_errno(e); | |
414 | log_warning_errno(r, | |
415 | "Failed to update slice of %s, ignoring: %s", | |
416 | ur->user_name, | |
417 | bus_error_message(e, r)); | |
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 | ||
439 | r = bus_message_new_method_call(u->manager->bus, &m, bus_systemd_mgr, "SetUnitProperties"); | |
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 | ||
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 | }; | |
461 | ||
462 | FOREACH_ELEMENT(st, settings) { | |
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 | } | |
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 | ||
485 | int user_start(User *u) { | |
486 | int r; | |
487 | ||
488 | assert(u); | |
489 | ||
490 | if (u->service_manager_started) { | |
491 | /* Everything is up. No action needed. */ | |
492 | assert(u->started && !u->stopping); | |
493 | return 0; | |
494 | } | |
495 | ||
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 | |
499 | * user object can be reused just fine (pid1 takes care of job-ordering and proper restart), | |
500 | * but if they fail, we want to force another user_stop() so possibly pending units are | |
501 | * stopped. */ | |
502 | u->stopping = false; | |
503 | ||
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); | |
511 | ||
512 | /* Set slice parameters */ | |
513 | (void) user_update_slice(u); | |
514 | ||
515 | (void) user_start_runtime_dir(u); | |
516 | } | |
517 | ||
518 | /* Start user@UID.service if needed. */ | |
519 | r = user_start_service_manager(u); | |
520 | if (r < 0) | |
521 | return r; | |
522 | ||
523 | if (!u->started) { | |
524 | if (!dual_timestamp_is_set(&u->timestamp)) | |
525 | dual_timestamp_now(&u->timestamp); | |
526 | ||
527 | user_send_signal(u, true); | |
528 | u->started = true; | |
529 | } | |
530 | ||
531 | /* Save new user data */ | |
532 | user_save(u); | |
533 | ||
534 | return 0; | |
535 | } | |
536 | ||
537 | static void user_stop_service(User *u, bool force) { | |
538 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
539 | int r; | |
540 | ||
541 | assert(u); | |
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. */ | |
548 | ||
549 | u->service_manager_job = mfree(u->service_manager_job); | |
550 | u->service_manager_started = false; | |
551 | ||
552 | u->runtime_dir_job = mfree(u->runtime_dir_job); | |
553 | ||
554 | r = manager_stop_unit(u->manager, u->runtime_dir_unit, force ? "replace" : "fail", &error, &u->runtime_dir_job); | |
555 | if (r < 0) | |
556 | log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", | |
557 | u->runtime_dir_unit, bus_error_message(&error, r)); | |
558 | } | |
559 | ||
560 | int user_stop(User *u, bool force) { | |
561 | int r = 0; | |
562 | ||
563 | assert(u); | |
564 | ||
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 */ | |
574 | user_save(u); | |
575 | return 0; | |
576 | } | |
577 | ||
578 | LIST_FOREACH(sessions_by_user, s, u->sessions) | |
579 | RET_GATHER(r, session_stop(s, force)); | |
580 | ||
581 | user_stop_service(u, force); | |
582 | ||
583 | u->stopping = true; | |
584 | ||
585 | user_save(u); | |
586 | ||
587 | return r; | |
588 | } | |
589 | ||
590 | int user_finalize(User *u) { | |
591 | int r = 0; | |
592 | ||
593 | assert(u); | |
594 | ||
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 | ||
598 | if (u->started) | |
599 | log_debug("User %s exited.", u->user_record->user_name); | |
600 | ||
601 | LIST_FOREACH(sessions_by_user, s, u->sessions) | |
602 | RET_GATHER(r, session_finalize(s)); | |
603 | ||
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. */ | |
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)); | |
612 | ||
613 | (void) unlink(u->state_file); | |
614 | user_add_to_gc_queue(u); | |
615 | ||
616 | if (u->started) { | |
617 | user_send_signal(u, false); | |
618 | u->started = false; | |
619 | } | |
620 | ||
621 | return r; | |
622 | } | |
623 | ||
624 | int user_get_idle_hint(User *u, dual_timestamp *t) { | |
625 | bool idle_hint = true; | |
626 | dual_timestamp ts = DUAL_TIMESTAMP_NULL; | |
627 | ||
628 | assert(u); | |
629 | ||
630 | LIST_FOREACH(sessions_by_user, s, u->sessions) { | |
631 | dual_timestamp k; | |
632 | int ih; | |
633 | ||
634 | if (!SESSION_CLASS_CAN_IDLE(s->class)) | |
635 | continue; | |
636 | ||
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 | ||
662 | int user_check_linger_file(const User *u) { | |
663 | _cleanup_free_ char *cc = NULL; | |
664 | const char *p; | |
665 | ||
666 | assert(u); | |
667 | assert(u->user_record); | |
668 | ||
669 | cc = cescape(u->user_record->user_name); | |
670 | if (!cc) | |
671 | return -ENOMEM; | |
672 | ||
673 | p = strjoina("/var/lib/systemd/linger/", cc); | |
674 | if (access(p, F_OK) < 0) { | |
675 | if (errno != ENOENT) | |
676 | return -errno; | |
677 | ||
678 | return false; | |
679 | } | |
680 | ||
681 | return true; | |
682 | } | |
683 | ||
684 | static bool user_unit_active(User *u) { | |
685 | int r; | |
686 | ||
687 | assert(u->slice); | |
688 | assert(u->runtime_dir_unit); | |
689 | assert(u->service_manager_unit); | |
690 | ||
691 | FOREACH_STRING(i, u->slice, u->runtime_dir_unit, u->service_manager_unit) { | |
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) | |
696 | log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", i, bus_error_message(&error, r)); | |
697 | if (r != 0) | |
698 | return true; | |
699 | } | |
700 | ||
701 | return false; | |
702 | } | |
703 | ||
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 | ||
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 | ||
722 | switch (u->gc_mode) { | |
723 | ||
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 | } | |
737 | } | |
738 | ||
739 | bool user_may_gc(User *u, bool drop_not_started) { | |
740 | int r; | |
741 | ||
742 | assert(u); | |
743 | ||
744 | if (drop_not_started && !u->started) | |
745 | return true; | |
746 | ||
747 | if (user_pinned_by_sessions(u)) | |
748 | return false; | |
749 | ||
750 | if (u->last_session_timestamp != USEC_INFINITY) { | |
751 | usec_t user_stop_delay; | |
752 | ||
753 | /* All sessions have been closed. Let's see if we shall leave the user record around for a bit */ | |
754 | ||
755 | user_stop_delay = user_get_stop_delay(u); | |
756 | ||
757 | if (user_stop_delay == USEC_INFINITY) | |
758 | return false; /* Leave it around forever! */ | |
759 | if (user_stop_delay > 0 && | |
760 | now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, user_stop_delay)) | |
761 | return false; /* Leave it around for a bit longer. */ | |
762 | } | |
763 | ||
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)) | |
768 | return false; | |
769 | ||
770 | /* Check if our job is still pending */ | |
771 | const char *j; | |
772 | FOREACH_ARGUMENT(j, u->runtime_dir_job, u->service_manager_job) { | |
773 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
774 | ||
775 | if (!j) | |
776 | continue; | |
777 | ||
778 | r = manager_job_is_active(u->manager, j, &error); | |
779 | if (r < 0) | |
780 | log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", | |
781 | j, bus_error_message(&error, r)); | |
782 | if (r != 0) | |
783 | return false; | |
784 | } | |
785 | ||
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. */ | |
788 | ||
789 | return true; | |
790 | } | |
791 | ||
792 | void user_add_to_gc_queue(User *u) { | |
793 | assert(u); | |
794 | ||
795 | if (u->in_gc_queue) | |
796 | return; | |
797 | ||
798 | LIST_PREPEND(gc_queue, u->manager->user_gc_queue, u); | |
799 | u->in_gc_queue = true; | |
800 | } | |
801 | ||
802 | UserState user_get_state(User *u) { | |
803 | assert(u); | |
804 | ||
805 | if (u->stopping) | |
806 | return USER_CLOSING; | |
807 | ||
808 | if (!u->started || u->runtime_dir_job) | |
809 | return USER_OPENING; | |
810 | ||
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; | |
816 | LIST_FOREACH(sessions_by_user, i, u->sessions) { | |
817 | bool pinned = SESSION_CLASS_PIN_USER(i->class); | |
818 | ||
819 | if (u->gc_mode == USER_GC_BY_PIN && !pinned) | |
820 | continue; | |
821 | ||
822 | has_pinning = has_pinning || pinned; | |
823 | ||
824 | SessionState state = session_get_state(i); | |
825 | if (state == SESSION_ACTIVE && pinned) | |
826 | return USER_ACTIVE; | |
827 | if (state != SESSION_CLOSING) | |
828 | all_closing = false; | |
829 | } | |
830 | ||
831 | if (!has_pinning && user_check_linger_file(u) > 0 && user_unit_active(u)) | |
832 | return USER_LINGERING; | |
833 | ||
834 | return all_closing ? USER_CLOSING : USER_ONLINE; | |
835 | } | |
836 | ||
837 | int user_kill(User *u, int signo) { | |
838 | assert(u); | |
839 | ||
840 | return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL); | |
841 | } | |
842 | ||
843 | static bool elect_display_filter(Session *s) { | |
844 | /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */ | |
845 | assert(s); | |
846 | ||
847 | return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping; | |
848 | } | |
849 | ||
850 | static int elect_display_compare(Session *s1, Session *s2) { | |
851 | /* Indexed by SessionType. Lower numbers mean more preferred. */ | |
852 | static const int type_ranks[_SESSION_TYPE_MAX] = { | |
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. */ | |
867 | if (!s1 && !s2) | |
868 | return 0; | |
869 | ||
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 | ||
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 | ||
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 | ||
891 | void user_elect_display(User *u) { | |
892 | assert(u); | |
893 | ||
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. */ | |
896 | log_debug("Electing new display for user %s", u->user_record->user_name); | |
897 | ||
898 | LIST_FOREACH(sessions_by_user, s, u->sessions) { | |
899 | if (!elect_display_filter(s)) { | |
900 | log_debug("Ignoring session %s", s->id); | |
901 | continue; | |
902 | } | |
903 | ||
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 | } | |
908 | } | |
909 | } | |
910 | ||
911 | static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) { | |
912 | User *u = ASSERT_PTR(userdata); | |
913 | ||
914 | user_add_to_gc_queue(u); | |
915 | ||
916 | return 0; | |
917 | } | |
918 | ||
919 | void user_update_last_session_timer(User *u) { | |
920 | usec_t user_stop_delay; | |
921 | int r; | |
922 | ||
923 | assert(u); | |
924 | ||
925 | if (user_pinned_by_sessions(u)) { | |
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 | ||
939 | user_stop_delay = user_get_stop_delay(u); | |
940 | if (!timestamp_is_set(user_stop_delay)) | |
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, | |
951 | usec_add(u->last_session_timestamp, user_stop_delay), 0, | |
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 | ||
956 | if (DEBUG_LOGGING) | |
957 | log_debug("Last session of user '%s' logged out, terminating user context in %s.", | |
958 | u->user_record->user_name, | |
959 | FORMAT_TIMESPAN(user_stop_delay, USEC_PER_MSEC)); | |
960 | } | |
961 | ||
962 | static const char* const user_state_table[_USER_STATE_MAX] = { | |
963 | [USER_OFFLINE] = "offline", | |
964 | [USER_OPENING] = "opening", | |
965 | [USER_LINGERING] = "lingering", | |
966 | [USER_ONLINE] = "online", | |
967 | [USER_ACTIVE] = "active", | |
968 | [USER_CLOSING] = "closing" | |
969 | }; | |
970 | ||
971 | DEFINE_STRING_TABLE_LOOKUP(user_state, UserState); | |
972 | ||
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 | ||
980 | int config_parse_tmpfs_size( | |
981 | const char *unit, | |
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 | ||
992 | uint64_t *sz = ASSERT_PTR(data); | |
993 | int r; | |
994 | ||
995 | assert(filename); | |
996 | assert(lvalue); | |
997 | assert(rvalue); | |
998 | ||
999 | /* First, try to parse as percentage */ | |
1000 | r = parse_permyriad(rvalue); | |
1001 | if (r > 0) | |
1002 | *sz = physical_memory_scale(r, 10000U); | |
1003 | else { | |
1004 | uint64_t k; | |
1005 | ||
1006 | /* If the passed argument was not a percentage, or out of range, parse as byte size */ | |
1007 | ||
1008 | r = parse_size(rvalue, 1024, &k); | |
1009 | if (r >= 0 && (k <= 0 || (uint64_t) (size_t) k != k)) | |
1010 | r = -ERANGE; | |
1011 | if (r < 0) { | |
1012 | log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue); | |
1013 | return 0; | |
1014 | } | |
1015 | ||
1016 | *sz = PAGE_ALIGN((size_t) k); | |
1017 | } | |
1018 | ||
1019 | return 0; | |
1020 | } | |
1021 | ||
1022 | int config_parse_compat_user_tasks_max( | |
1023 | const char *unit, | |
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 | ||
1034 | assert(filename); | |
1035 | assert(lvalue); | |
1036 | assert(rvalue); | |
1037 | ||
1038 | log_syntax(unit, LOG_NOTICE, filename, line, 0, | |
1039 | "Support for option %s= has been removed.", | |
1040 | lvalue); | |
1041 | log_info("Hint: try creating /etc/systemd/system/user-.slice.d/50-limits.conf with:\n" | |
1042 | " [Slice]\n" | |
1043 | " TasksMax=%s", | |
1044 | rvalue); | |
1045 | return 0; | |
1046 | } |