]>
Commit | Line | Data |
---|---|---|
2b3ab29d ZJS |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2011 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <sys/types.h> | |
2b3ab29d ZJS |
23 | #include <sys/ioctl.h> |
24 | #include <fcntl.h> | |
25 | #include <pwd.h> | |
2b3ab29d ZJS |
26 | #include <linux/vt.h> |
27 | ||
2b3ab29d | 28 | #include "strv.h" |
cc377381 | 29 | #include "cgroup-util.h" |
cc377381 LP |
30 | #include "bus-util.h" |
31 | #include "bus-error.h" | |
6a79c586 | 32 | #include "udev-util.h" |
cc377381 | 33 | #include "logind.h" |
288a74cc | 34 | #include "terminal-util.h" |
2b3ab29d ZJS |
35 | |
36 | int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_device) { | |
37 | Device *d; | |
38 | ||
39 | assert(m); | |
40 | assert(sysfs); | |
41 | ||
42 | d = hashmap_get(m->devices, sysfs); | |
61376f96 | 43 | if (d) |
2b3ab29d ZJS |
44 | /* we support adding master-flags, but not removing them */ |
45 | d->master = d->master || master; | |
61376f96 ZJS |
46 | else { |
47 | d = device_new(m, sysfs, master); | |
48 | if (!d) | |
49 | return -ENOMEM; | |
2b3ab29d ZJS |
50 | } |
51 | ||
2b3ab29d ZJS |
52 | if (_device) |
53 | *_device = d; | |
54 | ||
55 | return 0; | |
56 | } | |
57 | ||
58 | int manager_add_seat(Manager *m, const char *id, Seat **_seat) { | |
59 | Seat *s; | |
60 | ||
61 | assert(m); | |
62 | assert(id); | |
63 | ||
64 | s = hashmap_get(m->seats, id); | |
61376f96 ZJS |
65 | if (!s) { |
66 | s = seat_new(m, id); | |
67 | if (!s) | |
68 | return -ENOMEM; | |
2b3ab29d ZJS |
69 | } |
70 | ||
2b3ab29d ZJS |
71 | if (_seat) |
72 | *_seat = s; | |
73 | ||
74 | return 0; | |
75 | } | |
76 | ||
77 | int manager_add_session(Manager *m, const char *id, Session **_session) { | |
78 | Session *s; | |
79 | ||
80 | assert(m); | |
81 | assert(id); | |
82 | ||
83 | s = hashmap_get(m->sessions, id); | |
61376f96 ZJS |
84 | if (!s) { |
85 | s = session_new(m, id); | |
86 | if (!s) | |
87 | return -ENOMEM; | |
2b3ab29d ZJS |
88 | } |
89 | ||
2b3ab29d ZJS |
90 | if (_session) |
91 | *_session = s; | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) { | |
97 | User *u; | |
98 | ||
99 | assert(m); | |
100 | assert(name); | |
101 | ||
8cb4ab00 | 102 | u = hashmap_get(m->users, UID_TO_PTR(uid)); |
61376f96 ZJS |
103 | if (!u) { |
104 | u = user_new(m, uid, gid, name); | |
105 | if (!u) | |
106 | return -ENOMEM; | |
2b3ab29d ZJS |
107 | } |
108 | ||
2b3ab29d ZJS |
109 | if (_user) |
110 | *_user = u; | |
111 | ||
112 | return 0; | |
113 | } | |
114 | ||
115 | int manager_add_user_by_name(Manager *m, const char *name, User **_user) { | |
116 | uid_t uid; | |
117 | gid_t gid; | |
118 | int r; | |
119 | ||
120 | assert(m); | |
121 | assert(name); | |
122 | ||
123 | r = get_user_creds(&name, &uid, &gid, NULL, NULL); | |
124 | if (r < 0) | |
125 | return r; | |
126 | ||
127 | return manager_add_user(m, uid, gid, name, _user); | |
128 | } | |
129 | ||
130 | int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) { | |
131 | struct passwd *p; | |
132 | ||
133 | assert(m); | |
134 | ||
135 | errno = 0; | |
136 | p = getpwuid(uid); | |
137 | if (!p) | |
138 | return errno ? -errno : -ENOENT; | |
139 | ||
140 | return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user); | |
141 | } | |
142 | ||
143 | int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor) { | |
144 | Inhibitor *i; | |
145 | ||
146 | assert(m); | |
147 | assert(id); | |
148 | ||
149 | i = hashmap_get(m->inhibitors, id); | |
150 | if (i) { | |
151 | if (_inhibitor) | |
152 | *_inhibitor = i; | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
157 | i = inhibitor_new(m, id); | |
158 | if (!i) | |
159 | return -ENOMEM; | |
160 | ||
161 | if (_inhibitor) | |
162 | *_inhibitor = i; | |
163 | ||
164 | return 0; | |
165 | } | |
166 | ||
167 | int manager_add_button(Manager *m, const char *name, Button **_button) { | |
168 | Button *b; | |
169 | ||
170 | assert(m); | |
171 | assert(name); | |
172 | ||
173 | b = hashmap_get(m->buttons, name); | |
61376f96 ZJS |
174 | if (!b) { |
175 | b = button_new(m, name); | |
176 | if (!b) | |
177 | return -ENOMEM; | |
2b3ab29d ZJS |
178 | } |
179 | ||
2b3ab29d ZJS |
180 | if (_button) |
181 | *_button = b; | |
182 | ||
183 | return 0; | |
184 | } | |
185 | ||
186 | int manager_watch_busname(Manager *m, const char *name) { | |
187 | char *n; | |
188 | int r; | |
189 | ||
190 | assert(m); | |
191 | assert(name); | |
192 | ||
cc377381 | 193 | if (set_get(m->busnames, (char*) name)) |
2b3ab29d ZJS |
194 | return 0; |
195 | ||
196 | n = strdup(name); | |
197 | if (!n) | |
198 | return -ENOMEM; | |
199 | ||
cc377381 | 200 | r = set_put(m->busnames, n); |
2b3ab29d ZJS |
201 | if (r < 0) { |
202 | free(n); | |
203 | return r; | |
204 | } | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
209 | void manager_drop_busname(Manager *m, const char *name) { | |
210 | Session *session; | |
211 | Iterator i; | |
2b3ab29d ZJS |
212 | |
213 | assert(m); | |
214 | assert(name); | |
215 | ||
2b3ab29d ZJS |
216 | /* keep it if the name still owns a controller */ |
217 | HASHMAP_FOREACH(session, m->sessions, i) | |
218 | if (session_is_controller(session, name)) | |
219 | return; | |
220 | ||
cc377381 | 221 | free(set_remove(m->busnames, (char*) name)); |
2b3ab29d ZJS |
222 | } |
223 | ||
224 | int manager_process_seat_device(Manager *m, struct udev_device *d) { | |
225 | Device *device; | |
226 | int r; | |
227 | ||
228 | assert(m); | |
229 | ||
230 | if (streq_ptr(udev_device_get_action(d), "remove")) { | |
231 | ||
232 | device = hashmap_get(m->devices, udev_device_get_syspath(d)); | |
233 | if (!device) | |
234 | return 0; | |
235 | ||
236 | seat_add_to_gc_queue(device->seat); | |
237 | device_free(device); | |
238 | ||
239 | } else { | |
240 | const char *sn; | |
241 | Seat *seat = NULL; | |
242 | bool master; | |
243 | ||
244 | sn = udev_device_get_property_value(d, "ID_SEAT"); | |
245 | if (isempty(sn)) | |
246 | sn = "seat0"; | |
247 | ||
248 | if (!seat_name_is_valid(sn)) { | |
249 | log_warning("Device with invalid seat name %s found, ignoring.", sn); | |
250 | return 0; | |
251 | } | |
252 | ||
6a79c586 | 253 | seat = hashmap_get(m->seats, sn); |
2b3ab29d | 254 | master = udev_device_has_tag(d, "master-of-seat"); |
6a79c586 LP |
255 | |
256 | /* Ignore non-master devices for unknown seats */ | |
257 | if (!master && !seat) | |
2b3ab29d ZJS |
258 | return 0; |
259 | ||
260 | r = manager_add_device(m, udev_device_get_syspath(d), master, &device); | |
261 | if (r < 0) | |
262 | return r; | |
263 | ||
264 | if (!seat) { | |
265 | r = manager_add_seat(m, sn, &seat); | |
266 | if (r < 0) { | |
267 | if (!device->seat) | |
268 | device_free(device); | |
269 | ||
270 | return r; | |
271 | } | |
272 | } | |
273 | ||
274 | device_attach(device, seat); | |
275 | seat_start(seat); | |
276 | } | |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
281 | int manager_process_button_device(Manager *m, struct udev_device *d) { | |
282 | Button *b; | |
283 | ||
284 | int r; | |
285 | ||
286 | assert(m); | |
287 | ||
288 | if (streq_ptr(udev_device_get_action(d), "remove")) { | |
289 | ||
290 | b = hashmap_get(m->buttons, udev_device_get_sysname(d)); | |
291 | if (!b) | |
292 | return 0; | |
293 | ||
294 | button_free(b); | |
295 | ||
296 | } else { | |
297 | const char *sn; | |
298 | ||
299 | r = manager_add_button(m, udev_device_get_sysname(d), &b); | |
300 | if (r < 0) | |
301 | return r; | |
302 | ||
303 | sn = udev_device_get_property_value(d, "ID_SEAT"); | |
304 | if (isempty(sn)) | |
305 | sn = "seat0"; | |
306 | ||
307 | button_set_seat(b, sn); | |
308 | button_open(b); | |
309 | } | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) { | |
315 | _cleanup_free_ char *unit = NULL; | |
316 | Session *s; | |
317 | int r; | |
318 | ||
319 | assert(m); | |
2b3ab29d ZJS |
320 | |
321 | if (pid < 1) | |
322 | return -EINVAL; | |
323 | ||
324 | r = cg_pid_get_unit(pid, &unit); | |
325 | if (r < 0) | |
d3e84ddb | 326 | return 0; |
2b3ab29d ZJS |
327 | |
328 | s = hashmap_get(m->session_units, unit); | |
329 | if (!s) | |
330 | return 0; | |
331 | ||
b80120c4 DH |
332 | if (session) |
333 | *session = s; | |
2b3ab29d ZJS |
334 | return 1; |
335 | } | |
336 | ||
337 | int manager_get_user_by_pid(Manager *m, pid_t pid, User **user) { | |
338 | _cleanup_free_ char *unit = NULL; | |
339 | User *u; | |
340 | int r; | |
341 | ||
342 | assert(m); | |
343 | assert(user); | |
344 | ||
345 | if (pid < 1) | |
346 | return -EINVAL; | |
347 | ||
348 | r = cg_pid_get_slice(pid, &unit); | |
349 | if (r < 0) | |
d3e84ddb | 350 | return 0; |
2b3ab29d ZJS |
351 | |
352 | u = hashmap_get(m->user_units, unit); | |
353 | if (!u) | |
354 | return 0; | |
355 | ||
356 | *user = u; | |
357 | return 1; | |
358 | } | |
359 | ||
360 | int manager_get_idle_hint(Manager *m, dual_timestamp *t) { | |
361 | Session *s; | |
362 | bool idle_hint; | |
5cb14b37 | 363 | dual_timestamp ts = DUAL_TIMESTAMP_NULL; |
2b3ab29d ZJS |
364 | Iterator i; |
365 | ||
366 | assert(m); | |
367 | ||
85a428c6 | 368 | idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0, NULL); |
2b3ab29d ZJS |
369 | |
370 | HASHMAP_FOREACH(s, m->sessions, i) { | |
371 | dual_timestamp k; | |
372 | int ih; | |
373 | ||
374 | ih = session_get_idle_hint(s, &k); | |
375 | if (ih < 0) | |
376 | return ih; | |
377 | ||
378 | if (!ih) { | |
379 | if (!idle_hint) { | |
380 | if (k.monotonic < ts.monotonic) | |
381 | ts = k; | |
382 | } else { | |
383 | idle_hint = false; | |
384 | ts = k; | |
385 | } | |
386 | } else if (idle_hint) { | |
387 | ||
388 | if (k.monotonic > ts.monotonic) | |
389 | ts = k; | |
390 | } | |
391 | } | |
392 | ||
393 | if (t) | |
394 | *t = ts; | |
395 | ||
396 | return idle_hint; | |
397 | } | |
398 | ||
399 | bool manager_shall_kill(Manager *m, const char *user) { | |
400 | assert(m); | |
401 | assert(user); | |
402 | ||
403 | if (!m->kill_user_processes) | |
404 | return false; | |
405 | ||
406 | if (strv_contains(m->kill_exclude_users, user)) | |
407 | return false; | |
408 | ||
409 | if (strv_isempty(m->kill_only_users)) | |
410 | return true; | |
411 | ||
412 | return strv_contains(m->kill_only_users, user); | |
413 | } | |
414 | ||
92bd5ff3 | 415 | static int vt_is_busy(unsigned int vtnr) { |
2b3ab29d | 416 | struct vt_stat vt_stat; |
61376f96 ZJS |
417 | int r = 0; |
418 | _cleanup_close_ int fd; | |
2b3ab29d ZJS |
419 | |
420 | assert(vtnr >= 1); | |
421 | ||
422 | /* We explicitly open /dev/tty1 here instead of /dev/tty0. If | |
423 | * we'd open the latter we'd open the foreground tty which | |
424 | * hence would be unconditionally busy. By opening /dev/tty1 | |
425 | * we avoid this. Since tty1 is special and needs to be an | |
426 | * explicitly loaded getty or DM this is safe. */ | |
427 | ||
428 | fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); | |
429 | if (fd < 0) | |
430 | return -errno; | |
431 | ||
432 | if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0) | |
433 | r = -errno; | |
434 | else | |
435 | r = !!(vt_stat.v_state & (1 << vtnr)); | |
436 | ||
2b3ab29d ZJS |
437 | return r; |
438 | } | |
439 | ||
92bd5ff3 | 440 | int manager_spawn_autovt(Manager *m, unsigned int vtnr) { |
cc377381 | 441 | _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; |
61376f96 | 442 | char name[sizeof("autovt@tty.service") + DECIMAL_STR_MAX(unsigned int)]; |
2b3ab29d | 443 | int r; |
2b3ab29d ZJS |
444 | |
445 | assert(m); | |
446 | assert(vtnr >= 1); | |
447 | ||
92bd5ff3 DH |
448 | if (vtnr > m->n_autovts && |
449 | vtnr != m->reserve_vt) | |
2b3ab29d ZJS |
450 | return 0; |
451 | ||
92bd5ff3 | 452 | if (vtnr != m->reserve_vt) { |
2b3ab29d ZJS |
453 | /* If this is the reserved TTY, we'll start the getty |
454 | * on it in any case, but otherwise only if it is not | |
455 | * busy. */ | |
456 | ||
457 | r = vt_is_busy(vtnr); | |
458 | if (r < 0) | |
459 | return r; | |
460 | else if (r > 0) | |
461 | return -EBUSY; | |
462 | } | |
463 | ||
61376f96 | 464 | snprintf(name, sizeof(name), "autovt@tty%u.service", vtnr); |
cc377381 | 465 | r = sd_bus_call_method( |
2b3ab29d ZJS |
466 | m->bus, |
467 | "org.freedesktop.systemd1", | |
468 | "/org/freedesktop/systemd1", | |
469 | "org.freedesktop.systemd1.Manager", | |
470 | "StartUnit", | |
cc377381 | 471 | &error, |
2b3ab29d | 472 | NULL, |
cc377381 LP |
473 | "ss", name, "fail"); |
474 | if (r < 0) | |
475 | log_error("Failed to start %s: %s", name, bus_error_message(&error, r)); | |
2b3ab29d ZJS |
476 | |
477 | return r; | |
478 | } | |
2d62c530 | 479 | |
602a41c2 | 480 | static bool manager_is_docked(Manager *m) { |
2d62c530 LP |
481 | Iterator i; |
482 | Button *b; | |
483 | ||
484 | HASHMAP_FOREACH(b, m->buttons, i) | |
485 | if (b->docked) | |
486 | return true; | |
487 | ||
488 | return false; | |
489 | } | |
6a79c586 | 490 | |
602a41c2 | 491 | static int manager_count_external_displays(Manager *m) { |
6a79c586 LP |
492 | _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; |
493 | struct udev_list_entry *item = NULL, *first = NULL; | |
494 | int r; | |
495 | int n = 0; | |
496 | ||
497 | e = udev_enumerate_new(m->udev); | |
498 | if (!e) | |
499 | return -ENOMEM; | |
500 | ||
501 | r = udev_enumerate_add_match_subsystem(e, "drm"); | |
502 | if (r < 0) | |
503 | return r; | |
504 | ||
505 | r = udev_enumerate_scan_devices(e); | |
506 | if (r < 0) | |
507 | return r; | |
508 | ||
509 | first = udev_enumerate_get_list_entry(e); | |
510 | udev_list_entry_foreach(item, first) { | |
511 | _cleanup_udev_device_unref_ struct udev_device *d = NULL; | |
512 | struct udev_device *p; | |
602a41c2 LP |
513 | const char *status, *enabled, *dash, *nn, *i; |
514 | bool external = false; | |
6a79c586 LP |
515 | |
516 | d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item)); | |
517 | if (!d) | |
518 | return -ENOMEM; | |
519 | ||
520 | p = udev_device_get_parent(d); | |
521 | if (!p) | |
94036de8 | 522 | continue; |
6a79c586 LP |
523 | |
524 | /* If the parent shares the same subsystem as the | |
525 | * device we are looking at then it is a connector, | |
526 | * which is what we are interested in. */ | |
527 | if (!streq_ptr(udev_device_get_subsystem(p), "drm")) | |
528 | continue; | |
529 | ||
602a41c2 LP |
530 | nn = udev_device_get_sysname(d); |
531 | if (!nn) | |
532 | continue; | |
533 | ||
534 | /* Ignore internal displays: the type is encoded in | |
535 | * the sysfs name, as the second dash seperated item | |
536 | * (the first is the card name, the last the connector | |
537 | * number). We implement a whitelist of external | |
538 | * displays here, rather than a whitelist, to ensure | |
539 | * we don't block suspends too eagerly. */ | |
540 | dash = strchr(nn, '-'); | |
541 | if (!dash) | |
542 | continue; | |
543 | ||
544 | dash++; | |
545 | FOREACH_STRING(i, "VGA-", "DVI-I-", "DVI-D-", "DVI-A-" | |
546 | "Composite-", "SVIDEO-", "Component-", | |
547 | "DIN-", "DP-", "HDMI-A-", "HDMI-B-", "TV-") { | |
548 | ||
549 | if (startswith(dash, i)) { | |
550 | external = true; | |
551 | break; | |
552 | } | |
553 | } | |
554 | if (!external) | |
555 | continue; | |
556 | ||
557 | /* Ignore ports that are not enabled */ | |
558 | enabled = udev_device_get_sysattr_value(d, "enabled"); | |
559 | if (!enabled) | |
560 | continue; | |
561 | if (!streq_ptr(enabled, "enabled")) | |
562 | continue; | |
563 | ||
6a79c586 LP |
564 | /* We count any connector which is not explicitly |
565 | * "disconnected" as connected. */ | |
566 | status = udev_device_get_sysattr_value(d, "status"); | |
567 | if (!streq_ptr(status, "disconnected")) | |
568 | n++; | |
569 | } | |
570 | ||
571 | return n; | |
572 | } | |
3c56cab4 | 573 | |
602a41c2 | 574 | bool manager_is_docked_or_external_displays(Manager *m) { |
3c56cab4 BW |
575 | int n; |
576 | ||
577 | /* If we are docked don't react to lid closing */ | |
578 | if (manager_is_docked(m)) { | |
579 | log_debug("System is docked."); | |
580 | return true; | |
581 | } | |
582 | ||
583 | /* If we have more than one display connected, | |
584 | * assume that we are docked. */ | |
602a41c2 | 585 | n = manager_count_external_displays(m); |
3c56cab4 | 586 | if (n < 0) |
da927ba9 | 587 | log_warning_errno(n, "Display counting failed: %m"); |
602a41c2 LP |
588 | else if (n >= 1) { |
589 | log_debug("External (%i) displays connected.", n); | |
3c56cab4 BW |
590 | return true; |
591 | } | |
592 | ||
593 | return false; | |
594 | } |