]>
Commit | Line | Data |
---|---|---|
6c12b52e LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2013 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 <errno.h> | |
23 | #include <signal.h> | |
24 | #include <unistd.h> | |
25 | ||
26 | #include "unit.h" | |
27 | #include "scope.h" | |
28 | #include "load-fragment.h" | |
29 | #include "log.h" | |
30 | #include "dbus-scope.h" | |
31 | #include "special.h" | |
32 | #include "unit-name.h" | |
33 | #include "load-dropin.h" | |
34 | ||
35 | static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { | |
36 | [SCOPE_DEAD] = UNIT_INACTIVE, | |
37 | [SCOPE_RUNNING] = UNIT_ACTIVE, | |
38 | [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, | |
39 | [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, | |
40 | [SCOPE_FAILED] = UNIT_FAILED | |
41 | }; | |
42 | ||
43 | static void scope_init(Unit *u) { | |
44 | Scope *s = SCOPE(u); | |
45 | ||
46 | assert(u); | |
47 | assert(u->load_state == UNIT_STUB); | |
48 | ||
49 | s->timeout_stop_usec = DEFAULT_TIMEOUT_USEC; | |
50 | ||
51 | watch_init(&s->timer_watch); | |
52 | ||
53 | cgroup_context_init(&s->cgroup_context); | |
54 | kill_context_init(&s->kill_context); | |
55 | ||
56 | UNIT(s)->ignore_on_isolate = true; | |
57 | UNIT(s)->ignore_on_snapshot = true; | |
58 | } | |
59 | ||
60 | static void scope_done(Unit *u) { | |
61 | Scope *s = SCOPE(u); | |
62 | ||
63 | assert(u); | |
64 | ||
65 | cgroup_context_done(&s->cgroup_context); | |
66 | ||
67 | set_free(s->pids); | |
68 | s->pids = NULL; | |
69 | ||
70 | unit_unwatch_timer(u, &s->timer_watch); | |
71 | } | |
72 | ||
73 | static void scope_set_state(Scope *s, ScopeState state) { | |
74 | ScopeState old_state; | |
75 | assert(s); | |
76 | ||
77 | old_state = s->state; | |
78 | s->state = state; | |
79 | ||
80 | if (state != SCOPE_STOP_SIGTERM && | |
81 | state != SCOPE_STOP_SIGKILL) | |
82 | unit_unwatch_timer(UNIT(s), &s->timer_watch); | |
83 | ||
84 | if (state != old_state) | |
85 | log_debug("%s changed %s -> %s", | |
86 | UNIT(s)->id, | |
87 | scope_state_to_string(old_state), | |
88 | scope_state_to_string(state)); | |
89 | ||
90 | unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); | |
91 | } | |
92 | ||
93 | static int scope_add_default_dependencies(Scope *s) { | |
94 | int r; | |
95 | ||
96 | assert(s); | |
97 | ||
98 | /* Make sure scopes are unloaded on shutdown */ | |
99 | r = unit_add_two_dependencies_by_name( | |
100 | UNIT(s), | |
101 | UNIT_BEFORE, UNIT_CONFLICTS, | |
102 | SPECIAL_SHUTDOWN_TARGET, NULL, true); | |
103 | if (r < 0) | |
104 | return r; | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
109 | static int scope_verify(Scope *s) { | |
110 | assert(s); | |
111 | ||
112 | if (UNIT(s)->load_state != UNIT_LOADED) | |
113 | return 0; | |
114 | ||
0c5778a2 | 115 | if (set_size(s->pids) <= 0 && UNIT(s)->manager->n_reloading <= 0) { |
6c12b52e LP |
116 | log_error_unit(UNIT(s)->id, "Scope %s has no PIDs. Refusing.", UNIT(s)->id); |
117 | return -EINVAL; | |
118 | } | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
123 | static int scope_load(Unit *u) { | |
124 | Scope *s = SCOPE(u); | |
125 | int r; | |
126 | ||
127 | assert(s); | |
128 | assert(u->load_state == UNIT_STUB); | |
129 | ||
130 | if (!u->transient && UNIT(s)->manager->n_reloading <= 0) | |
131 | return -ENOENT; | |
132 | ||
133 | u->load_state = UNIT_LOADED; | |
134 | ||
135 | r = unit_load_dropin(u); | |
136 | if (r < 0) | |
137 | return r; | |
138 | ||
139 | r = unit_add_default_slice(u); | |
140 | if (r < 0) | |
141 | return r; | |
142 | ||
143 | if (u->default_dependencies) { | |
144 | r = scope_add_default_dependencies(s); | |
145 | if (r < 0) | |
146 | return r; | |
147 | } | |
148 | ||
149 | return scope_verify(s); | |
150 | } | |
151 | ||
152 | static int scope_coldplug(Unit *u) { | |
153 | Scope *s = SCOPE(u); | |
154 | int r; | |
155 | ||
156 | assert(s); | |
157 | assert(s->state == SCOPE_DEAD); | |
158 | ||
159 | if (s->deserialized_state != s->state) { | |
160 | ||
161 | if ((s->deserialized_state == SCOPE_STOP_SIGKILL || s->deserialized_state == SCOPE_STOP_SIGTERM) | |
162 | && s->timeout_stop_usec > 0) { | |
163 | r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_stop_usec, &s->timer_watch); | |
164 | if (r < 0) | |
165 | ||
166 | return r; | |
167 | } | |
168 | ||
169 | scope_set_state(s, s->deserialized_state); | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static void scope_dump(Unit *u, FILE *f, const char *prefix) { | |
176 | Scope *s = SCOPE(u); | |
177 | ||
178 | assert(s); | |
179 | assert(f); | |
180 | ||
181 | fprintf(f, | |
182 | "%sScope State: %s\n" | |
183 | "%sResult: %s\n", | |
184 | prefix, scope_state_to_string(s->state), | |
185 | prefix, scope_result_to_string(s->result)); | |
186 | ||
187 | cgroup_context_dump(&s->cgroup_context, f, prefix); | |
188 | kill_context_dump(&s->kill_context, f, prefix); | |
189 | } | |
190 | ||
191 | static void scope_enter_dead(Scope *s, ScopeResult f) { | |
192 | assert(s); | |
193 | ||
194 | if (f != SCOPE_SUCCESS) | |
195 | s->result = f; | |
196 | ||
197 | scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); | |
198 | } | |
199 | ||
200 | static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { | |
201 | int r; | |
202 | ||
203 | assert(s); | |
204 | ||
205 | if (f != SCOPE_SUCCESS) | |
206 | s->result = f; | |
207 | ||
208 | r = unit_kill_context( | |
209 | UNIT(s), | |
210 | &s->kill_context, | |
211 | state != SCOPE_STOP_SIGTERM, | |
212 | -1, -1, false); | |
213 | if (r < 0) | |
214 | goto fail; | |
215 | ||
216 | if (r > 0) { | |
217 | if (s->timeout_stop_usec > 0) { | |
218 | r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_stop_usec, &s->timer_watch); | |
219 | if (r < 0) | |
220 | goto fail; | |
221 | } | |
222 | ||
223 | scope_set_state(s, state); | |
224 | } else | |
225 | scope_enter_dead(s, SCOPE_SUCCESS); | |
226 | ||
227 | return; | |
228 | ||
229 | fail: | |
230 | log_warning_unit(UNIT(s)->id, | |
231 | "%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); | |
232 | ||
233 | scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); | |
234 | } | |
235 | ||
236 | static int scope_start(Unit *u) { | |
237 | Scope *s = SCOPE(u); | |
238 | int r; | |
239 | ||
240 | assert(s); | |
241 | ||
7b617155 LP |
242 | if (s->state == SCOPE_FAILED) |
243 | return -EPERM; | |
244 | ||
6c12b52e LP |
245 | if (s->state == SCOPE_STOP_SIGTERM || |
246 | s->state == SCOPE_STOP_SIGKILL) | |
247 | return -EAGAIN; | |
248 | ||
249 | assert(s->state == SCOPE_DEAD); | |
250 | ||
251 | if (!u->transient && UNIT(s)->manager->n_reloading <= 0) | |
252 | return -ENOENT; | |
253 | ||
254 | r = unit_realize_cgroup(u); | |
255 | if (r < 0) { | |
256 | log_error("Failed to realize cgroup: %s", strerror(-r)); | |
257 | return r; | |
258 | } | |
259 | ||
13b84ec7 | 260 | r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, s->pids); |
6c12b52e LP |
261 | if (r < 0) |
262 | return r; | |
263 | ||
264 | set_free(s->pids); | |
265 | s->pids = NULL; | |
266 | ||
267 | s->result = SCOPE_SUCCESS; | |
268 | ||
269 | scope_set_state(s, SCOPE_RUNNING); | |
270 | return 0; | |
271 | } | |
272 | ||
273 | static int scope_stop(Unit *u) { | |
274 | Scope *s = SCOPE(u); | |
275 | ||
276 | assert(s); | |
277 | assert(s->state == SCOPE_RUNNING); | |
278 | ||
279 | if (s->state == SCOPE_STOP_SIGTERM || | |
280 | s->state == SCOPE_STOP_SIGKILL) | |
281 | return 0; | |
282 | ||
283 | assert(s->state == SCOPE_RUNNING); | |
284 | ||
285 | scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); | |
286 | return 0; | |
287 | } | |
288 | ||
8bcca7e2 LP |
289 | static void scope_reset_failed(Unit *u) { |
290 | Scope *s = SCOPE(u); | |
291 | ||
292 | assert(s); | |
293 | ||
294 | if (s->state == SCOPE_FAILED) | |
295 | scope_set_state(s, SCOPE_DEAD); | |
296 | ||
297 | s->result = SCOPE_SUCCESS; | |
298 | } | |
299 | ||
6c12b52e LP |
300 | static int scope_kill(Unit *u, KillWho who, int signo, DBusError *error) { |
301 | return unit_kill_common(u, who, signo, -1, -1, error); | |
302 | } | |
303 | ||
304 | static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { | |
305 | Scope *s = SCOPE(u); | |
306 | ||
307 | assert(s); | |
308 | assert(f); | |
309 | assert(fds); | |
310 | ||
311 | unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); | |
312 | return 0; | |
313 | } | |
314 | ||
315 | static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { | |
316 | Scope *s = SCOPE(u); | |
317 | ||
318 | assert(u); | |
319 | assert(key); | |
320 | assert(value); | |
321 | assert(fds); | |
322 | ||
323 | if (streq(key, "state")) { | |
324 | ScopeState state; | |
325 | ||
326 | state = scope_state_from_string(value); | |
327 | if (state < 0) | |
328 | log_debug("Failed to parse state value %s", value); | |
329 | else | |
330 | s->deserialized_state = state; | |
331 | ||
332 | } else | |
333 | log_debug("Unknown serialization key '%s'", key); | |
334 | ||
335 | return 0; | |
336 | } | |
337 | ||
338 | static bool scope_check_gc(Unit *u) { | |
339 | Scope *s = SCOPE(u); | |
340 | int r; | |
341 | ||
342 | assert(s); | |
343 | ||
344 | /* Never clean up scopes that still have a process around, | |
345 | * even if the scope is formally dead. */ | |
346 | ||
347 | if (UNIT(s)->cgroup_path) { | |
348 | r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path, true); | |
349 | if (r <= 0) | |
350 | return true; | |
351 | } | |
352 | ||
353 | return false; | |
354 | } | |
355 | ||
356 | static void scope_timer_event(Unit *u, uint64_t elapsed, Watch*w) { | |
357 | Scope *s = SCOPE(u); | |
358 | ||
359 | assert(s); | |
360 | assert(elapsed == 1); | |
361 | assert(w == &s->timer_watch); | |
362 | ||
363 | switch (s->state) { | |
364 | ||
365 | case SCOPE_STOP_SIGTERM: | |
366 | if (s->kill_context.send_sigkill) { | |
367 | log_warning_unit(u->id, "%s stopping timed out. Killing.", u->id); | |
368 | scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); | |
369 | } else { | |
370 | log_warning_unit(u->id, "%s stopping timed out. Skipping SIGKILL.", u->id); | |
371 | scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); | |
372 | } | |
373 | ||
374 | break; | |
375 | ||
376 | case SCOPE_STOP_SIGKILL: | |
377 | log_warning_unit(u->id, "%s still around after SIGKILL. Ignoring.", u->id); | |
378 | scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); | |
379 | break; | |
380 | ||
381 | default: | |
382 | assert_not_reached("Timeout at wrong time."); | |
383 | } | |
384 | } | |
385 | ||
386 | static void scope_notify_cgroup_empty_event(Unit *u) { | |
387 | Scope *s = SCOPE(u); | |
388 | assert(u); | |
389 | ||
390 | log_debug_unit(u->id, "%s: cgroup is empty", u->id); | |
391 | ||
392 | switch (s->state) { | |
393 | ||
394 | case SCOPE_RUNNING: | |
395 | case SCOPE_STOP_SIGTERM: | |
396 | case SCOPE_STOP_SIGKILL: | |
397 | scope_enter_dead(s, SCOPE_SUCCESS); | |
398 | ||
399 | break; | |
400 | ||
401 | default: | |
402 | ; | |
403 | } | |
404 | } | |
405 | ||
406 | _pure_ static UnitActiveState scope_active_state(Unit *u) { | |
407 | assert(u); | |
408 | ||
409 | return state_translation_table[SCOPE(u)->state]; | |
410 | } | |
411 | ||
412 | _pure_ static const char *scope_sub_state_to_string(Unit *u) { | |
413 | assert(u); | |
414 | ||
415 | return scope_state_to_string(SCOPE(u)->state); | |
416 | } | |
417 | ||
418 | static const char* const scope_state_table[_SCOPE_STATE_MAX] = { | |
419 | [SCOPE_DEAD] = "dead", | |
358712f3 | 420 | [SCOPE_RUNNING] = "running", |
6c12b52e LP |
421 | [SCOPE_STOP_SIGTERM] = "stop-sigterm", |
422 | [SCOPE_STOP_SIGKILL] = "stop-sigkill", | |
423 | [SCOPE_FAILED] = "failed", | |
424 | }; | |
425 | ||
426 | DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); | |
427 | ||
428 | static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { | |
429 | [SCOPE_SUCCESS] = "success", | |
430 | [SCOPE_FAILURE_RESOURCES] = "resources", | |
431 | [SCOPE_FAILURE_TIMEOUT] = "timeout", | |
432 | }; | |
433 | ||
434 | DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); | |
435 | ||
436 | const UnitVTable scope_vtable = { | |
437 | .object_size = sizeof(Scope), | |
438 | .sections = | |
439 | "Unit\0" | |
440 | "Scope\0" | |
441 | "Install\0", | |
442 | ||
443 | .private_section = "Scope", | |
444 | .cgroup_context_offset = offsetof(Scope, cgroup_context), | |
445 | ||
446 | .no_alias = true, | |
447 | .no_instances = true, | |
448 | ||
449 | .init = scope_init, | |
450 | .load = scope_load, | |
451 | .done = scope_done, | |
452 | ||
453 | .coldplug = scope_coldplug, | |
454 | ||
455 | .dump = scope_dump, | |
456 | ||
457 | .start = scope_start, | |
458 | .stop = scope_stop, | |
459 | ||
460 | .kill = scope_kill, | |
461 | ||
462 | .serialize = scope_serialize, | |
463 | .deserialize_item = scope_deserialize_item, | |
464 | ||
465 | .active_state = scope_active_state, | |
466 | .sub_state_to_string = scope_sub_state_to_string, | |
467 | ||
468 | .check_gc = scope_check_gc, | |
469 | ||
470 | .timer_event = scope_timer_event, | |
471 | ||
8bcca7e2 LP |
472 | .reset_failed = scope_reset_failed, |
473 | ||
6c12b52e LP |
474 | .notify_cgroup_empty = scope_notify_cgroup_empty_event, |
475 | ||
476 | .bus_interface = "org.freedesktop.systemd1.Scope", | |
477 | .bus_message_handler = bus_scope_message_handler, | |
478 | .bus_set_property = bus_scope_set_property, | |
479 | .bus_commit_properties = bus_scope_commit_properties, | |
480 | ||
481 | .can_transient = true | |
482 | }; |