]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <fcntl.h> | |
4 | #include <linux/kd.h> | |
5 | #include <linux/vt.h> | |
6 | #include <signal.h> | |
7 | #include <sys/ioctl.h> | |
8 | #include <sys/stat.h> | |
9 | #include <unistd.h> | |
10 | ||
11 | #include "sd-bus.h" | |
12 | #include "sd-event.h" | |
13 | #include "sd-messages.h" | |
14 | #include "sd-varlink.h" | |
15 | ||
16 | #include "alloc-util.h" | |
17 | #include "audit-util.h" | |
18 | #include "bus-error.h" | |
19 | #include "bus-util.h" | |
20 | #include "daemon-util.h" | |
21 | #include "devnum-util.h" | |
22 | #include "env-file.h" | |
23 | #include "errno-util.h" | |
24 | #include "extract-word.h" | |
25 | #include "fd-util.h" | |
26 | #include "format-util.h" | |
27 | #include "fs-util.h" | |
28 | #include "hashmap.h" | |
29 | #include "login-util.h" | |
30 | #include "logind.h" | |
31 | #include "logind-dbus.h" | |
32 | #include "logind-seat.h" | |
33 | #include "logind-seat-dbus.h" | |
34 | #include "logind-session.h" | |
35 | #include "logind-session-dbus.h" | |
36 | #include "logind-session-device.h" | |
37 | #include "logind-user.h" | |
38 | #include "logind-user-dbus.h" | |
39 | #include "logind-varlink.h" | |
40 | #include "mkdir-label.h" | |
41 | #include "parse-util.h" | |
42 | #include "path-util.h" | |
43 | #include "process-util.h" | |
44 | #include "serialize.h" | |
45 | #include "string-table.h" | |
46 | #include "terminal-util.h" | |
47 | #include "tmpfile-util.h" | |
48 | #include "user-record.h" | |
49 | #include "user-util.h" | |
50 | ||
51 | #define RELEASE_USEC (20*USEC_PER_SEC) | |
52 | ||
53 | static void session_restore_vt(Session *s); | |
54 | ||
55 | int session_new(Manager *m, const char *id, Session **ret) { | |
56 | _cleanup_(session_freep) Session *s = NULL; | |
57 | int r; | |
58 | ||
59 | assert(m); | |
60 | assert(id); | |
61 | assert(ret); | |
62 | ||
63 | if (!session_id_valid(id)) | |
64 | return -EINVAL; | |
65 | ||
66 | s = new(Session, 1); | |
67 | if (!s) | |
68 | return -ENOMEM; | |
69 | ||
70 | *s = (Session) { | |
71 | .manager = m, | |
72 | .id = strdup(id), | |
73 | .state_file = path_join("/run/systemd/sessions/", id), | |
74 | .vtfd = -EBADF, | |
75 | .audit_id = AUDIT_SESSION_INVALID, | |
76 | .tty_validity = _TTY_VALIDITY_INVALID, | |
77 | .leader = PIDREF_NULL, | |
78 | }; | |
79 | if (!s->id || !s->state_file) | |
80 | return -ENOMEM; | |
81 | ||
82 | s->devices = hashmap_new(&devt_hash_ops); | |
83 | if (!s->devices) | |
84 | return -ENOMEM; | |
85 | ||
86 | r = hashmap_put(m->sessions, s->id, s); | |
87 | if (r < 0) | |
88 | return r; | |
89 | ||
90 | *ret = TAKE_PTR(s); | |
91 | return 0; | |
92 | } | |
93 | ||
94 | static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) { | |
95 | Session *s = ASSERT_PTR(userdata); | |
96 | ||
97 | assert(s->leader.fd == fd); | |
98 | ||
99 | s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source); | |
100 | ||
101 | session_stop(s, /* force= */ false); | |
102 | ||
103 | session_add_to_gc_queue(s); | |
104 | ||
105 | return 1; | |
106 | } | |
107 | ||
108 | static int session_watch_pidfd(Session *s) { | |
109 | int r; | |
110 | ||
111 | assert(s); | |
112 | assert(s->manager); | |
113 | assert(pidref_is_set(&s->leader)); | |
114 | assert(s->leader.fd >= 0); | |
115 | assert(!s->leader_pidfd_event_source); | |
116 | ||
117 | r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s); | |
118 | if (r < 0) | |
119 | return r; | |
120 | ||
121 | r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT); | |
122 | if (r < 0) | |
123 | return r; | |
124 | ||
125 | (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd"); | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
130 | static void session_reset_leader(Session *s, bool keep_fdstore) { | |
131 | assert(s); | |
132 | ||
133 | if (!keep_fdstore) { | |
134 | /* Clear fdstore if we're asked to, no matter if s->leader is set or not, so that when | |
135 | * initially deserializing leader fd we clear the old fd too. */ | |
136 | (void) notify_remove_fd_warnf("session-%s-leader-fd", s->id); | |
137 | s->leader_fd_saved = false; | |
138 | } | |
139 | ||
140 | if (!pidref_is_set(&s->leader)) | |
141 | return; | |
142 | ||
143 | s->leader_pidfd_event_source = sd_event_source_disable_unref(s->leader_pidfd_event_source); | |
144 | ||
145 | (void) hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s); | |
146 | ||
147 | return pidref_done(&s->leader); | |
148 | } | |
149 | ||
150 | Session* session_free(Session *s) { | |
151 | SessionDevice *sd; | |
152 | ||
153 | if (!s) | |
154 | return NULL; | |
155 | ||
156 | sd_event_source_unref(s->stop_on_idle_event_source); | |
157 | ||
158 | if (s->in_gc_queue) { | |
159 | assert(s->manager); | |
160 | LIST_REMOVE(gc_queue, s->manager->session_gc_queue, s); | |
161 | } | |
162 | ||
163 | sd_event_source_unref(s->timer_event_source); | |
164 | ||
165 | session_drop_controller(s); | |
166 | ||
167 | while ((sd = hashmap_first(s->devices))) | |
168 | session_device_free(sd); | |
169 | ||
170 | hashmap_free(s->devices); | |
171 | ||
172 | if (s->user) { | |
173 | LIST_REMOVE(sessions_by_user, s->user->sessions, s); | |
174 | ||
175 | if (s->user->display == s) | |
176 | s->user->display = NULL; | |
177 | ||
178 | user_update_last_session_timer(s->user); | |
179 | } | |
180 | ||
181 | if (s->seat) { | |
182 | if (s->seat->active == s) | |
183 | s->seat->active = NULL; | |
184 | if (s->seat->pending_switch == s) | |
185 | s->seat->pending_switch = NULL; | |
186 | ||
187 | seat_evict_position(s->seat, s); | |
188 | LIST_REMOVE(sessions_by_seat, s->seat->sessions, s); | |
189 | } | |
190 | ||
191 | if (s->scope) { | |
192 | hashmap_remove(s->manager->session_units, s->scope); | |
193 | free(s->scope); | |
194 | } | |
195 | ||
196 | free(s->scope_job); | |
197 | ||
198 | session_reset_leader(s, /* keep_fdstore = */ true); | |
199 | ||
200 | sd_bus_message_unref(s->create_message); | |
201 | sd_bus_message_unref(s->upgrade_message); | |
202 | ||
203 | sd_varlink_unref(s->create_link); | |
204 | ||
205 | free(s->tty); | |
206 | free(s->display); | |
207 | free(s->remote_host); | |
208 | free(s->remote_user); | |
209 | free(s->service); | |
210 | free(s->desktop); | |
211 | ||
212 | hashmap_remove(s->manager->sessions, s->id); | |
213 | ||
214 | /* Note that we don't remove the state file here, since it's supposed to survive daemon restarts */ | |
215 | free(s->state_file); | |
216 | free(s->id); | |
217 | ||
218 | return mfree(s); | |
219 | } | |
220 | ||
221 | void session_set_user(Session *s, User *u) { | |
222 | assert(s); | |
223 | assert(!s->user); | |
224 | ||
225 | s->user = u; | |
226 | LIST_PREPEND(sessions_by_user, u->sessions, s); | |
227 | ||
228 | user_update_last_session_timer(u); | |
229 | } | |
230 | ||
231 | int session_set_leader_consume(Session *s, PidRef _leader) { | |
232 | _cleanup_(pidref_done) PidRef pidref = _leader; | |
233 | int r; | |
234 | ||
235 | assert(s); | |
236 | assert(pidref_is_set(&pidref)); | |
237 | assert(pidref.fd >= 0); | |
238 | ||
239 | if (pidref_equal(&s->leader, &pidref)) | |
240 | return 0; | |
241 | ||
242 | session_reset_leader(s, /* keep_fdstore = */ false); | |
243 | ||
244 | s->leader = TAKE_PIDREF(pidref); | |
245 | ||
246 | r = session_watch_pidfd(s); | |
247 | if (r < 0) | |
248 | return log_error_errno(r, "Failed to watch leader pidfd for session '%s': %m", s->id); | |
249 | ||
250 | r = hashmap_ensure_put(&s->manager->sessions_by_leader, &pidref_hash_ops, &s->leader, s); | |
251 | if (r < 0) | |
252 | return r; | |
253 | assert(r > 0); | |
254 | ||
255 | if (s->leader.fd >= 0) { | |
256 | r = notify_push_fdf(s->leader.fd, "session-%s-leader-fd", s->id); | |
257 | if (r < 0) | |
258 | log_warning_errno(r, "Failed to push leader pidfd for session '%s', ignoring: %m", s->id); | |
259 | else | |
260 | s->leader_fd_saved = true; | |
261 | } | |
262 | ||
263 | (void) audit_session_from_pid(&s->leader, &s->audit_id); | |
264 | ||
265 | return 1; | |
266 | } | |
267 | ||
268 | static void session_save_devices(Session *s, FILE *f) { | |
269 | SessionDevice *sd; | |
270 | ||
271 | if (!hashmap_isempty(s->devices)) { | |
272 | fprintf(f, "DEVICES="); | |
273 | HASHMAP_FOREACH(sd, s->devices) | |
274 | fprintf(f, DEVNUM_FORMAT_STR " ", DEVNUM_FORMAT_VAL(sd->dev)); | |
275 | fprintf(f, "\n"); | |
276 | } | |
277 | } | |
278 | ||
279 | int session_save(Session *s) { | |
280 | int r; | |
281 | ||
282 | assert(s); | |
283 | ||
284 | if (!s->user) | |
285 | return -ESTALE; | |
286 | ||
287 | if (!s->started) | |
288 | return 0; | |
289 | ||
290 | r = mkdir_safe_label("/run/systemd/sessions", 0755, 0, 0, MKDIR_WARN_MODE); | |
291 | if (r < 0) | |
292 | return log_error_errno(r, "Failed to create /run/systemd/sessions/: %m"); | |
293 | ||
294 | _cleanup_(unlink_and_freep) char *temp_path = NULL; | |
295 | _cleanup_fclose_ FILE *f = NULL; | |
296 | r = fopen_tmpfile_linkable(s->state_file, O_WRONLY|O_CLOEXEC, &temp_path, &f); | |
297 | if (r < 0) | |
298 | return log_error_errno(r, "Failed to create state file '%s': %m", s->state_file); | |
299 | ||
300 | if (fchmod(fileno(f), 0644) < 0) | |
301 | return log_error_errno(errno, "Failed to set access mode for state file '%s' to 0644: %m", s->state_file); | |
302 | ||
303 | fprintf(f, | |
304 | "# This is private data. Do not parse.\n" | |
305 | "UID="UID_FMT"\n" | |
306 | "ACTIVE=%s\n" | |
307 | "IS_DISPLAY=%s\n" | |
308 | "STATE=%s\n" | |
309 | "REMOTE=%s\n" | |
310 | "LEADER_FD_SAVED=%s\n", | |
311 | s->user->user_record->uid, | |
312 | one_zero(session_is_active(s)), | |
313 | one_zero(s->user->display == s), | |
314 | session_state_to_string(session_get_state(s)), | |
315 | one_zero(s->remote), | |
316 | one_zero(s->leader_fd_saved)); | |
317 | ||
318 | env_file_fputs_assignment(f, "USER=", s->user->user_record->user_name); | |
319 | ||
320 | if (s->type >= 0) | |
321 | fprintf(f, "TYPE=%s\n", session_type_to_string(s->type)); | |
322 | ||
323 | if (s->original_type >= 0) | |
324 | fprintf(f, "ORIGINAL_TYPE=%s\n", session_type_to_string(s->original_type)); | |
325 | ||
326 | if (s->class >= 0) | |
327 | fprintf(f, "CLASS=%s\n", session_class_to_string(s->class)); | |
328 | ||
329 | env_file_fputs_assignment(f, "SCOPE=", s->scope); | |
330 | env_file_fputs_assignment(f, "SCOPE_JOB=", s->scope_job); | |
331 | if (s->seat) | |
332 | env_file_fputs_assignment(f, "SEAT=", s->seat->id); | |
333 | env_file_fputs_assignment(f, "TTY=", s->tty); | |
334 | ||
335 | if (s->tty_validity >= 0) | |
336 | fprintf(f, "TTY_VALIDITY=%s\n", tty_validity_to_string(s->tty_validity)); | |
337 | ||
338 | env_file_fputs_assignment(f, "DISPLAY=", s->display); | |
339 | env_file_fputs_assignment(f, "REMOTE_HOST=", s->remote_host); | |
340 | env_file_fputs_assignment(f, "REMOTE_USER=", s->remote_user); | |
341 | env_file_fputs_assignment(f, "SERVICE=", s->service); | |
342 | env_file_fputs_assignment(f, "DESKTOP=", s->desktop); | |
343 | ||
344 | if (s->seat) { | |
345 | if (!seat_has_vts(s->seat)) | |
346 | fprintf(f, "POSITION=%u\n", s->position); | |
347 | else if (s->vtnr > 0) | |
348 | fprintf(f, "VTNR=%u\n", s->vtnr); | |
349 | } | |
350 | ||
351 | if (pidref_is_set(&s->leader)) { | |
352 | fprintf(f, "LEADER="PID_FMT"\n", s->leader.pid); | |
353 | (void) pidref_acquire_pidfd_id(&s->leader); | |
354 | if (s->leader.fd_id != 0) | |
355 | fprintf(f, "LEADER_PIDFDID=%" PRIu64 "\n", s->leader.fd_id); | |
356 | } | |
357 | ||
358 | if (audit_session_is_valid(s->audit_id)) | |
359 | fprintf(f, "AUDIT=%"PRIu32"\n", s->audit_id); | |
360 | ||
361 | if (dual_timestamp_is_set(&s->timestamp)) | |
362 | fprintf(f, | |
363 | "REALTIME="USEC_FMT"\n" | |
364 | "MONOTONIC="USEC_FMT"\n", | |
365 | s->timestamp.realtime, | |
366 | s->timestamp.monotonic); | |
367 | ||
368 | if (s->controller) { | |
369 | env_file_fputs_assignment(f, "CONTROLLER=", s->controller); | |
370 | session_save_devices(s, f); | |
371 | } | |
372 | ||
373 | r = flink_tmpfile(f, temp_path, s->state_file, LINK_TMPFILE_REPLACE); | |
374 | if (r < 0) | |
375 | return log_error_errno(r, "Failed to move '%s' into place: %m", s->state_file); | |
376 | ||
377 | temp_path = mfree(temp_path); /* disarm auto-destroy: temporary file does not exist anymore */ | |
378 | return 0; | |
379 | } | |
380 | ||
381 | static int session_load_devices(Session *s, const char *devices) { | |
382 | int r = 0; | |
383 | ||
384 | assert(s); | |
385 | ||
386 | for (const char *p = devices;;) { | |
387 | _cleanup_free_ char *word = NULL; | |
388 | dev_t dev; | |
389 | int k; | |
390 | ||
391 | k = extract_first_word(&p, &word, NULL, 0); | |
392 | if (k <= 0) { | |
393 | RET_GATHER(r, k); | |
394 | break; | |
395 | } | |
396 | ||
397 | k = parse_devnum(word, &dev); | |
398 | if (k < 0) { | |
399 | RET_GATHER(r, k); | |
400 | continue; | |
401 | } | |
402 | ||
403 | /* The file descriptors for loaded devices will be reattached later. */ | |
404 | RET_GATHER(r, session_device_new(s, dev, /* open_device = */ false, /* ret = */ NULL)); | |
405 | } | |
406 | ||
407 | if (r < 0) | |
408 | log_error_errno(r, "Failed to load some session devices for session '%s': %m", s->id); | |
409 | return r; | |
410 | } | |
411 | ||
412 | int session_load(Session *s) { | |
413 | _cleanup_free_ char *remote = NULL, | |
414 | *seat = NULL, | |
415 | *tty_validity = NULL, | |
416 | *vtnr = NULL, | |
417 | *state = NULL, | |
418 | *position = NULL, | |
419 | *leader_pid = NULL, | |
420 | *leader_fd_saved = NULL, | |
421 | *type = NULL, | |
422 | *original_type = NULL, | |
423 | *class = NULL, | |
424 | *uid = NULL, | |
425 | *realtime = NULL, | |
426 | *monotonic = NULL, | |
427 | *controller = NULL, | |
428 | *active = NULL, | |
429 | *devices = NULL, | |
430 | *is_display = NULL, | |
431 | *fifo_path = NULL; /* compat only, not used */ | |
432 | ||
433 | int k, r; | |
434 | ||
435 | assert(s); | |
436 | ||
437 | r = parse_env_file(NULL, s->state_file, | |
438 | "REMOTE", &remote, | |
439 | "SCOPE", &s->scope, | |
440 | "SCOPE_JOB", &s->scope_job, | |
441 | "FIFO", &fifo_path, | |
442 | "SEAT", &seat, | |
443 | "TTY", &s->tty, | |
444 | "TTY_VALIDITY", &tty_validity, | |
445 | "DISPLAY", &s->display, | |
446 | "REMOTE_HOST", &s->remote_host, | |
447 | "REMOTE_USER", &s->remote_user, | |
448 | "SERVICE", &s->service, | |
449 | "DESKTOP", &s->desktop, | |
450 | "VTNR", &vtnr, | |
451 | "STATE", &state, | |
452 | "POSITION", &position, | |
453 | "LEADER", &leader_pid, | |
454 | "LEADER_FD_SAVED", &leader_fd_saved, | |
455 | "TYPE", &type, | |
456 | "ORIGINAL_TYPE", &original_type, | |
457 | "CLASS", &class, | |
458 | "UID", &uid, | |
459 | "REALTIME", &realtime, | |
460 | "MONOTONIC", &monotonic, | |
461 | "CONTROLLER", &controller, | |
462 | "ACTIVE", &active, | |
463 | "DEVICES", &devices, | |
464 | "IS_DISPLAY", &is_display); | |
465 | if (r < 0) | |
466 | return log_error_errno(r, "Failed to read %s: %m", s->state_file); | |
467 | ||
468 | if (!s->user) { | |
469 | uid_t u; | |
470 | User *user; | |
471 | ||
472 | if (!uid) | |
473 | return log_error_errno(SYNTHETIC_ERRNO(ENOENT), | |
474 | "UID not specified for session %s", | |
475 | s->id); | |
476 | ||
477 | r = parse_uid(uid, &u); | |
478 | if (r < 0) { | |
479 | log_error("Failed to parse UID value %s for session %s.", uid, s->id); | |
480 | return r; | |
481 | } | |
482 | ||
483 | user = hashmap_get(s->manager->users, UID_TO_PTR(u)); | |
484 | if (!user) | |
485 | return log_error_errno(SYNTHETIC_ERRNO(ENOENT), | |
486 | "User of session %s not known.", | |
487 | s->id); | |
488 | ||
489 | session_set_user(s, user); | |
490 | } | |
491 | ||
492 | if (remote) { | |
493 | k = parse_boolean(remote); | |
494 | if (k >= 0) | |
495 | s->remote = k; | |
496 | } | |
497 | ||
498 | if (vtnr) | |
499 | (void) safe_atou(vtnr, &s->vtnr); | |
500 | ||
501 | if (seat && !s->seat) { | |
502 | Seat *o; | |
503 | ||
504 | o = hashmap_get(s->manager->seats, seat); | |
505 | if (o) | |
506 | r = seat_attach_session(o, s); | |
507 | if (!o || r < 0) | |
508 | log_error("Cannot attach session %s to seat %s", s->id, seat); | |
509 | } | |
510 | ||
511 | if (!s->seat || !seat_has_vts(s->seat)) | |
512 | s->vtnr = 0; | |
513 | ||
514 | if (position && s->seat) { | |
515 | unsigned npos; | |
516 | ||
517 | (void) safe_atou(position, &npos); | |
518 | seat_claim_position(s->seat, s, npos); | |
519 | } | |
520 | ||
521 | if (tty_validity) { | |
522 | TTYValidity v; | |
523 | ||
524 | v = tty_validity_from_string(tty_validity); | |
525 | if (v < 0) | |
526 | log_debug("Failed to parse TTY validity: %s", tty_validity); | |
527 | else | |
528 | s->tty_validity = v; | |
529 | } | |
530 | ||
531 | if (type) { | |
532 | SessionType t; | |
533 | ||
534 | t = session_type_from_string(type); | |
535 | if (t >= 0) | |
536 | s->type = t; | |
537 | } | |
538 | ||
539 | if (original_type) { | |
540 | SessionType ot; | |
541 | ||
542 | ot = session_type_from_string(original_type); | |
543 | if (ot >= 0) | |
544 | s->original_type = ot; | |
545 | } else | |
546 | /* Pre-v246 compat: initialize original_type if not set in the state file */ | |
547 | s->original_type = s->type; | |
548 | ||
549 | if (class) { | |
550 | SessionClass c; | |
551 | ||
552 | c = session_class_from_string(class); | |
553 | if (c >= 0) | |
554 | s->class = c; | |
555 | } | |
556 | ||
557 | if (streq_ptr(state, "closing")) | |
558 | s->stopping = true; | |
559 | ||
560 | /* logind before v258 used a fifo for session close notification. Since v258 we fully employ | |
561 | * pidfd for the job, hence just unlink the legacy fifo. */ | |
562 | if (fifo_path) | |
563 | (void) unlink(fifo_path); | |
564 | ||
565 | if (realtime) | |
566 | (void) deserialize_usec(realtime, &s->timestamp.realtime); | |
567 | if (monotonic) | |
568 | (void) deserialize_usec(monotonic, &s->timestamp.monotonic); | |
569 | ||
570 | if (active) { | |
571 | k = parse_boolean(active); | |
572 | if (k >= 0) | |
573 | s->was_active = k; | |
574 | } | |
575 | ||
576 | if (is_display) { | |
577 | /* Note that when enumerating users are loaded before sessions, hence the display session to use is | |
578 | * something we have to store along with the session and not the user, as in that case we couldn't | |
579 | * apply it at the time we load the user. */ | |
580 | ||
581 | k = parse_boolean(is_display); | |
582 | if (k < 0) | |
583 | log_warning_errno(k, "Failed to parse IS_DISPLAY session property: %m"); | |
584 | else if (k > 0) | |
585 | s->user->display = s; | |
586 | } | |
587 | ||
588 | if (controller) { | |
589 | if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0) { | |
590 | session_set_controller(s, controller, false, false); | |
591 | session_load_devices(s, devices); | |
592 | } else | |
593 | session_restore_vt(s); | |
594 | } | |
595 | ||
596 | if (leader_pid) { | |
597 | assert(!pidref_is_set(&s->leader)); | |
598 | ||
599 | r = parse_pid(leader_pid, &s->deserialized_pid); | |
600 | if (r < 0) | |
601 | return log_error_errno(r, "Failed to parse LEADER=%s: %m", leader_pid); | |
602 | ||
603 | if (leader_fd_saved) { | |
604 | r = parse_boolean(leader_fd_saved); | |
605 | if (r < 0) | |
606 | return log_error_errno(r, "Failed to parse LEADER_FD_SAVED=%s: %m", leader_fd_saved); | |
607 | s->leader_fd_saved = r > 0; | |
608 | ||
609 | if (s->leader_fd_saved) | |
610 | /* The leader fd will be acquired from fdstore later */ | |
611 | return 0; | |
612 | } | |
613 | ||
614 | _cleanup_(pidref_done) PidRef p = PIDREF_NULL; | |
615 | ||
616 | r = pidref_set_pid(&p, s->deserialized_pid); | |
617 | if (r < 0) | |
618 | return log_error_errno(r, "Failed to deserialize leader PID for session '%s': %m", s->id); | |
619 | if (p.fd < 0) | |
620 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
621 | "Failed to acquire pidfd for session leader '" PID_FMT "', refusing.", | |
622 | p.pid); | |
623 | ||
624 | r = session_set_leader_consume(s, TAKE_PIDREF(p)); | |
625 | if (r < 0) | |
626 | return log_error_errno(r, "Failed to set leader PID for session '%s': %m", s->id); | |
627 | } | |
628 | ||
629 | return 0; | |
630 | } | |
631 | ||
632 | int session_activate(Session *s) { | |
633 | unsigned num_pending; | |
634 | ||
635 | assert(s); | |
636 | assert(s->user); | |
637 | ||
638 | if (!s->seat) | |
639 | return -EOPNOTSUPP; | |
640 | ||
641 | if (s->seat->active == s) | |
642 | return 0; | |
643 | ||
644 | /* on seats with VTs, we let VTs manage session-switching */ | |
645 | if (seat_has_vts(s->seat)) { | |
646 | if (s->vtnr == 0) | |
647 | return -EOPNOTSUPP; | |
648 | ||
649 | return chvt(s->vtnr); | |
650 | } | |
651 | ||
652 | /* On seats without VTs, we implement session-switching in logind. We | |
653 | * try to pause all session-devices and wait until the session | |
654 | * controller acknowledged them. Once all devices are asleep, we simply | |
655 | * switch the active session and be done. | |
656 | * We save the session we want to switch to in seat->pending_switch and | |
657 | * seat_complete_switch() will perform the final switch. */ | |
658 | ||
659 | s->seat->pending_switch = s; | |
660 | ||
661 | /* if no devices are running, immediately perform the session switch */ | |
662 | num_pending = session_device_try_pause_all(s); | |
663 | if (!num_pending) | |
664 | seat_complete_switch(s->seat); | |
665 | ||
666 | return 0; | |
667 | } | |
668 | ||
669 | static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_error *error) { | |
670 | _cleanup_free_ char *scope = NULL; | |
671 | const char *description; | |
672 | int r; | |
673 | ||
674 | assert(s); | |
675 | assert(s->user); | |
676 | ||
677 | if (!SESSION_CLASS_WANTS_SCOPE(s->class)) | |
678 | return 0; | |
679 | ||
680 | if (s->scope) | |
681 | goto finish; | |
682 | ||
683 | s->scope_job = mfree(s->scope_job); | |
684 | ||
685 | scope = strjoin("session-", s->id, ".scope"); | |
686 | if (!scope) | |
687 | return log_oom(); | |
688 | ||
689 | description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name); | |
690 | ||
691 | r = manager_start_scope( | |
692 | s->manager, | |
693 | scope, | |
694 | &s->leader, | |
695 | /* allow_pidfd = */ true, | |
696 | s->user->slice, | |
697 | description, | |
698 | /* These should have been pulled in explicitly in user_start(). Just to be sure. */ | |
699 | /* requires = */ STRV_MAKE_CONST(s->user->runtime_dir_unit), | |
700 | /* wants = */ STRV_MAKE_CONST(SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) ? s->user->service_manager_unit : NULL), | |
701 | /* We usually want to order session scopes after systemd-user-sessions.service | |
702 | * since the unit is used as login session barrier for unprivileged users. However | |
703 | * the barrier doesn't apply for root as sysadmin should always be able to log in | |
704 | * (and without waiting for any timeout to expire) in case something goes wrong | |
705 | * during the boot process. */ | |
706 | /* extra_after = */ STRV_MAKE_CONST("systemd-logind.service", | |
707 | SESSION_CLASS_IS_EARLY(s->class) ? NULL : "systemd-user-sessions.service"), | |
708 | user_record_home_directory(s->user->user_record), | |
709 | properties, | |
710 | error, | |
711 | &s->scope_job); | |
712 | if (r < 0) | |
713 | return log_error_errno(r, "Failed to start session scope %s: %s", | |
714 | scope, bus_error_message(error, r)); | |
715 | ||
716 | s->scope = TAKE_PTR(scope); | |
717 | ||
718 | finish: | |
719 | (void) hashmap_put(s->manager->session_units, s->scope, s); | |
720 | return 0; | |
721 | } | |
722 | ||
723 | static int session_dispatch_stop_on_idle(sd_event_source *source, uint64_t t, void *userdata) { | |
724 | Session *s = userdata; | |
725 | dual_timestamp ts; | |
726 | int r, idle; | |
727 | ||
728 | assert(s); | |
729 | ||
730 | if (s->stopping) | |
731 | return 0; | |
732 | ||
733 | idle = session_get_idle_hint(s, &ts); | |
734 | if (idle) { | |
735 | log_info("Session \"%s\" of user \"%s\" is idle, stopping.", s->id, s->user->user_record->user_name); | |
736 | ||
737 | return session_stop(s, /* force */ true); | |
738 | } | |
739 | ||
740 | r = sd_event_source_set_time( | |
741 | source, | |
742 | usec_add(dual_timestamp_is_set(&ts) ? ts.monotonic : now(CLOCK_MONOTONIC), | |
743 | s->manager->stop_idle_session_usec)); | |
744 | if (r < 0) | |
745 | return log_error_errno(r, "Failed to configure stop on idle session event source: %m"); | |
746 | ||
747 | r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); | |
748 | if (r < 0) | |
749 | return log_error_errno(r, "Failed to enable stop on idle session event source: %m"); | |
750 | ||
751 | return 1; | |
752 | } | |
753 | ||
754 | static int session_setup_stop_on_idle_timer(Session *s) { | |
755 | int r; | |
756 | ||
757 | assert(s); | |
758 | ||
759 | if (s->manager->stop_idle_session_usec == USEC_INFINITY || !SESSION_CLASS_CAN_STOP_ON_IDLE(s->class)) | |
760 | return 0; | |
761 | ||
762 | r = sd_event_add_time_relative( | |
763 | s->manager->event, | |
764 | &s->stop_on_idle_event_source, | |
765 | CLOCK_MONOTONIC, | |
766 | s->manager->stop_idle_session_usec, | |
767 | 0, | |
768 | session_dispatch_stop_on_idle, s); | |
769 | if (r < 0) | |
770 | return log_error_errno(r, "Failed to add stop on idle session event source: %m"); | |
771 | ||
772 | return 0; | |
773 | } | |
774 | ||
775 | int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) { | |
776 | int r; | |
777 | ||
778 | assert(s); | |
779 | ||
780 | if (!s->user) | |
781 | return -ESTALE; | |
782 | ||
783 | if (s->stopping) | |
784 | return -EINVAL; | |
785 | ||
786 | if (s->started) | |
787 | return 0; | |
788 | ||
789 | r = user_start(s->user); | |
790 | if (r < 0) | |
791 | return r; | |
792 | ||
793 | r = session_start_scope(s, properties, error); | |
794 | if (r < 0) | |
795 | return r; | |
796 | ||
797 | r = session_setup_stop_on_idle_timer(s); | |
798 | if (r < 0) | |
799 | return r; | |
800 | ||
801 | log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO, | |
802 | LOG_MESSAGE_ID(SD_MESSAGE_SESSION_START_STR), | |
803 | LOG_ITEM("SESSION_ID=%s", s->id), | |
804 | LOG_ITEM("USER_ID=%s", s->user->user_record->user_name), | |
805 | LOG_ITEM("LEADER="PID_FMT, s->leader.pid), | |
806 | LOG_ITEM("CLASS=%s", session_class_to_string(s->class)), | |
807 | LOG_ITEM("TYPE=%s", session_type_to_string(s->type)), | |
808 | LOG_MESSAGE("New session '%s' of user '%s' with class '%s' and type '%s'.", | |
809 | s->id, | |
810 | s->user->user_record->user_name, | |
811 | session_class_to_string(s->class), | |
812 | session_type_to_string(s->type))); | |
813 | ||
814 | if (!dual_timestamp_is_set(&s->timestamp)) | |
815 | dual_timestamp_now(&s->timestamp); | |
816 | ||
817 | if (s->seat) | |
818 | seat_read_active_vt(s->seat); | |
819 | ||
820 | s->started = true; | |
821 | ||
822 | user_elect_display(s->user); | |
823 | ||
824 | /* Save data */ | |
825 | (void) session_save(s); | |
826 | (void) user_save(s->user); | |
827 | if (s->seat) | |
828 | (void) seat_save(s->seat); | |
829 | ||
830 | /* Send signals */ | |
831 | session_send_signal(s, true); | |
832 | user_send_changed(s->user, "Display"); | |
833 | ||
834 | if (s->seat && s->seat->active == s) | |
835 | (void) seat_send_changed(s->seat, "ActiveSession"); | |
836 | ||
837 | return 0; | |
838 | } | |
839 | ||
840 | static int session_stop_scope(Session *s, bool force) { | |
841 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
842 | int r; | |
843 | ||
844 | assert(s); | |
845 | ||
846 | if (!s->scope) | |
847 | return 0; | |
848 | ||
849 | /* Let's always abandon the scope first. This tells systemd that we are not interested anymore, and everything | |
850 | * that is left in the scope is "left-over". Informing systemd about this has the benefit that it will log | |
851 | * when killing any processes left after this point. */ | |
852 | r = manager_abandon_scope(s->manager, s->scope, &error); | |
853 | if (r < 0) { | |
854 | log_warning_errno(r, "Failed to abandon session scope, ignoring: %s", bus_error_message(&error, r)); | |
855 | sd_bus_error_free(&error); | |
856 | } | |
857 | ||
858 | s->scope_job = mfree(s->scope_job); | |
859 | ||
860 | /* Optionally, let's kill everything that's left now. */ | |
861 | if (force || | |
862 | (s->user->user_record->kill_processes != 0 && | |
863 | (s->user->user_record->kill_processes > 0 || | |
864 | manager_shall_kill(s->manager, s->user->user_record->user_name)))) { | |
865 | ||
866 | r = manager_stop_unit(s->manager, s->scope, force ? "replace" : "fail", &error, &s->scope_job); | |
867 | if (r < 0) { | |
868 | if (force) | |
869 | return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r)); | |
870 | ||
871 | log_warning_errno(r, "Failed to stop session scope, ignoring: %s", bus_error_message(&error, r)); | |
872 | } | |
873 | } else { | |
874 | ||
875 | /* With no killing, this session is allowed to persist in "closing" state indefinitely. | |
876 | * Therefore session stop and session removal may be two distinct events. | |
877 | * Session stop is quite significant on its own, let's log it. */ | |
878 | log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO, | |
879 | LOG_ITEM("SESSION_ID=%s", s->id), | |
880 | LOG_ITEM("USER_ID=%s", s->user->user_record->user_name), | |
881 | LOG_ITEM("LEADER="PID_FMT, s->leader.pid), | |
882 | LOG_MESSAGE("Session %s logged out. Waiting for processes to exit.", s->id)); | |
883 | } | |
884 | ||
885 | return 0; | |
886 | } | |
887 | ||
888 | int session_stop(Session *s, bool force) { | |
889 | int r; | |
890 | ||
891 | assert(s); | |
892 | ||
893 | /* This is called whenever we begin with tearing down a session record. It's called in four cases: explicit API | |
894 | * request via the bus (either directly for the session object or for the seat or user object this session | |
895 | * belongs to; 'force' is true), or due to automatic GC (i.e. scope vanished; 'force' is false), or because the | |
896 | * session FIFO saw an EOF ('force' is false), or because the release timer hit ('force' is false). */ | |
897 | ||
898 | if (!s->user) | |
899 | return -ESTALE; | |
900 | if (!s->started) | |
901 | return 0; | |
902 | if (s->stopping) | |
903 | return 0; | |
904 | ||
905 | s->timer_event_source = sd_event_source_unref(s->timer_event_source); | |
906 | ||
907 | if (s->seat) | |
908 | seat_evict_position(s->seat, s); | |
909 | ||
910 | /* Kill cgroup */ | |
911 | r = session_stop_scope(s, force); | |
912 | ||
913 | s->stopping = true; | |
914 | ||
915 | user_elect_display(s->user); | |
916 | ||
917 | (void) session_save(s); | |
918 | (void) user_save(s->user); | |
919 | ||
920 | return r; | |
921 | } | |
922 | ||
923 | int session_finalize(Session *s) { | |
924 | SessionDevice *sd; | |
925 | ||
926 | assert(s); | |
927 | ||
928 | if (!s->user) | |
929 | return -ESTALE; | |
930 | ||
931 | if (s->started) | |
932 | log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO, | |
933 | LOG_MESSAGE_ID(SD_MESSAGE_SESSION_STOP_STR), | |
934 | LOG_ITEM("SESSION_ID=%s", s->id), | |
935 | LOG_ITEM("USER_ID=%s", s->user->user_record->user_name), | |
936 | LOG_ITEM("LEADER="PID_FMT, s->leader.pid), | |
937 | LOG_MESSAGE("Removed session %s.", s->id)); | |
938 | ||
939 | s->timer_event_source = sd_event_source_unref(s->timer_event_source); | |
940 | ||
941 | if (s->seat) | |
942 | seat_evict_position(s->seat, s); | |
943 | ||
944 | /* Kill session devices */ | |
945 | while ((sd = hashmap_first(s->devices))) | |
946 | session_device_free(sd); | |
947 | ||
948 | (void) unlink(s->state_file); | |
949 | session_add_to_gc_queue(s); | |
950 | user_add_to_gc_queue(s->user); | |
951 | ||
952 | if (s->started) { | |
953 | session_send_signal(s, false); | |
954 | s->started = false; | |
955 | } | |
956 | ||
957 | if (s->seat) { | |
958 | if (s->seat->active == s) | |
959 | seat_set_active(s->seat, NULL); | |
960 | ||
961 | seat_save(s->seat); | |
962 | } | |
963 | ||
964 | session_reset_leader(s, /* keep_fdstore = */ false); | |
965 | ||
966 | (void) user_save(s->user); | |
967 | (void) user_send_changed(s->user, "Display"); | |
968 | ||
969 | return 0; | |
970 | } | |
971 | ||
972 | static int release_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) { | |
973 | Session *s = ASSERT_PTR(userdata); | |
974 | ||
975 | assert(es); | |
976 | ||
977 | session_stop(s, /* force = */ false); | |
978 | return 0; | |
979 | } | |
980 | ||
981 | int session_release(Session *s) { | |
982 | assert(s); | |
983 | ||
984 | if (!s->started || s->stopping) | |
985 | return 0; | |
986 | ||
987 | if (s->timer_event_source) | |
988 | return 0; | |
989 | ||
990 | return sd_event_add_time_relative( | |
991 | s->manager->event, | |
992 | &s->timer_event_source, | |
993 | CLOCK_MONOTONIC, | |
994 | RELEASE_USEC, 0, | |
995 | release_timeout_callback, s); | |
996 | } | |
997 | ||
998 | bool session_is_active(Session *s) { | |
999 | assert(s); | |
1000 | ||
1001 | if (!s->seat) | |
1002 | return true; | |
1003 | ||
1004 | return s->seat->active == s; | |
1005 | } | |
1006 | ||
1007 | static int get_tty_atime(const char *tty, usec_t *atime) { | |
1008 | _cleanup_free_ char *p = NULL; | |
1009 | struct stat st; | |
1010 | ||
1011 | assert(tty); | |
1012 | assert(atime); | |
1013 | ||
1014 | if (!path_is_absolute(tty)) { | |
1015 | p = path_join("/dev", tty); | |
1016 | if (!p) | |
1017 | return -ENOMEM; | |
1018 | ||
1019 | tty = p; | |
1020 | } else if (!path_startswith(tty, "/dev/")) | |
1021 | return -ENOENT; | |
1022 | ||
1023 | if (lstat(tty, &st) < 0) | |
1024 | return -errno; | |
1025 | ||
1026 | *atime = timespec_load(&st.st_atim); | |
1027 | return 0; | |
1028 | } | |
1029 | ||
1030 | static int get_process_ctty_atime(pid_t pid, usec_t *atime) { | |
1031 | _cleanup_free_ char *p = NULL; | |
1032 | int r; | |
1033 | ||
1034 | assert(pid > 0); | |
1035 | assert(atime); | |
1036 | ||
1037 | r = get_ctty(pid, NULL, &p); | |
1038 | if (r < 0) | |
1039 | return r; | |
1040 | ||
1041 | return get_tty_atime(p, atime); | |
1042 | } | |
1043 | ||
1044 | int session_get_idle_hint(Session *s, dual_timestamp *t) { | |
1045 | usec_t atime = 0, dtime = 0; | |
1046 | int r; | |
1047 | ||
1048 | assert(s); | |
1049 | ||
1050 | if (!SESSION_CLASS_CAN_IDLE(s->class)) | |
1051 | return false; | |
1052 | ||
1053 | /* Graphical sessions have an explicit idle hint */ | |
1054 | if (SESSION_TYPE_IS_GRAPHICAL(s->type)) { | |
1055 | if (t) | |
1056 | *t = s->idle_hint_timestamp; | |
1057 | ||
1058 | return s->idle_hint; | |
1059 | } | |
1060 | ||
1061 | if (s->type == SESSION_TTY) { | |
1062 | /* For sessions with an explicitly configured tty, let's check its atime */ | |
1063 | if (s->tty) { | |
1064 | r = get_tty_atime(s->tty, &atime); | |
1065 | if (r >= 0) | |
1066 | goto found_atime; | |
1067 | } | |
1068 | ||
1069 | /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of | |
1070 | * the leader */ | |
1071 | if (pidref_is_set(&s->leader)) { | |
1072 | r = get_process_ctty_atime(s->leader.pid, &atime); | |
1073 | if (r >= 0) | |
1074 | goto found_atime; | |
1075 | } | |
1076 | } | |
1077 | ||
1078 | if (t) | |
1079 | *t = DUAL_TIMESTAMP_NULL; | |
1080 | ||
1081 | return false; | |
1082 | ||
1083 | found_atime: | |
1084 | if (t) | |
1085 | dual_timestamp_from_realtime(t, atime); | |
1086 | ||
1087 | if (s->manager->idle_action_usec > 0 && s->manager->stop_idle_session_usec != USEC_INFINITY) | |
1088 | dtime = MIN(s->manager->idle_action_usec, s->manager->stop_idle_session_usec); | |
1089 | else if (s->manager->idle_action_usec > 0) | |
1090 | dtime = s->manager->idle_action_usec; | |
1091 | else if (s->manager->stop_idle_session_usec != USEC_INFINITY) | |
1092 | dtime = s->manager->stop_idle_session_usec; | |
1093 | else | |
1094 | return false; | |
1095 | ||
1096 | return usec_add(atime, dtime) <= now(CLOCK_REALTIME); | |
1097 | } | |
1098 | ||
1099 | int session_set_idle_hint(Session *s, bool b) { | |
1100 | assert(s); | |
1101 | ||
1102 | if (!SESSION_CLASS_CAN_IDLE(s->class)) /* Only some session classes know the idle concept at all */ | |
1103 | return -ENOTTY; | |
1104 | if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) /* And only graphical session types can set the field explicitly */ | |
1105 | return -ENOTTY; | |
1106 | ||
1107 | if (s->idle_hint == b) | |
1108 | return 0; | |
1109 | ||
1110 | s->idle_hint = b; | |
1111 | dual_timestamp_now(&s->idle_hint_timestamp); | |
1112 | ||
1113 | session_send_changed(s, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic"); | |
1114 | ||
1115 | if (s->seat) | |
1116 | seat_send_changed(s->seat, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic"); | |
1117 | ||
1118 | user_send_changed(s->user, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic"); | |
1119 | manager_send_changed(s->manager, "IdleHint", "IdleSinceHint", "IdleSinceHintMonotonic"); | |
1120 | ||
1121 | return 1; | |
1122 | } | |
1123 | ||
1124 | int session_get_locked_hint(Session *s) { | |
1125 | assert(s); | |
1126 | ||
1127 | return s->locked_hint; | |
1128 | } | |
1129 | ||
1130 | int session_set_locked_hint(Session *s, bool b) { | |
1131 | assert(s); | |
1132 | ||
1133 | if (!SESSION_CLASS_CAN_LOCK(s->class)) | |
1134 | return -ENOTTY; | |
1135 | ||
1136 | if (s->locked_hint == b) | |
1137 | return 0; | |
1138 | ||
1139 | s->locked_hint = b; | |
1140 | (void) session_save(s); | |
1141 | (void) session_send_changed(s, "LockedHint"); | |
1142 | ||
1143 | return 1; | |
1144 | } | |
1145 | ||
1146 | void session_set_type(Session *s, SessionType t) { | |
1147 | assert(s); | |
1148 | ||
1149 | if (s->type == t) | |
1150 | return; | |
1151 | ||
1152 | s->type = t; | |
1153 | (void) session_save(s); | |
1154 | (void) session_send_changed(s, "Type"); | |
1155 | } | |
1156 | ||
1157 | void session_set_class(Session *s, SessionClass c) { | |
1158 | assert(s); | |
1159 | ||
1160 | if (s->class == c) | |
1161 | return; | |
1162 | ||
1163 | s->class = c; | |
1164 | (void) session_save(s); | |
1165 | (void) session_send_changed(s, "Class"); | |
1166 | ||
1167 | /* This class change might mean we need the per-user session manager now. Try to start it. */ | |
1168 | (void) user_start_service_manager(s->user); | |
1169 | } | |
1170 | ||
1171 | int session_set_display(Session *s, const char *display) { | |
1172 | int r; | |
1173 | ||
1174 | assert(s); | |
1175 | assert(display); | |
1176 | ||
1177 | r = free_and_strdup(&s->display, display); | |
1178 | if (r <= 0) /* 0 means the strings were equal */ | |
1179 | return r; | |
1180 | ||
1181 | (void) session_save(s); | |
1182 | (void) session_send_changed(s, "Display"); | |
1183 | ||
1184 | return 1; | |
1185 | } | |
1186 | ||
1187 | int session_set_tty(Session *s, const char *tty) { | |
1188 | int r; | |
1189 | ||
1190 | assert(s); | |
1191 | assert(tty); | |
1192 | ||
1193 | r = free_and_strdup(&s->tty, tty); | |
1194 | if (r <= 0) /* 0 means the strings were equal */ | |
1195 | return r; | |
1196 | ||
1197 | (void) session_save(s); | |
1198 | (void) session_send_changed(s, "TTY"); | |
1199 | ||
1200 | return 1; | |
1201 | } | |
1202 | ||
1203 | bool session_may_gc(Session *s, bool drop_not_started) { | |
1204 | int r; | |
1205 | ||
1206 | assert(s); | |
1207 | ||
1208 | if (drop_not_started && !s->started) | |
1209 | return true; | |
1210 | ||
1211 | if (!s->user) | |
1212 | return true; | |
1213 | ||
1214 | r = pidref_is_alive(&s->leader); | |
1215 | if (r == -ESRCH) | |
1216 | /* Session has no leader. This is probably because the leader vanished before deserializing | |
1217 | * pidfd from FD store. */ | |
1218 | return true; | |
1219 | if (r < 0) | |
1220 | log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not: %m", s->leader.pid); | |
1221 | if (r > 0) | |
1222 | return false; | |
1223 | ||
1224 | if (s->scope_job) { | |
1225 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
1226 | ||
1227 | r = manager_job_is_active(s->manager, s->scope_job, &error); | |
1228 | if (r < 0) | |
1229 | log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", s->scope_job, bus_error_message(&error, r)); | |
1230 | if (r != 0) | |
1231 | return false; | |
1232 | } | |
1233 | ||
1234 | if (s->scope) { | |
1235 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
1236 | ||
1237 | r = manager_unit_is_active(s->manager, s->scope, &error); | |
1238 | if (r < 0) | |
1239 | log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", s->scope, bus_error_message(&error, r)); | |
1240 | if (r != 0) | |
1241 | return false; | |
1242 | } | |
1243 | ||
1244 | return true; | |
1245 | } | |
1246 | ||
1247 | void session_add_to_gc_queue(Session *s) { | |
1248 | assert(s); | |
1249 | ||
1250 | if (s->in_gc_queue) | |
1251 | return; | |
1252 | ||
1253 | LIST_PREPEND(gc_queue, s->manager->session_gc_queue, s); | |
1254 | s->in_gc_queue = true; | |
1255 | } | |
1256 | ||
1257 | SessionState session_get_state(Session *s) { | |
1258 | assert(s); | |
1259 | ||
1260 | /* always check closing first */ | |
1261 | if (s->stopping || s->timer_event_source) | |
1262 | return SESSION_CLOSING; | |
1263 | ||
1264 | if (s->scope_job || !pidref_is_set(&s->leader)) | |
1265 | return SESSION_OPENING; | |
1266 | ||
1267 | if (session_is_active(s)) | |
1268 | return SESSION_ACTIVE; | |
1269 | ||
1270 | return SESSION_ONLINE; | |
1271 | } | |
1272 | ||
1273 | int session_kill(Session *s, KillWhom whom, int signo, sd_bus_error *error) { | |
1274 | assert(s); | |
1275 | ||
1276 | switch (whom) { | |
1277 | ||
1278 | case KILL_ALL: | |
1279 | if (!SESSION_CLASS_WANTS_SCOPE(s->class)) | |
1280 | return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, | |
1281 | "Session '%s' has no associated scope", s->id); | |
1282 | ||
1283 | if (!s->scope) | |
1284 | return sd_bus_error_set_errnof(error, ESRCH, "Scope for session '%s' not active", s->id); | |
1285 | ||
1286 | return manager_kill_unit(s->manager, s->scope, KILL_ALL, signo, error); | |
1287 | ||
1288 | case KILL_LEADER: | |
1289 | return pidref_kill(&s->leader, signo); | |
1290 | ||
1291 | default: | |
1292 | assert_not_reached(); | |
1293 | } | |
1294 | } | |
1295 | ||
1296 | static int session_open_vt(Session *s, bool reopen) { | |
1297 | _cleanup_close_ int fd = -EBADF; | |
1298 | char path[sizeof("/dev/tty") + DECIMAL_STR_MAX(s->vtnr)]; | |
1299 | ||
1300 | assert(s); | |
1301 | ||
1302 | if (s->vtnr < 1) | |
1303 | return -ENODEV; | |
1304 | ||
1305 | if (!reopen && s->vtfd >= 0) | |
1306 | return s->vtfd; | |
1307 | ||
1308 | sprintf(path, "/dev/tty%u", s->vtnr); | |
1309 | ||
1310 | fd = open_terminal(path, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY); | |
1311 | if (fd < 0) | |
1312 | return log_error_errno(fd, "Cannot open VT %s of session %s: %m", path, s->id); | |
1313 | ||
1314 | close_and_replace(s->vtfd, fd); | |
1315 | return s->vtfd; | |
1316 | } | |
1317 | ||
1318 | static int session_prepare_vt(Session *s) { | |
1319 | int vt, r; | |
1320 | struct vt_mode mode = {}; | |
1321 | ||
1322 | assert(s); | |
1323 | ||
1324 | if (s->vtnr < 1) | |
1325 | return 0; | |
1326 | ||
1327 | vt = session_open_vt(s, /* reopen = */ false); | |
1328 | if (vt < 0) | |
1329 | return vt; | |
1330 | ||
1331 | r = fchown(vt, s->user->user_record->uid, -1); | |
1332 | if (r < 0) { | |
1333 | r = log_error_errno(errno, | |
1334 | "Cannot change owner of /dev/tty%u: %m", | |
1335 | s->vtnr); | |
1336 | goto error; | |
1337 | } | |
1338 | ||
1339 | r = ioctl(vt, KDSKBMODE, K_OFF); | |
1340 | if (r < 0) { | |
1341 | r = log_error_errno(errno, | |
1342 | "Cannot set K_OFF on /dev/tty%u: %m", | |
1343 | s->vtnr); | |
1344 | goto error; | |
1345 | } | |
1346 | ||
1347 | r = ioctl(vt, KDSETMODE, KD_GRAPHICS); | |
1348 | if (r < 0) { | |
1349 | r = log_error_errno(errno, | |
1350 | "Cannot set KD_GRAPHICS on /dev/tty%u: %m", | |
1351 | s->vtnr); | |
1352 | goto error; | |
1353 | } | |
1354 | ||
1355 | /* Oh, thanks to the VT layer, VT_AUTO does not work with KD_GRAPHICS. | |
1356 | * So we need a dummy handler here which just acknowledges *all* VT | |
1357 | * switch requests. */ | |
1358 | mode.mode = VT_PROCESS; | |
1359 | mode.relsig = SIGRTMIN; | |
1360 | mode.acqsig = SIGRTMIN + 1; | |
1361 | r = ioctl(vt, VT_SETMODE, &mode); | |
1362 | if (r < 0) { | |
1363 | r = log_error_errno(errno, | |
1364 | "Cannot set VT_PROCESS on /dev/tty%u: %m", | |
1365 | s->vtnr); | |
1366 | goto error; | |
1367 | } | |
1368 | ||
1369 | return 0; | |
1370 | ||
1371 | error: | |
1372 | session_restore_vt(s); | |
1373 | return r; | |
1374 | } | |
1375 | ||
1376 | static void session_restore_vt(Session *s) { | |
1377 | int r; | |
1378 | ||
1379 | assert(s); | |
1380 | ||
1381 | if (s->vtfd < 0) | |
1382 | return; | |
1383 | ||
1384 | r = vt_restore(s->vtfd); | |
1385 | if (r == -EIO) { | |
1386 | /* It might happen if the controlling process exited before or while we were | |
1387 | * restoring the VT as it would leave the old file-descriptor in a hung-up | |
1388 | * state. In this case let's retry with a fresh handle to the virtual terminal. */ | |
1389 | ||
1390 | /* We do a little dance to avoid having the terminal be available | |
1391 | * for reuse before we've cleaned it up. */ | |
1392 | ||
1393 | int fd = session_open_vt(s, /* reopen = */ true); | |
1394 | if (fd >= 0) | |
1395 | r = vt_restore(fd); | |
1396 | } | |
1397 | if (r < 0) | |
1398 | log_warning_errno(r, "Failed to restore VT, ignoring: %m"); | |
1399 | ||
1400 | s->vtfd = safe_close(s->vtfd); | |
1401 | } | |
1402 | ||
1403 | void session_leave_vt(Session *s) { | |
1404 | int r; | |
1405 | ||
1406 | assert(s); | |
1407 | ||
1408 | /* This is called whenever we get a VT-switch signal from the kernel. | |
1409 | * We acknowledge all of them unconditionally. Note that session are | |
1410 | * free to overwrite those handlers and we only register them for | |
1411 | * sessions with controllers. Legacy sessions are not affected. | |
1412 | * However, if we switch from a non-legacy to a legacy session, we must | |
1413 | * make sure to pause all device before acknowledging the switch. We | |
1414 | * process the real switch only after we are notified via sysfs, so the | |
1415 | * legacy session might have already started using the devices. If we | |
1416 | * don't pause the devices before the switch, we might confuse the | |
1417 | * session we switch to. */ | |
1418 | ||
1419 | if (s->vtfd < 0) | |
1420 | return; | |
1421 | ||
1422 | session_device_pause_all(s); | |
1423 | r = vt_release(s->vtfd, /* restore = */ false); | |
1424 | if (r == -EIO) { | |
1425 | /* Handle the same VT hung-up case as in session_restore_vt */ | |
1426 | ||
1427 | int fd = session_open_vt(s, /* reopen = */ true); | |
1428 | if (fd >= 0) | |
1429 | r = vt_release(fd, /* restore = */ false); | |
1430 | } | |
1431 | if (r < 0) | |
1432 | log_debug_errno(r, "Cannot release VT of session %s: %m", s->id); | |
1433 | } | |
1434 | ||
1435 | bool session_is_controller(Session *s, const char *sender) { | |
1436 | return streq_ptr(ASSERT_PTR(s)->controller, sender); | |
1437 | } | |
1438 | ||
1439 | static void session_release_controller(Session *s, bool notify) { | |
1440 | _unused_ _cleanup_free_ char *name = NULL; | |
1441 | SessionDevice *sd; | |
1442 | ||
1443 | assert(s); | |
1444 | ||
1445 | if (!s->controller) | |
1446 | return; | |
1447 | ||
1448 | name = s->controller; | |
1449 | ||
1450 | /* By resetting the controller before releasing the devices, we won't send notification signals. | |
1451 | * This avoids sending useless notifications if the controller is released on disconnects. */ | |
1452 | if (!notify) | |
1453 | s->controller = NULL; | |
1454 | ||
1455 | while ((sd = hashmap_first(s->devices))) | |
1456 | session_device_free(sd); | |
1457 | ||
1458 | s->controller = NULL; | |
1459 | s->track = sd_bus_track_unref(s->track); | |
1460 | } | |
1461 | ||
1462 | static int on_bus_track(sd_bus_track *track, void *userdata) { | |
1463 | Session *s = ASSERT_PTR(userdata); | |
1464 | ||
1465 | assert(track); | |
1466 | ||
1467 | session_drop_controller(s); | |
1468 | ||
1469 | return 0; | |
1470 | } | |
1471 | ||
1472 | int session_set_controller(Session *s, const char *sender, bool force, bool prepare) { | |
1473 | _cleanup_free_ char *name = NULL; | |
1474 | int r; | |
1475 | ||
1476 | assert(s); | |
1477 | assert(sender); | |
1478 | ||
1479 | if (session_is_controller(s, sender)) | |
1480 | return 0; | |
1481 | if (s->controller && !force) | |
1482 | return -EBUSY; | |
1483 | ||
1484 | name = strdup(sender); | |
1485 | if (!name) | |
1486 | return -ENOMEM; | |
1487 | ||
1488 | s->track = sd_bus_track_unref(s->track); | |
1489 | r = sd_bus_track_new(s->manager->bus, &s->track, on_bus_track, s); | |
1490 | if (r < 0) | |
1491 | return r; | |
1492 | ||
1493 | r = sd_bus_track_add_name(s->track, name); | |
1494 | if (r < 0) | |
1495 | return r; | |
1496 | ||
1497 | /* When setting a session controller, we forcibly mute the VT and set | |
1498 | * it into graphics-mode. Applications can override that by changing | |
1499 | * VT state after calling TakeControl(). However, this serves as a good | |
1500 | * default and well-behaving controllers can now ignore VTs entirely. | |
1501 | * Note that we reset the VT on ReleaseControl() and if the controller | |
1502 | * exits. | |
1503 | * If logind crashes/restarts, we restore the controller during restart | |
1504 | * (without preparing the VT since the controller has probably overridden | |
1505 | * VT state by now) or reset the VT in case it crashed/exited, too. */ | |
1506 | if (prepare) { | |
1507 | r = session_prepare_vt(s); | |
1508 | if (r < 0) { | |
1509 | s->track = sd_bus_track_unref(s->track); | |
1510 | return r; | |
1511 | } | |
1512 | } | |
1513 | ||
1514 | session_release_controller(s, true); | |
1515 | s->controller = TAKE_PTR(name); | |
1516 | (void) session_save(s); | |
1517 | ||
1518 | return 0; | |
1519 | } | |
1520 | ||
1521 | void session_drop_controller(Session *s) { | |
1522 | assert(s); | |
1523 | ||
1524 | if (!s->controller) | |
1525 | return; | |
1526 | ||
1527 | s->track = sd_bus_track_unref(s->track); | |
1528 | session_set_type(s, s->original_type); | |
1529 | session_release_controller(s, false); | |
1530 | (void) session_save(s); | |
1531 | session_restore_vt(s); | |
1532 | } | |
1533 | ||
1534 | bool session_job_pending(Session *s) { | |
1535 | assert(s); | |
1536 | assert(s->user); | |
1537 | ||
1538 | /* Check if we have some jobs enqueued and not finished yet. Each time we get JobRemoved signal about | |
1539 | * relevant units, session_send_create_reply and hence us is called (see match_job_removed). | |
1540 | * Note that we don't care about job result here. */ | |
1541 | ||
1542 | return s->scope_job || | |
1543 | s->user->runtime_dir_job || | |
1544 | (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) && s->user->service_manager_job); | |
1545 | } | |
1546 | ||
1547 | int session_send_create_reply(Session *s, const sd_bus_error *error) { | |
1548 | int r; | |
1549 | ||
1550 | assert(s); | |
1551 | ||
1552 | /* If error occurred, return it immediately. Otherwise let's wait for all jobs to finish before | |
1553 | * continuing. */ | |
1554 | if (!sd_bus_error_is_set(error) && session_job_pending(s)) | |
1555 | return 0; | |
1556 | ||
1557 | r = 0; | |
1558 | RET_GATHER(r, session_send_create_reply_bus(s, error)); | |
1559 | RET_GATHER(r, session_send_create_reply_varlink(s, error)); | |
1560 | return r; | |
1561 | } | |
1562 | ||
1563 | bool session_is_self(const char *name) { | |
1564 | return isempty(name) || streq(name, "self"); | |
1565 | } | |
1566 | ||
1567 | bool session_is_auto(const char *name) { | |
1568 | return streq_ptr(name, "auto"); | |
1569 | } | |
1570 | ||
1571 | static const char* const session_state_table[_SESSION_STATE_MAX] = { | |
1572 | [SESSION_OPENING] = "opening", | |
1573 | [SESSION_ONLINE] = "online", | |
1574 | [SESSION_ACTIVE] = "active", | |
1575 | [SESSION_CLOSING] = "closing", | |
1576 | }; | |
1577 | ||
1578 | DEFINE_STRING_TABLE_LOOKUP(session_state, SessionState); | |
1579 | ||
1580 | static const char* const session_type_table[_SESSION_TYPE_MAX] = { | |
1581 | [SESSION_UNSPECIFIED] = "unspecified", | |
1582 | [SESSION_TTY] = "tty", | |
1583 | [SESSION_X11] = "x11", | |
1584 | [SESSION_WAYLAND] = "wayland", | |
1585 | [SESSION_MIR] = "mir", | |
1586 | [SESSION_WEB] = "web", | |
1587 | }; | |
1588 | ||
1589 | DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType); | |
1590 | ||
1591 | static const char* const session_class_table[_SESSION_CLASS_MAX] = { | |
1592 | [SESSION_USER] = "user", | |
1593 | [SESSION_USER_EARLY] = "user-early", | |
1594 | [SESSION_USER_INCOMPLETE] = "user-incomplete", | |
1595 | [SESSION_USER_LIGHT] = "user-light", | |
1596 | [SESSION_USER_EARLY_LIGHT] = "user-early-light", | |
1597 | [SESSION_GREETER] = "greeter", | |
1598 | [SESSION_LOCK_SCREEN] = "lock-screen", | |
1599 | [SESSION_BACKGROUND] = "background", | |
1600 | [SESSION_BACKGROUND_LIGHT] = "background-light", | |
1601 | [SESSION_MANAGER] = "manager", | |
1602 | [SESSION_MANAGER_EARLY] = "manager-early", | |
1603 | [SESSION_NONE] = "none", | |
1604 | }; | |
1605 | ||
1606 | DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass); | |
1607 | ||
1608 | static const char* const kill_whom_table[_KILL_WHOM_MAX] = { | |
1609 | [KILL_LEADER] = "leader", | |
1610 | [KILL_ALL] = "all", | |
1611 | }; | |
1612 | ||
1613 | DEFINE_STRING_TABLE_LOOKUP(kill_whom, KillWhom); | |
1614 | ||
1615 | static const char* const tty_validity_table[_TTY_VALIDITY_MAX] = { | |
1616 | [TTY_FROM_PAM] = "from-pam", | |
1617 | [TTY_FROM_UTMP] = "from-utmp", | |
1618 | [TTY_UTMP_INCONSISTENT] = "utmp-inconsistent", | |
1619 | }; | |
1620 | ||
1621 | DEFINE_STRING_TABLE_LOOKUP(tty_validity, TTYValidity); |