/src/systemd/src/core/job.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | | |
3 | | #include <errno.h> |
4 | | |
5 | | #include "sd-id128.h" |
6 | | #include "sd-messages.h" |
7 | | |
8 | | #include "alloc-util.h" |
9 | | #include "async.h" |
10 | | #include "dbus-job.h" |
11 | | #include "dbus.h" |
12 | | #include "escape.h" |
13 | | #include "fileio.h" |
14 | | #include "job.h" |
15 | | #include "log.h" |
16 | | #include "macro.h" |
17 | | #include "parse-util.h" |
18 | | #include "serialize.h" |
19 | | #include "set.h" |
20 | | #include "sort-util.h" |
21 | | #include "special.h" |
22 | | #include "stdio-util.h" |
23 | | #include "string-table.h" |
24 | | #include "string-util.h" |
25 | | #include "strv.h" |
26 | | #include "terminal-util.h" |
27 | | #include "unit.h" |
28 | | #include "virt.h" |
29 | | |
30 | 0 | Job* job_new_raw(Unit *unit) { |
31 | 0 | Job *j; |
32 | 0 |
|
33 | 0 | /* used for deserialization */ |
34 | 0 |
|
35 | 0 | assert(unit); |
36 | 0 |
|
37 | 0 | j = new(Job, 1); |
38 | 0 | if (!j) |
39 | 0 | return NULL; |
40 | 0 | |
41 | 0 | *j = (Job) { |
42 | 0 | .manager = unit->manager, |
43 | 0 | .unit = unit, |
44 | 0 | .type = _JOB_TYPE_INVALID, |
45 | 0 | }; |
46 | 0 |
|
47 | 0 | return j; |
48 | 0 | } |
49 | | |
50 | 0 | Job* job_new(Unit *unit, JobType type) { |
51 | 0 | Job *j; |
52 | 0 |
|
53 | 0 | assert(type < _JOB_TYPE_MAX); |
54 | 0 |
|
55 | 0 | j = job_new_raw(unit); |
56 | 0 | if (!j) |
57 | 0 | return NULL; |
58 | 0 | |
59 | 0 | j->id = j->manager->current_job_id++; |
60 | 0 | j->type = type; |
61 | 0 |
|
62 | 0 | /* We don't link it here, that's what job_dependency() is for */ |
63 | 0 |
|
64 | 0 | return j; |
65 | 0 | } |
66 | | |
67 | 0 | void job_unlink(Job *j) { |
68 | 0 | assert(j); |
69 | 0 | assert(!j->installed); |
70 | 0 | assert(!j->transaction_prev); |
71 | 0 | assert(!j->transaction_next); |
72 | 0 | assert(!j->subject_list); |
73 | 0 | assert(!j->object_list); |
74 | 0 |
|
75 | 0 | if (j->in_run_queue) { |
76 | 0 | LIST_REMOVE(run_queue, j->manager->run_queue, j); |
77 | 0 | j->in_run_queue = false; |
78 | 0 | } |
79 | 0 |
|
80 | 0 | if (j->in_dbus_queue) { |
81 | 0 | LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); |
82 | 0 | j->in_dbus_queue = false; |
83 | 0 | } |
84 | 0 |
|
85 | 0 | if (j->in_gc_queue) { |
86 | 0 | LIST_REMOVE(gc_queue, j->manager->gc_job_queue, j); |
87 | 0 | j->in_gc_queue = false; |
88 | 0 | } |
89 | 0 |
|
90 | 0 | j->timer_event_source = sd_event_source_unref(j->timer_event_source); |
91 | 0 | } |
92 | | |
93 | 0 | Job* job_free(Job *j) { |
94 | 0 | assert(j); |
95 | 0 | assert(!j->installed); |
96 | 0 | assert(!j->transaction_prev); |
97 | 0 | assert(!j->transaction_next); |
98 | 0 | assert(!j->subject_list); |
99 | 0 | assert(!j->object_list); |
100 | 0 |
|
101 | 0 | job_unlink(j); |
102 | 0 |
|
103 | 0 | sd_bus_track_unref(j->bus_track); |
104 | 0 | strv_free(j->deserialized_clients); |
105 | 0 |
|
106 | 0 | return mfree(j); |
107 | 0 | } |
108 | | |
109 | 0 | static void job_set_state(Job *j, JobState state) { |
110 | 0 | assert(j); |
111 | 0 | assert(state >= 0); |
112 | 0 | assert(state < _JOB_STATE_MAX); |
113 | 0 |
|
114 | 0 | if (j->state == state) |
115 | 0 | return; |
116 | 0 | |
117 | 0 | j->state = state; |
118 | 0 |
|
119 | 0 | if (!j->installed) |
120 | 0 | return; |
121 | 0 | |
122 | 0 | if (j->state == JOB_RUNNING) |
123 | 0 | j->unit->manager->n_running_jobs++; |
124 | 0 | else { |
125 | 0 | assert(j->state == JOB_WAITING); |
126 | 0 | assert(j->unit->manager->n_running_jobs > 0); |
127 | 0 |
|
128 | 0 | j->unit->manager->n_running_jobs--; |
129 | 0 |
|
130 | 0 | if (j->unit->manager->n_running_jobs <= 0) |
131 | 0 | j->unit->manager->jobs_in_progress_event_source = sd_event_source_unref(j->unit->manager->jobs_in_progress_event_source); |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | 0 | void job_uninstall(Job *j) { |
136 | 0 | Job **pj; |
137 | 0 |
|
138 | 0 | assert(j->installed); |
139 | 0 |
|
140 | 0 | job_set_state(j, JOB_WAITING); |
141 | 0 |
|
142 | 0 | pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; |
143 | 0 | assert(*pj == j); |
144 | 0 |
|
145 | 0 | /* Detach from next 'bigger' objects */ |
146 | 0 |
|
147 | 0 | /* daemon-reload should be transparent to job observers */ |
148 | 0 | if (!MANAGER_IS_RELOADING(j->manager)) |
149 | 0 | bus_job_send_removed_signal(j); |
150 | 0 |
|
151 | 0 | *pj = NULL; |
152 | 0 |
|
153 | 0 | unit_add_to_gc_queue(j->unit); |
154 | 0 |
|
155 | 0 | unit_add_to_dbus_queue(j->unit); /* The Job property of the unit has changed now */ |
156 | 0 |
|
157 | 0 | hashmap_remove_value(j->manager->jobs, UINT32_TO_PTR(j->id), j); |
158 | 0 | j->installed = false; |
159 | 0 | } |
160 | | |
161 | 0 | static bool job_type_allows_late_merge(JobType t) { |
162 | 0 | /* Tells whether it is OK to merge a job of type 't' with an already |
163 | 0 | * running job. |
164 | 0 | * Reloads cannot be merged this way. Think of the sequence: |
165 | 0 | * 1. Reload of a daemon is in progress; the daemon has already loaded |
166 | 0 | * its config file, but hasn't completed the reload operation yet. |
167 | 0 | * 2. Edit foo's config file. |
168 | 0 | * 3. Trigger another reload to have the daemon use the new config. |
169 | 0 | * Should the second reload job be merged into the first one, the daemon |
170 | 0 | * would not know about the new config. |
171 | 0 | * JOB_RESTART jobs on the other hand can be merged, because they get |
172 | 0 | * patched into JOB_START after stopping the unit. So if we see a |
173 | 0 | * JOB_RESTART running, it means the unit hasn't stopped yet and at |
174 | 0 | * this time the merge is still allowed. */ |
175 | 0 | return t != JOB_RELOAD; |
176 | 0 | } |
177 | | |
178 | 0 | static void job_merge_into_installed(Job *j, Job *other) { |
179 | 0 | assert(j->installed); |
180 | 0 | assert(j->unit == other->unit); |
181 | 0 |
|
182 | 0 | if (j->type != JOB_NOP) |
183 | 0 | assert_se(job_type_merge_and_collapse(&j->type, other->type, j->unit) == 0); |
184 | 0 | else |
185 | 0 | assert(other->type == JOB_NOP); |
186 | 0 |
|
187 | 0 | j->irreversible = j->irreversible || other->irreversible; |
188 | 0 | j->ignore_order = j->ignore_order || other->ignore_order; |
189 | 0 | } |
190 | | |
191 | 0 | Job* job_install(Job *j) { |
192 | 0 | Job **pj; |
193 | 0 | Job *uj; |
194 | 0 |
|
195 | 0 | assert(!j->installed); |
196 | 0 | assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); |
197 | 0 | assert(j->state == JOB_WAITING); |
198 | 0 |
|
199 | 0 | pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; |
200 | 0 | uj = *pj; |
201 | 0 |
|
202 | 0 | if (uj) { |
203 | 0 | if (job_type_is_conflicting(uj->type, j->type)) |
204 | 0 | job_finish_and_invalidate(uj, JOB_CANCELED, false, false); |
205 | 0 | else { |
206 | 0 | /* not conflicting, i.e. mergeable */ |
207 | 0 |
|
208 | 0 | if (uj->state == JOB_WAITING || |
209 | 0 | (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) { |
210 | 0 | job_merge_into_installed(uj, j); |
211 | 0 | log_unit_debug(uj->unit, |
212 | 0 | "Merged %s/%s into installed job %s/%s as %"PRIu32, |
213 | 0 | j->unit->id, job_type_to_string(j->type), uj->unit->id, |
214 | 0 | job_type_to_string(uj->type), uj->id); |
215 | 0 | return uj; |
216 | 0 | } else { |
217 | 0 | /* already running and not safe to merge into */ |
218 | 0 | /* Patch uj to become a merged job and re-run it. */ |
219 | 0 | /* XXX It should be safer to queue j to run after uj finishes, but it is |
220 | 0 | * not currently possible to have more than one installed job per unit. */ |
221 | 0 | job_merge_into_installed(uj, j); |
222 | 0 | log_unit_debug(uj->unit, |
223 | 0 | "Merged into running job, re-running: %s/%s as %"PRIu32, |
224 | 0 | uj->unit->id, job_type_to_string(uj->type), uj->id); |
225 | 0 |
|
226 | 0 | job_set_state(uj, JOB_WAITING); |
227 | 0 | return uj; |
228 | 0 | } |
229 | 0 | } |
230 | 0 | } |
231 | 0 |
|
232 | 0 | /* Install the job */ |
233 | 0 | *pj = j; |
234 | 0 | j->installed = true; |
235 | 0 |
|
236 | 0 | j->manager->n_installed_jobs++; |
237 | 0 | log_unit_debug(j->unit, |
238 | 0 | "Installed new job %s/%s as %u", |
239 | 0 | j->unit->id, job_type_to_string(j->type), (unsigned) j->id); |
240 | 0 |
|
241 | 0 | job_add_to_gc_queue(j); |
242 | 0 |
|
243 | 0 | job_add_to_dbus_queue(j); /* announce this job to clients */ |
244 | 0 | unit_add_to_dbus_queue(j->unit); /* The Job property of the unit has changed now */ |
245 | 0 |
|
246 | 0 | return j; |
247 | 0 | } |
248 | | |
249 | 0 | int job_install_deserialized(Job *j) { |
250 | 0 | Job **pj; |
251 | 0 | int r; |
252 | 0 |
|
253 | 0 | assert(!j->installed); |
254 | 0 |
|
255 | 0 | if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) |
256 | 0 | return log_unit_debug_errno(j->unit, SYNTHETIC_ERRNO(EINVAL), |
257 | 0 | "Invalid job type %s in deserialization.", |
258 | 0 | strna(job_type_to_string(j->type))); |
259 | 0 | |
260 | 0 | pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; |
261 | 0 | if (*pj) |
262 | 0 | return log_unit_debug_errno(j->unit, SYNTHETIC_ERRNO(EEXIST), |
263 | 0 | "Unit already has a job installed. Not installing deserialized job."); |
264 | 0 | |
265 | 0 | r = hashmap_put(j->manager->jobs, UINT32_TO_PTR(j->id), j); |
266 | 0 | if (r == -EEXIST) |
267 | 0 | return log_unit_debug_errno(j->unit, r, "Job ID %" PRIu32 " already used, cannot deserialize job.", j->id); |
268 | 0 | if (r < 0) |
269 | 0 | return log_unit_debug_errno(j->unit, r, "Failed to insert job into jobs hash table: %m"); |
270 | 0 | |
271 | 0 | *pj = j; |
272 | 0 | j->installed = true; |
273 | 0 |
|
274 | 0 | if (j->state == JOB_RUNNING) |
275 | 0 | j->unit->manager->n_running_jobs++; |
276 | 0 |
|
277 | 0 | log_unit_debug(j->unit, |
278 | 0 | "Reinstalled deserialized job %s/%s as %u", |
279 | 0 | j->unit->id, job_type_to_string(j->type), (unsigned) j->id); |
280 | 0 | return 0; |
281 | 0 | } |
282 | | |
283 | 0 | JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { |
284 | 0 | JobDependency *l; |
285 | 0 |
|
286 | 0 | assert(object); |
287 | 0 |
|
288 | 0 | /* Adds a new job link, which encodes that the 'subject' job |
289 | 0 | * needs the 'object' job in some way. If 'subject' is NULL |
290 | 0 | * this means the 'anchor' job (i.e. the one the user |
291 | 0 | * explicitly asked for) is the requester. */ |
292 | 0 |
|
293 | 0 | l = new0(JobDependency, 1); |
294 | 0 | if (!l) |
295 | 0 | return NULL; |
296 | 0 | |
297 | 0 | l->subject = subject; |
298 | 0 | l->object = object; |
299 | 0 | l->matters = matters; |
300 | 0 | l->conflicts = conflicts; |
301 | 0 |
|
302 | 0 | if (subject) |
303 | 0 | LIST_PREPEND(subject, subject->subject_list, l); |
304 | 0 |
|
305 | 0 | LIST_PREPEND(object, object->object_list, l); |
306 | 0 |
|
307 | 0 | return l; |
308 | 0 | } |
309 | | |
310 | 0 | void job_dependency_free(JobDependency *l) { |
311 | 0 | assert(l); |
312 | 0 |
|
313 | 0 | if (l->subject) |
314 | 0 | LIST_REMOVE(subject, l->subject->subject_list, l); |
315 | 0 |
|
316 | 0 | LIST_REMOVE(object, l->object->object_list, l); |
317 | 0 |
|
318 | 0 | free(l); |
319 | 0 | } |
320 | | |
321 | 0 | void job_dump(Job *j, FILE *f, const char *prefix) { |
322 | 0 | assert(j); |
323 | 0 | assert(f); |
324 | 0 |
|
325 | 0 | prefix = strempty(prefix); |
326 | 0 |
|
327 | 0 | fprintf(f, |
328 | 0 | "%s-> Job %u:\n" |
329 | 0 | "%s\tAction: %s -> %s\n" |
330 | 0 | "%s\tState: %s\n" |
331 | 0 | "%s\tIrreversible: %s\n" |
332 | 0 | "%s\tMay GC: %s\n", |
333 | 0 | prefix, j->id, |
334 | 0 | prefix, j->unit->id, job_type_to_string(j->type), |
335 | 0 | prefix, job_state_to_string(j->state), |
336 | 0 | prefix, yes_no(j->irreversible), |
337 | 0 | prefix, yes_no(job_may_gc(j))); |
338 | 0 | } |
339 | | |
340 | | /* |
341 | | * Merging is commutative, so imagine the matrix as symmetric. We store only |
342 | | * its lower triangle to avoid duplication. We don't store the main diagonal, |
343 | | * because A merged with A is simply A. |
344 | | * |
345 | | * If the resulting type is collapsed immediately afterwards (to get rid of |
346 | | * the JOB_RELOAD_OR_START, which lies outside the lookup function's domain), |
347 | | * the following properties hold: |
348 | | * |
349 | | * Merging is associative! A merged with B, and then merged with C is the same |
350 | | * as A merged with the result of B merged with C. |
351 | | * |
352 | | * Mergeability is transitive! If A can be merged with B and B with C then |
353 | | * A also with C. |
354 | | * |
355 | | * Also, if A merged with B cannot be merged with C, then either A or B cannot |
356 | | * be merged with C either. |
357 | | */ |
358 | | static const JobType job_merging_table[] = { |
359 | | /* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */ |
360 | | /*********************************************************************************/ |
361 | | /*JOB_START */ |
362 | | /*JOB_VERIFY_ACTIVE */ JOB_START, |
363 | | /*JOB_STOP */ -1, -1, |
364 | | /*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, |
365 | | /*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, |
366 | | }; |
367 | | |
368 | 0 | JobType job_type_lookup_merge(JobType a, JobType b) { |
369 | 0 | assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2); |
370 | 0 | assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING); |
371 | 0 | assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING); |
372 | 0 |
|
373 | 0 | if (a == b) |
374 | 0 | return a; |
375 | 0 | |
376 | 0 | if (a < b) { |
377 | 0 | JobType tmp = a; |
378 | 0 | a = b; |
379 | 0 | b = tmp; |
380 | 0 | } |
381 | 0 |
|
382 | 0 | return job_merging_table[(a - 1) * a / 2 + b]; |
383 | 0 | } |
384 | | |
385 | 0 | bool job_type_is_redundant(JobType a, UnitActiveState b) { |
386 | 0 | switch (a) { |
387 | 0 |
|
388 | 0 | case JOB_START: |
389 | 0 | return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING); |
390 | 0 |
|
391 | 0 | case JOB_STOP: |
392 | 0 | return IN_SET(b, UNIT_INACTIVE, UNIT_FAILED); |
393 | 0 |
|
394 | 0 | case JOB_VERIFY_ACTIVE: |
395 | 0 | return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING); |
396 | 0 |
|
397 | 0 | case JOB_RELOAD: |
398 | 0 | return |
399 | 0 | b == UNIT_RELOADING; |
400 | 0 |
|
401 | 0 | case JOB_RESTART: |
402 | 0 | return |
403 | 0 | b == UNIT_ACTIVATING; |
404 | 0 |
|
405 | 0 | case JOB_NOP: |
406 | 0 | return true; |
407 | 0 |
|
408 | 0 | default: |
409 | 0 | assert_not_reached("Invalid job type"); |
410 | 0 | } |
411 | 0 | } |
412 | | |
413 | 0 | JobType job_type_collapse(JobType t, Unit *u) { |
414 | 0 | UnitActiveState s; |
415 | 0 |
|
416 | 0 | switch (t) { |
417 | 0 |
|
418 | 0 | case JOB_TRY_RESTART: |
419 | 0 | s = unit_active_state(u); |
420 | 0 | if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) |
421 | 0 | return JOB_NOP; |
422 | 0 | |
423 | 0 | return JOB_RESTART; |
424 | 0 |
|
425 | 0 | case JOB_TRY_RELOAD: |
426 | 0 | s = unit_active_state(u); |
427 | 0 | if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) |
428 | 0 | return JOB_NOP; |
429 | 0 | |
430 | 0 | return JOB_RELOAD; |
431 | 0 |
|
432 | 0 | case JOB_RELOAD_OR_START: |
433 | 0 | s = unit_active_state(u); |
434 | 0 | if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) |
435 | 0 | return JOB_START; |
436 | 0 | |
437 | 0 | return JOB_RELOAD; |
438 | 0 |
|
439 | 0 | default: |
440 | 0 | return t; |
441 | 0 | } |
442 | 0 | } |
443 | | |
444 | 0 | int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { |
445 | 0 | JobType t; |
446 | 0 |
|
447 | 0 | t = job_type_lookup_merge(*a, b); |
448 | 0 | if (t < 0) |
449 | 0 | return -EEXIST; |
450 | 0 | |
451 | 0 | *a = job_type_collapse(t, u); |
452 | 0 | return 0; |
453 | 0 | } |
454 | | |
455 | 0 | static bool job_is_runnable(Job *j) { |
456 | 0 | Iterator i; |
457 | 0 | Unit *other; |
458 | 0 | void *v; |
459 | 0 |
|
460 | 0 | assert(j); |
461 | 0 | assert(j->installed); |
462 | 0 |
|
463 | 0 | /* Checks whether there is any job running for the units this |
464 | 0 | * job needs to be running after (in the case of a 'positive' |
465 | 0 | * job type) or before (in the case of a 'negative' job |
466 | 0 | * type. */ |
467 | 0 |
|
468 | 0 | /* Note that unit types have a say in what is runnable, |
469 | 0 | * too. For example, if they return -EAGAIN from |
470 | 0 | * unit_start() they can indicate they are not |
471 | 0 | * runnable yet. */ |
472 | 0 |
|
473 | 0 | /* First check if there is an override */ |
474 | 0 | if (j->ignore_order) |
475 | 0 | return true; |
476 | 0 | |
477 | 0 | if (j->type == JOB_NOP) |
478 | 0 | return true; |
479 | 0 | |
480 | 0 | if (IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) { |
481 | 0 | /* Immediate result is that the job is or might be |
482 | 0 | * started. In this case let's wait for the |
483 | 0 | * dependencies, regardless whether they are |
484 | 0 | * starting or stopping something. */ |
485 | 0 |
|
486 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) |
487 | 0 | if (other->job) |
488 | 0 | return false; |
489 | 0 | } |
490 | 0 |
|
491 | 0 | /* Also, if something else is being stopped and we should |
492 | 0 | * change state after it, then let's wait. */ |
493 | 0 |
|
494 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i) |
495 | 0 | if (other->job && |
496 | 0 | IN_SET(other->job->type, JOB_STOP, JOB_RESTART)) |
497 | 0 | return false; |
498 | 0 |
|
499 | 0 | /* This means that for a service a and a service b where b |
500 | 0 | * shall be started after a: |
501 | 0 | * |
502 | 0 | * start a + start b → 1st step start a, 2nd step start b |
503 | 0 | * start a + stop b → 1st step stop b, 2nd step start a |
504 | 0 | * stop a + start b → 1st step stop a, 2nd step start b |
505 | 0 | * stop a + stop b → 1st step stop b, 2nd step stop a |
506 | 0 | * |
507 | 0 | * This has the side effect that restarts are properly |
508 | 0 | * synchronized too. */ |
509 | 0 |
|
510 | 0 | return true; |
511 | 0 | } |
512 | | |
513 | 0 | static void job_change_type(Job *j, JobType newtype) { |
514 | 0 | assert(j); |
515 | 0 |
|
516 | 0 | log_unit_debug(j->unit, |
517 | 0 | "Converting job %s/%s -> %s/%s", |
518 | 0 | j->unit->id, job_type_to_string(j->type), |
519 | 0 | j->unit->id, job_type_to_string(newtype)); |
520 | 0 |
|
521 | 0 | j->type = newtype; |
522 | 0 | } |
523 | | |
524 | 0 | _pure_ static const char* job_get_begin_status_message_format(Unit *u, JobType t) { |
525 | 0 | const char *format; |
526 | 0 |
|
527 | 0 | assert(u); |
528 | 0 |
|
529 | 0 | if (t == JOB_RELOAD) |
530 | 0 | return "Reloading %s."; |
531 | 0 | |
532 | 0 | assert(IN_SET(t, JOB_START, JOB_STOP)); |
533 | 0 |
|
534 | 0 | format = UNIT_VTABLE(u)->status_message_formats.starting_stopping[t == JOB_STOP]; |
535 | 0 | if (format) |
536 | 0 | return format; |
537 | 0 | |
538 | 0 | /* Return generic strings */ |
539 | 0 | if (t == JOB_START) |
540 | 0 | return "Starting %s."; |
541 | 0 | else { |
542 | 0 | assert(t == JOB_STOP); |
543 | 0 | return "Stopping %s."; |
544 | 0 | } |
545 | 0 | } |
546 | | |
547 | 0 | static void job_print_begin_status_message(Unit *u, JobType t) { |
548 | 0 | const char *format; |
549 | 0 |
|
550 | 0 | assert(u); |
551 | 0 |
|
552 | 0 | /* Reload status messages have traditionally not been printed to console. */ |
553 | 0 | if (!IN_SET(t, JOB_START, JOB_STOP)) |
554 | 0 | return; |
555 | 0 | |
556 | 0 | format = job_get_begin_status_message_format(u, t); |
557 | 0 |
|
558 | 0 | DISABLE_WARNING_FORMAT_NONLITERAL; |
559 | 0 | unit_status_printf(u, "", format); |
560 | 0 | REENABLE_WARNING; |
561 | 0 | } |
562 | | |
563 | 0 | static void job_log_begin_status_message(Unit *u, uint32_t job_id, JobType t) { |
564 | 0 | const char *format, *mid; |
565 | 0 | char buf[LINE_MAX]; |
566 | 0 |
|
567 | 0 | assert(u); |
568 | 0 | assert(t >= 0); |
569 | 0 | assert(t < _JOB_TYPE_MAX); |
570 | 0 |
|
571 | 0 | if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) |
572 | 0 | return; |
573 | 0 | |
574 | 0 | if (log_on_console()) /* Skip this if it would only go on the console anyway */ |
575 | 0 | return; |
576 | 0 | |
577 | 0 | /* We log status messages for all units and all operations. */ |
578 | 0 | |
579 | 0 | format = job_get_begin_status_message_format(u, t); |
580 | 0 |
|
581 | 0 | DISABLE_WARNING_FORMAT_NONLITERAL; |
582 | 0 | (void) snprintf(buf, sizeof buf, format, unit_description(u)); |
583 | 0 | REENABLE_WARNING; |
584 | 0 |
|
585 | 0 | mid = t == JOB_START ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTING_STR : |
586 | 0 | t == JOB_STOP ? "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPING_STR : |
587 | 0 | "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADING_STR; |
588 | 0 |
|
589 | 0 | /* Note that we deliberately use LOG_MESSAGE() instead of |
590 | 0 | * LOG_UNIT_MESSAGE() here, since this is supposed to mimic |
591 | 0 | * closely what is written to screen using the status output, |
592 | 0 | * which is supposed the highest level, friendliest output |
593 | 0 | * possible, which means we should avoid the low-level unit |
594 | 0 | * name. */ |
595 | 0 | log_struct(LOG_INFO, |
596 | 0 | LOG_MESSAGE("%s", buf), |
597 | 0 | "JOB_ID=%" PRIu32, job_id, |
598 | 0 | "JOB_TYPE=%s", job_type_to_string(t), |
599 | 0 | LOG_UNIT_ID(u), |
600 | 0 | LOG_UNIT_INVOCATION_ID(u), |
601 | 0 | mid); |
602 | 0 | } |
603 | | |
604 | 0 | static void job_emit_begin_status_message(Unit *u, uint32_t job_id, JobType t) { |
605 | 0 | assert(u); |
606 | 0 | assert(t >= 0); |
607 | 0 | assert(t < _JOB_TYPE_MAX); |
608 | 0 |
|
609 | 0 | job_log_begin_status_message(u, job_id, t); |
610 | 0 | job_print_begin_status_message(u, t); |
611 | 0 | } |
612 | | |
613 | 0 | static int job_perform_on_unit(Job **j) { |
614 | 0 | uint32_t id; |
615 | 0 | Manager *m; |
616 | 0 | JobType t; |
617 | 0 | Unit *u; |
618 | 0 | int r; |
619 | 0 |
|
620 | 0 | /* While we execute this operation the job might go away (for |
621 | 0 | * example: because it finishes immediately or is replaced by |
622 | 0 | * a new, conflicting job.) To make sure we don't access a |
623 | 0 | * freed job later on we store the id here, so that we can |
624 | 0 | * verify the job is still valid. */ |
625 | 0 |
|
626 | 0 | assert(j); |
627 | 0 | assert(*j); |
628 | 0 |
|
629 | 0 | m = (*j)->manager; |
630 | 0 | u = (*j)->unit; |
631 | 0 | t = (*j)->type; |
632 | 0 | id = (*j)->id; |
633 | 0 |
|
634 | 0 | switch (t) { |
635 | 0 | case JOB_START: |
636 | 0 | r = unit_start(u); |
637 | 0 | break; |
638 | 0 |
|
639 | 0 | case JOB_RESTART: |
640 | 0 | t = JOB_STOP; |
641 | 0 | _fallthrough_; |
642 | 0 | case JOB_STOP: |
643 | 0 | r = unit_stop(u); |
644 | 0 | break; |
645 | 0 |
|
646 | 0 | case JOB_RELOAD: |
647 | 0 | r = unit_reload(u); |
648 | 0 | break; |
649 | 0 |
|
650 | 0 | default: |
651 | 0 | assert_not_reached("Invalid job type"); |
652 | 0 | } |
653 | 0 |
|
654 | 0 | /* Log if the job still exists and the start/stop/reload function actually did something. Note that this means |
655 | 0 | * for units for which there's no 'activating' phase (i.e. because we transition directly from 'inactive' to |
656 | 0 | * 'active') we'll possibly skip the "Starting..." message. */ |
657 | 0 | *j = manager_get_job(m, id); |
658 | 0 | if (*j && r > 0) |
659 | 0 | job_emit_begin_status_message(u, id, t); |
660 | 0 |
|
661 | 0 | return r; |
662 | 0 | } |
663 | | |
664 | 0 | int job_run_and_invalidate(Job *j) { |
665 | 0 | int r; |
666 | 0 |
|
667 | 0 | assert(j); |
668 | 0 | assert(j->installed); |
669 | 0 | assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); |
670 | 0 | assert(j->in_run_queue); |
671 | 0 |
|
672 | 0 | LIST_REMOVE(run_queue, j->manager->run_queue, j); |
673 | 0 | j->in_run_queue = false; |
674 | 0 |
|
675 | 0 | if (j->state != JOB_WAITING) |
676 | 0 | return 0; |
677 | 0 | |
678 | 0 | if (!job_is_runnable(j)) |
679 | 0 | return -EAGAIN; |
680 | 0 | |
681 | 0 | job_start_timer(j, true); |
682 | 0 | job_set_state(j, JOB_RUNNING); |
683 | 0 | job_add_to_dbus_queue(j); |
684 | 0 |
|
685 | 0 | switch (j->type) { |
686 | 0 |
|
687 | 0 | case JOB_VERIFY_ACTIVE: { |
688 | 0 | UnitActiveState t; |
689 | 0 |
|
690 | 0 | t = unit_active_state(j->unit); |
691 | 0 | if (UNIT_IS_ACTIVE_OR_RELOADING(t)) |
692 | 0 | r = -EALREADY; |
693 | 0 | else if (t == UNIT_ACTIVATING) |
694 | 0 | r = -EAGAIN; |
695 | 0 | else |
696 | 0 | r = -EBADR; |
697 | 0 | break; |
698 | 0 | } |
699 | 0 |
|
700 | 0 | case JOB_START: |
701 | 0 | case JOB_STOP: |
702 | 0 | case JOB_RESTART: |
703 | 0 | r = job_perform_on_unit(&j); |
704 | 0 |
|
705 | 0 | /* If the unit type does not support starting/stopping, then simply wait. */ |
706 | 0 | if (r == -EBADR) |
707 | 0 | r = 0; |
708 | 0 | break; |
709 | 0 |
|
710 | 0 | case JOB_RELOAD: |
711 | 0 | r = job_perform_on_unit(&j); |
712 | 0 | break; |
713 | 0 |
|
714 | 0 | case JOB_NOP: |
715 | 0 | r = -EALREADY; |
716 | 0 | break; |
717 | 0 |
|
718 | 0 | default: |
719 | 0 | assert_not_reached("Unknown job type"); |
720 | 0 | } |
721 | 0 |
|
722 | 0 | if (j) { |
723 | 0 | if (r == -EAGAIN) |
724 | 0 | job_set_state(j, JOB_WAITING); /* Hmm, not ready after all, let's return to JOB_WAITING state */ |
725 | 0 | else if (r == -EALREADY) /* already being executed */ |
726 | 0 | r = job_finish_and_invalidate(j, JOB_DONE, true, true); |
727 | 0 | else if (r == -ECOMM) /* condition failed, but all is good */ |
728 | 0 | r = job_finish_and_invalidate(j, JOB_DONE, true, false); |
729 | 0 | else if (r == -EBADR) |
730 | 0 | r = job_finish_and_invalidate(j, JOB_SKIPPED, true, false); |
731 | 0 | else if (r == -ENOEXEC) |
732 | 0 | r = job_finish_and_invalidate(j, JOB_INVALID, true, false); |
733 | 0 | else if (r == -EPROTO) |
734 | 0 | r = job_finish_and_invalidate(j, JOB_ASSERT, true, false); |
735 | 0 | else if (r == -EOPNOTSUPP) |
736 | 0 | r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true, false); |
737 | 0 | else if (r == -ENOLINK) |
738 | 0 | r = job_finish_and_invalidate(j, JOB_DEPENDENCY, true, false); |
739 | 0 | else if (r == -ESTALE) |
740 | 0 | r = job_finish_and_invalidate(j, JOB_ONCE, true, false); |
741 | 0 | else if (r < 0) |
742 | 0 | r = job_finish_and_invalidate(j, JOB_FAILED, true, false); |
743 | 0 | } |
744 | 0 |
|
745 | 0 | return r; |
746 | 0 | } |
747 | | |
748 | 0 | _pure_ static const char *job_get_done_status_message_format(Unit *u, JobType t, JobResult result) { |
749 | 0 |
|
750 | 0 | static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { |
751 | 0 | [JOB_DONE] = "Started %s.", |
752 | 0 | [JOB_TIMEOUT] = "Timed out starting %s.", |
753 | 0 | [JOB_FAILED] = "Failed to start %s.", |
754 | 0 | [JOB_DEPENDENCY] = "Dependency failed for %s.", |
755 | 0 | [JOB_ASSERT] = "Assertion failed for %s.", |
756 | 0 | [JOB_UNSUPPORTED] = "Starting of %s not supported.", |
757 | 0 | [JOB_COLLECTED] = "Unnecessary job for %s was removed.", |
758 | 0 | [JOB_ONCE] = "Unit %s has been started before and cannot be started again." |
759 | 0 | }; |
760 | 0 | static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { |
761 | 0 | [JOB_DONE] = "Stopped %s.", |
762 | 0 | [JOB_FAILED] = "Stopped (with error) %s.", |
763 | 0 | [JOB_TIMEOUT] = "Timed out stopping %s.", |
764 | 0 | }; |
765 | 0 | static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = { |
766 | 0 | [JOB_DONE] = "Reloaded %s.", |
767 | 0 | [JOB_FAILED] = "Reload failed for %s.", |
768 | 0 | [JOB_TIMEOUT] = "Timed out reloading %s.", |
769 | 0 | }; |
770 | 0 | /* When verify-active detects the unit is inactive, report it. |
771 | 0 | * Most likely a DEPEND warning from a requisiting unit will |
772 | 0 | * occur next and it's nice to see what was requisited. */ |
773 | 0 | static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = { |
774 | 0 | [JOB_SKIPPED] = "%s is not active.", |
775 | 0 | }; |
776 | 0 |
|
777 | 0 | const char *format; |
778 | 0 |
|
779 | 0 | assert(u); |
780 | 0 | assert(t >= 0); |
781 | 0 | assert(t < _JOB_TYPE_MAX); |
782 | 0 |
|
783 | 0 | if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) { |
784 | 0 | format = t == JOB_START ? |
785 | 0 | UNIT_VTABLE(u)->status_message_formats.finished_start_job[result] : |
786 | 0 | UNIT_VTABLE(u)->status_message_formats.finished_stop_job[result]; |
787 | 0 | if (format) |
788 | 0 | return format; |
789 | 0 | } |
790 | 0 | |
791 | 0 | /* Return generic strings */ |
792 | 0 | if (t == JOB_START) |
793 | 0 | return generic_finished_start_job[result]; |
794 | 0 | else if (IN_SET(t, JOB_STOP, JOB_RESTART)) |
795 | 0 | return generic_finished_stop_job[result]; |
796 | 0 | else if (t == JOB_RELOAD) |
797 | 0 | return generic_finished_reload_job[result]; |
798 | 0 | else if (t == JOB_VERIFY_ACTIVE) |
799 | 0 | return generic_finished_verify_active_job[result]; |
800 | 0 | |
801 | 0 | return NULL; |
802 | 0 | } |
803 | | |
804 | | static const struct { |
805 | | const char *color, *word; |
806 | | } job_print_done_status_messages[_JOB_RESULT_MAX] = { |
807 | | [JOB_DONE] = { ANSI_OK_COLOR, " OK " }, |
808 | | [JOB_TIMEOUT] = { ANSI_HIGHLIGHT_RED, " TIME " }, |
809 | | [JOB_FAILED] = { ANSI_HIGHLIGHT_RED, "FAILED" }, |
810 | | [JOB_DEPENDENCY] = { ANSI_HIGHLIGHT_YELLOW, "DEPEND" }, |
811 | | [JOB_SKIPPED] = { ANSI_HIGHLIGHT, " INFO " }, |
812 | | [JOB_ASSERT] = { ANSI_HIGHLIGHT_YELLOW, "ASSERT" }, |
813 | | [JOB_UNSUPPORTED] = { ANSI_HIGHLIGHT_YELLOW, "UNSUPP" }, |
814 | | /* JOB_COLLECTED */ |
815 | | [JOB_ONCE] = { ANSI_HIGHLIGHT_RED, " ONCE " }, |
816 | | }; |
817 | | |
818 | 0 | static void job_print_done_status_message(Unit *u, JobType t, JobResult result) { |
819 | 0 | const char *format; |
820 | 0 | const char *status; |
821 | 0 |
|
822 | 0 | assert(u); |
823 | 0 | assert(t >= 0); |
824 | 0 | assert(t < _JOB_TYPE_MAX); |
825 | 0 |
|
826 | 0 | /* Reload status messages have traditionally not been printed to console. */ |
827 | 0 | if (t == JOB_RELOAD) |
828 | 0 | return; |
829 | 0 | |
830 | 0 | /* No message if the job did not actually do anything due to failed condition. */ |
831 | 0 | if (t == JOB_START && result == JOB_DONE && !u->condition_result) |
832 | 0 | return; |
833 | 0 | |
834 | 0 | if (!job_print_done_status_messages[result].word) |
835 | 0 | return; |
836 | 0 | |
837 | 0 | format = job_get_done_status_message_format(u, t, result); |
838 | 0 | if (!format) |
839 | 0 | return; |
840 | 0 | |
841 | 0 | if (log_get_show_color()) |
842 | 0 | status = strjoina(job_print_done_status_messages[result].color, |
843 | 0 | job_print_done_status_messages[result].word, |
844 | 0 | ANSI_NORMAL); |
845 | 0 | else |
846 | 0 | status = job_print_done_status_messages[result].word; |
847 | 0 |
|
848 | 0 | if (result != JOB_DONE) |
849 | 0 | manager_flip_auto_status(u->manager, true); |
850 | 0 |
|
851 | 0 | DISABLE_WARNING_FORMAT_NONLITERAL; |
852 | 0 | unit_status_printf(u, status, format); |
853 | 0 | REENABLE_WARNING; |
854 | 0 |
|
855 | 0 | if (t == JOB_START && result == JOB_FAILED) { |
856 | 0 | _cleanup_free_ char *quoted; |
857 | 0 |
|
858 | 0 | quoted = shell_maybe_quote(u->id, ESCAPE_BACKSLASH); |
859 | 0 | manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); |
860 | 0 | } |
861 | 0 | } |
862 | | |
863 | 0 | static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) { |
864 | 0 | const char *format, *mid; |
865 | 0 | char buf[LINE_MAX]; |
866 | 0 | static const int job_result_log_level[_JOB_RESULT_MAX] = { |
867 | 0 | [JOB_DONE] = LOG_INFO, |
868 | 0 | [JOB_CANCELED] = LOG_INFO, |
869 | 0 | [JOB_TIMEOUT] = LOG_ERR, |
870 | 0 | [JOB_FAILED] = LOG_ERR, |
871 | 0 | [JOB_DEPENDENCY] = LOG_WARNING, |
872 | 0 | [JOB_SKIPPED] = LOG_NOTICE, |
873 | 0 | [JOB_INVALID] = LOG_INFO, |
874 | 0 | [JOB_ASSERT] = LOG_WARNING, |
875 | 0 | [JOB_UNSUPPORTED] = LOG_WARNING, |
876 | 0 | [JOB_COLLECTED] = LOG_INFO, |
877 | 0 | [JOB_ONCE] = LOG_ERR, |
878 | 0 | }; |
879 | 0 |
|
880 | 0 | assert(u); |
881 | 0 | assert(t >= 0); |
882 | 0 | assert(t < _JOB_TYPE_MAX); |
883 | 0 |
|
884 | 0 | /* Skip printing if output goes to the console, and job_print_status_message() |
885 | 0 | will actually print something to the console. */ |
886 | 0 | if (log_on_console() && job_print_done_status_messages[result].word) |
887 | 0 | return; |
888 | 0 | |
889 | 0 | /* Show condition check message if the job did not actually do anything due to failed condition. */ |
890 | 0 | if (t == JOB_START && result == JOB_DONE && !u->condition_result) { |
891 | 0 | log_struct(LOG_INFO, |
892 | 0 | "MESSAGE=Condition check resulted in %s being skipped.", unit_description(u), |
893 | 0 | "JOB_ID=%" PRIu32, job_id, |
894 | 0 | "JOB_TYPE=%s", job_type_to_string(t), |
895 | 0 | "JOB_RESULT=%s", job_result_to_string(result), |
896 | 0 | LOG_UNIT_ID(u), |
897 | 0 | LOG_UNIT_INVOCATION_ID(u), |
898 | 0 | "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTED_STR); |
899 | 0 |
|
900 | 0 | return; |
901 | 0 | } |
902 | 0 |
|
903 | 0 | format = job_get_done_status_message_format(u, t, result); |
904 | 0 | if (!format) |
905 | 0 | return; |
906 | 0 | |
907 | 0 | /* The description might be longer than the buffer, but that's OK, |
908 | 0 | * we'll just truncate it here. Note that we use snprintf() rather than |
909 | 0 | * xsprintf() on purpose here: we are fine with truncation and don't |
910 | 0 | * consider that an error. */ |
911 | 0 | DISABLE_WARNING_FORMAT_NONLITERAL; |
912 | 0 | (void) snprintf(buf, sizeof(buf), format, unit_description(u)); |
913 | 0 | REENABLE_WARNING; |
914 | 0 |
|
915 | 0 | switch (t) { |
916 | 0 |
|
917 | 0 | case JOB_START: |
918 | 0 | if (result == JOB_DONE) |
919 | 0 | mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_STARTED_STR; |
920 | 0 | else |
921 | 0 | mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_FAILED_STR; |
922 | 0 | break; |
923 | 0 |
|
924 | 0 | case JOB_RELOAD: |
925 | 0 | mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_RELOADED_STR; |
926 | 0 | break; |
927 | 0 |
|
928 | 0 | case JOB_STOP: |
929 | 0 | case JOB_RESTART: |
930 | 0 | mid = "MESSAGE_ID=" SD_MESSAGE_UNIT_STOPPED_STR; |
931 | 0 | break; |
932 | 0 |
|
933 | 0 | default: |
934 | 0 | log_struct(job_result_log_level[result], |
935 | 0 | LOG_MESSAGE("%s", buf), |
936 | 0 | "JOB_ID=%" PRIu32, job_id, |
937 | 0 | "JOB_TYPE=%s", job_type_to_string(t), |
938 | 0 | "JOB_RESULT=%s", job_result_to_string(result), |
939 | 0 | LOG_UNIT_ID(u), |
940 | 0 | LOG_UNIT_INVOCATION_ID(u)); |
941 | 0 | return; |
942 | 0 | } |
943 | 0 |
|
944 | 0 | log_struct(job_result_log_level[result], |
945 | 0 | LOG_MESSAGE("%s", buf), |
946 | 0 | "JOB_ID=%" PRIu32, job_id, |
947 | 0 | "JOB_TYPE=%s", job_type_to_string(t), |
948 | 0 | "JOB_RESULT=%s", job_result_to_string(result), |
949 | 0 | LOG_UNIT_ID(u), |
950 | 0 | LOG_UNIT_INVOCATION_ID(u), |
951 | 0 | mid); |
952 | 0 | } |
953 | | |
954 | 0 | static void job_emit_done_status_message(Unit *u, uint32_t job_id, JobType t, JobResult result) { |
955 | 0 | assert(u); |
956 | 0 |
|
957 | 0 | job_log_done_status_message(u, job_id, t, result); |
958 | 0 | job_print_done_status_message(u, t, result); |
959 | 0 | } |
960 | | |
961 | 0 | static void job_fail_dependencies(Unit *u, UnitDependency d) { |
962 | 0 | Unit *other; |
963 | 0 | Iterator i; |
964 | 0 | void *v; |
965 | 0 |
|
966 | 0 | assert(u); |
967 | 0 |
|
968 | 0 | HASHMAP_FOREACH_KEY(v, other, u->dependencies[d], i) { |
969 | 0 | Job *j = other->job; |
970 | 0 |
|
971 | 0 | if (!j) |
972 | 0 | continue; |
973 | 0 | if (!IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE)) |
974 | 0 | continue; |
975 | 0 | |
976 | 0 | job_finish_and_invalidate(j, JOB_DEPENDENCY, true, false); |
977 | 0 | } |
978 | 0 | } |
979 | | |
980 | 0 | int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already) { |
981 | 0 | Unit *u; |
982 | 0 | Unit *other; |
983 | 0 | JobType t; |
984 | 0 | Iterator i; |
985 | 0 | void *v; |
986 | 0 |
|
987 | 0 | assert(j); |
988 | 0 | assert(j->installed); |
989 | 0 | assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); |
990 | 0 |
|
991 | 0 | u = j->unit; |
992 | 0 | t = j->type; |
993 | 0 |
|
994 | 0 | j->result = result; |
995 | 0 |
|
996 | 0 | log_unit_debug(u, "Job %" PRIu32 " %s/%s finished, result=%s", j->id, u->id, job_type_to_string(t), job_result_to_string(result)); |
997 | 0 |
|
998 | 0 | /* If this job did nothing to respective unit we don't log the status message */ |
999 | 0 | if (!already) |
1000 | 0 | job_emit_done_status_message(u, j->id, t, result); |
1001 | 0 |
|
1002 | 0 | /* Patch restart jobs so that they become normal start jobs */ |
1003 | 0 | if (result == JOB_DONE && t == JOB_RESTART) { |
1004 | 0 |
|
1005 | 0 | job_change_type(j, JOB_START); |
1006 | 0 | job_set_state(j, JOB_WAITING); |
1007 | 0 |
|
1008 | 0 | job_add_to_dbus_queue(j); |
1009 | 0 | job_add_to_run_queue(j); |
1010 | 0 | job_add_to_gc_queue(j); |
1011 | 0 |
|
1012 | 0 | goto finish; |
1013 | 0 | } |
1014 | 0 | |
1015 | 0 | if (IN_SET(result, JOB_FAILED, JOB_INVALID)) |
1016 | 0 | j->manager->n_failed_jobs++; |
1017 | 0 |
|
1018 | 0 | job_uninstall(j); |
1019 | 0 | job_free(j); |
1020 | 0 |
|
1021 | 0 | /* Fail depending jobs on failure */ |
1022 | 0 | if (result != JOB_DONE && recursive) { |
1023 | 0 | if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) { |
1024 | 0 | job_fail_dependencies(u, UNIT_REQUIRED_BY); |
1025 | 0 | job_fail_dependencies(u, UNIT_REQUISITE_OF); |
1026 | 0 | job_fail_dependencies(u, UNIT_BOUND_BY); |
1027 | 0 | } else if (t == JOB_STOP) |
1028 | 0 | job_fail_dependencies(u, UNIT_CONFLICTED_BY); |
1029 | 0 | } |
1030 | 0 |
|
1031 | 0 | /* A special check to make sure we take down anything RequisiteOf if we |
1032 | 0 | * aren't active. This is when the verify-active job merges with a |
1033 | 0 | * satisfying job type, and then loses it's invalidation effect, as the |
1034 | 0 | * result there is JOB_DONE for the start job we merged into, while we |
1035 | 0 | * should be failing the depending job if the said unit isn't infact |
1036 | 0 | * active. Oneshots are an example of this, where going directly from |
1037 | 0 | * activating to inactive is success. |
1038 | 0 | * |
1039 | 0 | * This happens when you use ConditionXYZ= in a unit too, since in that |
1040 | 0 | * case the job completes with the JOB_DONE result, but the unit never |
1041 | 0 | * really becomes active. Note that such a case still involves merging: |
1042 | 0 | * |
1043 | 0 | * A start job waits for something else, and a verify-active comes in |
1044 | 0 | * and merges in the installed job. Then, later, when it becomes |
1045 | 0 | * runnable, it finishes with JOB_DONE result as execution on conditions |
1046 | 0 | * not being met is skipped, breaking our dependency semantics. |
1047 | 0 | * |
1048 | 0 | * Also, depending on if start job waits or not, the merging may or may |
1049 | 0 | * not happen (the verify-active job may trigger after it finishes), so |
1050 | 0 | * you get undeterministic results without this check. |
1051 | 0 | */ |
1052 | 0 | if (result == JOB_DONE && recursive && !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { |
1053 | 0 | if (IN_SET(t, JOB_START, JOB_RELOAD)) |
1054 | 0 | job_fail_dependencies(u, UNIT_REQUISITE_OF); |
1055 | 0 | } |
1056 | 0 | /* Trigger OnFailure dependencies that are not generated by |
1057 | 0 | * the unit itself. We don't treat JOB_CANCELED as failure in |
1058 | 0 | * this context. And JOB_FAILURE is already handled by the |
1059 | 0 | * unit itself. */ |
1060 | 0 | if (IN_SET(result, JOB_TIMEOUT, JOB_DEPENDENCY)) { |
1061 | 0 | log_struct(LOG_NOTICE, |
1062 | 0 | "JOB_TYPE=%s", job_type_to_string(t), |
1063 | 0 | "JOB_RESULT=%s", job_result_to_string(result), |
1064 | 0 | LOG_UNIT_ID(u), |
1065 | 0 | LOG_UNIT_MESSAGE(u, "Job %s/%s failed with result '%s'.", |
1066 | 0 | u->id, |
1067 | 0 | job_type_to_string(t), |
1068 | 0 | job_result_to_string(result))); |
1069 | 0 |
|
1070 | 0 | unit_start_on_failure(u); |
1071 | 0 | } |
1072 | 0 |
|
1073 | 0 | unit_trigger_notify(u); |
1074 | 0 |
|
1075 | 0 | finish: |
1076 | 0 | /* Try to start the next jobs that can be started */ |
1077 | 0 | HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_AFTER], i) |
1078 | 0 | if (other->job) { |
1079 | 0 | job_add_to_run_queue(other->job); |
1080 | 0 | job_add_to_gc_queue(other->job); |
1081 | 0 | } |
1082 | 0 | HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BEFORE], i) |
1083 | 0 | if (other->job) { |
1084 | 0 | job_add_to_run_queue(other->job); |
1085 | 0 | job_add_to_gc_queue(other->job); |
1086 | 0 | } |
1087 | 0 |
|
1088 | 0 | manager_check_finished(u->manager); |
1089 | 0 |
|
1090 | 0 | return 0; |
1091 | 0 | } |
1092 | | |
1093 | 0 | static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *userdata) { |
1094 | 0 | Job *j = userdata; |
1095 | 0 | Unit *u; |
1096 | 0 |
|
1097 | 0 | assert(j); |
1098 | 0 | assert(s == j->timer_event_source); |
1099 | 0 |
|
1100 | 0 | log_unit_warning(j->unit, "Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); |
1101 | 0 |
|
1102 | 0 | u = j->unit; |
1103 | 0 | job_finish_and_invalidate(j, JOB_TIMEOUT, true, false); |
1104 | 0 |
|
1105 | 0 | emergency_action(u->manager, u->job_timeout_action, |
1106 | 0 | EMERGENCY_ACTION_IS_WATCHDOG|EMERGENCY_ACTION_WARN, |
1107 | 0 | u->job_timeout_reboot_arg, -1, "job timed out"); |
1108 | 0 |
|
1109 | 0 | return 0; |
1110 | 0 | } |
1111 | | |
1112 | 0 | int job_start_timer(Job *j, bool job_running) { |
1113 | 0 | int r; |
1114 | 0 | usec_t timeout_time, old_timeout_time; |
1115 | 0 |
|
1116 | 0 | if (job_running) { |
1117 | 0 | j->begin_running_usec = now(CLOCK_MONOTONIC); |
1118 | 0 |
|
1119 | 0 | if (j->unit->job_running_timeout == USEC_INFINITY) |
1120 | 0 | return 0; |
1121 | 0 | |
1122 | 0 | timeout_time = usec_add(j->begin_running_usec, j->unit->job_running_timeout); |
1123 | 0 |
|
1124 | 0 | if (j->timer_event_source) { |
1125 | 0 | /* Update only if JobRunningTimeoutSec= results in earlier timeout */ |
1126 | 0 | r = sd_event_source_get_time(j->timer_event_source, &old_timeout_time); |
1127 | 0 | if (r < 0) |
1128 | 0 | return r; |
1129 | 0 | |
1130 | 0 | if (old_timeout_time <= timeout_time) |
1131 | 0 | return 0; |
1132 | 0 | |
1133 | 0 | return sd_event_source_set_time(j->timer_event_source, timeout_time); |
1134 | 0 | } |
1135 | 0 | } else { |
1136 | 0 | if (j->timer_event_source) |
1137 | 0 | return 0; |
1138 | 0 | |
1139 | 0 | j->begin_usec = now(CLOCK_MONOTONIC); |
1140 | 0 |
|
1141 | 0 | if (j->unit->job_timeout == USEC_INFINITY) |
1142 | 0 | return 0; |
1143 | 0 | |
1144 | 0 | timeout_time = usec_add(j->begin_usec, j->unit->job_timeout); |
1145 | 0 | } |
1146 | 0 |
|
1147 | 0 | r = sd_event_add_time( |
1148 | 0 | j->manager->event, |
1149 | 0 | &j->timer_event_source, |
1150 | 0 | CLOCK_MONOTONIC, |
1151 | 0 | timeout_time, 0, |
1152 | 0 | job_dispatch_timer, j); |
1153 | 0 | if (r < 0) |
1154 | 0 | return r; |
1155 | 0 | |
1156 | 0 | (void) sd_event_source_set_description(j->timer_event_source, "job-start"); |
1157 | 0 |
|
1158 | 0 | return 0; |
1159 | 0 | } |
1160 | | |
1161 | 0 | void job_add_to_run_queue(Job *j) { |
1162 | 0 | int r; |
1163 | 0 |
|
1164 | 0 | assert(j); |
1165 | 0 | assert(j->installed); |
1166 | 0 |
|
1167 | 0 | if (j->in_run_queue) |
1168 | 0 | return; |
1169 | 0 | |
1170 | 0 | if (!j->manager->run_queue) { |
1171 | 0 | r = sd_event_source_set_enabled(j->manager->run_queue_event_source, SD_EVENT_ONESHOT); |
1172 | 0 | if (r < 0) |
1173 | 0 | log_warning_errno(r, "Failed to enable job run queue event source, ignoring: %m"); |
1174 | 0 | } |
1175 | 0 |
|
1176 | 0 | LIST_PREPEND(run_queue, j->manager->run_queue, j); |
1177 | 0 | j->in_run_queue = true; |
1178 | 0 | } |
1179 | | |
1180 | 0 | void job_add_to_dbus_queue(Job *j) { |
1181 | 0 | assert(j); |
1182 | 0 | assert(j->installed); |
1183 | 0 |
|
1184 | 0 | if (j->in_dbus_queue) |
1185 | 0 | return; |
1186 | 0 | |
1187 | 0 | /* We don't check if anybody is subscribed here, since this |
1188 | 0 | * job might just have been created and not yet assigned to a |
1189 | 0 | * connection/client. */ |
1190 | 0 | |
1191 | 0 | LIST_PREPEND(dbus_queue, j->manager->dbus_job_queue, j); |
1192 | 0 | j->in_dbus_queue = true; |
1193 | 0 | } |
1194 | | |
1195 | 0 | char *job_dbus_path(Job *j) { |
1196 | 0 | char *p; |
1197 | 0 |
|
1198 | 0 | assert(j); |
1199 | 0 |
|
1200 | 0 | if (asprintf(&p, "/org/freedesktop/systemd1/job/%"PRIu32, j->id) < 0) |
1201 | 0 | return NULL; |
1202 | 0 | |
1203 | 0 | return p; |
1204 | 0 | } |
1205 | | |
1206 | 0 | int job_serialize(Job *j, FILE *f) { |
1207 | 0 | assert(j); |
1208 | 0 | assert(f); |
1209 | 0 |
|
1210 | 0 | (void) serialize_item_format(f, "job-id", "%u", j->id); |
1211 | 0 | (void) serialize_item(f, "job-type", job_type_to_string(j->type)); |
1212 | 0 | (void) serialize_item(f, "job-state", job_state_to_string(j->state)); |
1213 | 0 | (void) serialize_bool(f, "job-irreversible", j->irreversible); |
1214 | 0 | (void) serialize_bool(f, "job-sent-dbus-new-signal", j->sent_dbus_new_signal); |
1215 | 0 | (void) serialize_bool(f, "job-ignore-order", j->ignore_order); |
1216 | 0 |
|
1217 | 0 | if (j->begin_usec > 0) |
1218 | 0 | (void) serialize_usec(f, "job-begin", j->begin_usec); |
1219 | 0 | if (j->begin_running_usec > 0) |
1220 | 0 | (void) serialize_usec(f, "job-begin-running", j->begin_running_usec); |
1221 | 0 |
|
1222 | 0 | bus_track_serialize(j->bus_track, f, "subscribed"); |
1223 | 0 |
|
1224 | 0 | /* End marker */ |
1225 | 0 | fputc('\n', f); |
1226 | 0 | return 0; |
1227 | 0 | } |
1228 | | |
1229 | | int job_deserialize(Job *j, FILE *f) { |
1230 | | int r; |
1231 | | |
1232 | | assert(j); |
1233 | | assert(f); |
1234 | | |
1235 | | for (;;) { |
1236 | | _cleanup_free_ char *line = NULL; |
1237 | | char *l, *v; |
1238 | | size_t k; |
1239 | | |
1240 | | r = read_line(f, LONG_LINE_MAX, &line); |
1241 | | if (r < 0) |
1242 | | return log_error_errno(r, "Failed to read serialization line: %m"); |
1243 | | if (r == 0) |
1244 | | return 0; |
1245 | | |
1246 | | l = strstrip(line); |
1247 | | |
1248 | | /* End marker */ |
1249 | | if (isempty(l)) |
1250 | | return 0; |
1251 | | |
1252 | | k = strcspn(l, "="); |
1253 | | |
1254 | | if (l[k] == '=') { |
1255 | | l[k] = 0; |
1256 | | v = l+k+1; |
1257 | | } else |
1258 | | v = l+k; |
1259 | | |
1260 | | if (streq(l, "job-id")) { |
1261 | | |
1262 | | if (safe_atou32(v, &j->id) < 0) |
1263 | | log_debug("Failed to parse job id value: %s", v); |
1264 | | |
1265 | | } else if (streq(l, "job-type")) { |
1266 | | JobType t; |
1267 | | |
1268 | | t = job_type_from_string(v); |
1269 | | if (t < 0) |
1270 | | log_debug("Failed to parse job type: %s", v); |
1271 | | else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION) |
1272 | | log_debug("Cannot deserialize job of type: %s", v); |
1273 | | else |
1274 | | j->type = t; |
1275 | | |
1276 | | } else if (streq(l, "job-state")) { |
1277 | | JobState s; |
1278 | | |
1279 | | s = job_state_from_string(v); |
1280 | | if (s < 0) |
1281 | | log_debug("Failed to parse job state: %s", v); |
1282 | | else |
1283 | | job_set_state(j, s); |
1284 | | |
1285 | | } else if (streq(l, "job-irreversible")) { |
1286 | | int b; |
1287 | | |
1288 | | b = parse_boolean(v); |
1289 | | if (b < 0) |
1290 | | log_debug("Failed to parse job irreversible flag: %s", v); |
1291 | | else |
1292 | | j->irreversible = j->irreversible || b; |
1293 | | |
1294 | | } else if (streq(l, "job-sent-dbus-new-signal")) { |
1295 | | int b; |
1296 | | |
1297 | | b = parse_boolean(v); |
1298 | | if (b < 0) |
1299 | | log_debug("Failed to parse job sent_dbus_new_signal flag: %s", v); |
1300 | | else |
1301 | | j->sent_dbus_new_signal = j->sent_dbus_new_signal || b; |
1302 | | |
1303 | | } else if (streq(l, "job-ignore-order")) { |
1304 | | int b; |
1305 | | |
1306 | | b = parse_boolean(v); |
1307 | | if (b < 0) |
1308 | | log_debug("Failed to parse job ignore_order flag: %s", v); |
1309 | | else |
1310 | | j->ignore_order = j->ignore_order || b; |
1311 | | |
1312 | | } else if (streq(l, "job-begin")) |
1313 | | (void) deserialize_usec(v, &j->begin_usec); |
1314 | | |
1315 | | else if (streq(l, "job-begin-running")) |
1316 | | (void) deserialize_usec(v, &j->begin_running_usec); |
1317 | | |
1318 | | else if (streq(l, "subscribed")) { |
1319 | | if (strv_extend(&j->deserialized_clients, v) < 0) |
1320 | | return log_oom(); |
1321 | | } else |
1322 | | log_debug("Unknown job serialization key: %s", l); |
1323 | | } |
1324 | | } |
1325 | | |
1326 | 0 | int job_coldplug(Job *j) { |
1327 | 0 | int r; |
1328 | 0 | usec_t timeout_time = USEC_INFINITY; |
1329 | 0 |
|
1330 | 0 | assert(j); |
1331 | 0 |
|
1332 | 0 | /* After deserialization is complete and the bus connection |
1333 | 0 | * set up again, let's start watching our subscribers again */ |
1334 | 0 | (void) bus_job_coldplug_bus_track(j); |
1335 | 0 |
|
1336 | 0 | if (j->state == JOB_WAITING) |
1337 | 0 | job_add_to_run_queue(j); |
1338 | 0 |
|
1339 | 0 | /* Maybe due to new dependencies we don't actually need this job anymore? */ |
1340 | 0 | job_add_to_gc_queue(j); |
1341 | 0 |
|
1342 | 0 | /* Create timer only when job began or began running and the respective timeout is finite. |
1343 | 0 | * Follow logic of job_start_timer() if both timeouts are finite */ |
1344 | 0 | if (j->begin_usec == 0) |
1345 | 0 | return 0; |
1346 | 0 | |
1347 | 0 | if (j->unit->job_timeout != USEC_INFINITY) |
1348 | 0 | timeout_time = usec_add(j->begin_usec, j->unit->job_timeout); |
1349 | 0 |
|
1350 | 0 | if (j->begin_running_usec > 0 && j->unit->job_running_timeout != USEC_INFINITY) |
1351 | 0 | timeout_time = MIN(timeout_time, usec_add(j->begin_running_usec, j->unit->job_running_timeout)); |
1352 | 0 |
|
1353 | 0 | if (timeout_time == USEC_INFINITY) |
1354 | 0 | return 0; |
1355 | 0 | |
1356 | 0 | j->timer_event_source = sd_event_source_unref(j->timer_event_source); |
1357 | 0 |
|
1358 | 0 | r = sd_event_add_time( |
1359 | 0 | j->manager->event, |
1360 | 0 | &j->timer_event_source, |
1361 | 0 | CLOCK_MONOTONIC, |
1362 | 0 | timeout_time, 0, |
1363 | 0 | job_dispatch_timer, j); |
1364 | 0 | if (r < 0) |
1365 | 0 | log_debug_errno(r, "Failed to restart timeout for job: %m"); |
1366 | 0 |
|
1367 | 0 | (void) sd_event_source_set_description(j->timer_event_source, "job-timeout"); |
1368 | 0 |
|
1369 | 0 | return r; |
1370 | 0 | } |
1371 | | |
1372 | 0 | void job_shutdown_magic(Job *j) { |
1373 | 0 | assert(j); |
1374 | 0 |
|
1375 | 0 | /* The shutdown target gets some special treatment here: we |
1376 | 0 | * tell the kernel to begin with flushing its disk caches, to |
1377 | 0 | * optimize shutdown time a bit. Ideally we wouldn't hardcode |
1378 | 0 | * this magic into PID 1. However all other processes aren't |
1379 | 0 | * options either since they'd exit much sooner than PID 1 and |
1380 | 0 | * asynchronous sync() would cause their exit to be |
1381 | 0 | * delayed. */ |
1382 | 0 |
|
1383 | 0 | if (j->type != JOB_START) |
1384 | 0 | return; |
1385 | 0 | |
1386 | 0 | if (!MANAGER_IS_SYSTEM(j->unit->manager)) |
1387 | 0 | return; |
1388 | 0 | |
1389 | 0 | if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) |
1390 | 0 | return; |
1391 | 0 | |
1392 | 0 | /* In case messages on console has been disabled on boot */ |
1393 | 0 | j->unit->manager->no_console_output = false; |
1394 | 0 |
|
1395 | 0 | if (detect_container() > 0) |
1396 | 0 | return; |
1397 | 0 | |
1398 | 0 | (void) asynchronous_sync(NULL); |
1399 | 0 | } |
1400 | | |
1401 | 0 | int job_get_timeout(Job *j, usec_t *timeout) { |
1402 | 0 | usec_t x = USEC_INFINITY, y = USEC_INFINITY; |
1403 | 0 | Unit *u = j->unit; |
1404 | 0 | int r; |
1405 | 0 |
|
1406 | 0 | assert(u); |
1407 | 0 |
|
1408 | 0 | if (j->timer_event_source) { |
1409 | 0 | r = sd_event_source_get_time(j->timer_event_source, &x); |
1410 | 0 | if (r < 0) |
1411 | 0 | return r; |
1412 | 0 | } |
1413 | 0 | |
1414 | 0 | if (UNIT_VTABLE(u)->get_timeout) { |
1415 | 0 | r = UNIT_VTABLE(u)->get_timeout(u, &y); |
1416 | 0 | if (r < 0) |
1417 | 0 | return r; |
1418 | 0 | } |
1419 | 0 | |
1420 | 0 | if (x == USEC_INFINITY && y == USEC_INFINITY) |
1421 | 0 | return 0; |
1422 | 0 | |
1423 | 0 | *timeout = MIN(x, y); |
1424 | 0 | return 1; |
1425 | 0 | } |
1426 | | |
1427 | 0 | bool job_may_gc(Job *j) { |
1428 | 0 | Unit *other; |
1429 | 0 | Iterator i; |
1430 | 0 | void *v; |
1431 | 0 |
|
1432 | 0 | assert(j); |
1433 | 0 |
|
1434 | 0 | /* Checks whether this job should be GC'ed away. We only do this for jobs of units that have no effect on their |
1435 | 0 | * own and just track external state. For now the only unit type that qualifies for this are .device units. |
1436 | 0 | * Returns true if the job can be collected. */ |
1437 | 0 |
|
1438 | 0 | if (!UNIT_VTABLE(j->unit)->gc_jobs) |
1439 | 0 | return false; |
1440 | 0 | |
1441 | 0 | if (sd_bus_track_count(j->bus_track) > 0) |
1442 | 0 | return false; |
1443 | 0 | |
1444 | 0 | /* FIXME: So this is a bit ugly: for now we don't properly track references made via private bus connections |
1445 | 0 | * (because it's nasty, as sd_bus_track doesn't apply to it). We simply remember that the job was once |
1446 | 0 | * referenced by one, and reset this whenever we notice that no private bus connections are around. This means |
1447 | 0 | * the GC is a bit too conservative when it comes to jobs created by private bus connections. */ |
1448 | 0 | if (j->ref_by_private_bus) { |
1449 | 0 | if (set_isempty(j->unit->manager->private_buses)) |
1450 | 0 | j->ref_by_private_bus = false; |
1451 | 0 | else |
1452 | 0 | return false; |
1453 | 0 | } |
1454 | 0 | |
1455 | 0 | if (j->type == JOB_NOP) |
1456 | 0 | return false; |
1457 | 0 | |
1458 | 0 | /* If a job is ordered after ours, and is to be started, then it needs to wait for us, regardless if we stop or |
1459 | 0 | * start, hence let's not GC in that case. */ |
1460 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i) { |
1461 | 0 | if (!other->job) |
1462 | 0 | continue; |
1463 | 0 | |
1464 | 0 | if (other->job->ignore_order) |
1465 | 0 | continue; |
1466 | 0 | |
1467 | 0 | if (IN_SET(other->job->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) |
1468 | 0 | return false; |
1469 | 0 | } |
1470 | 0 |
|
1471 | 0 | /* If we are going down, but something else is ordered After= us, then it needs to wait for us */ |
1472 | 0 | if (IN_SET(j->type, JOB_STOP, JOB_RESTART)) |
1473 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) { |
1474 | 0 | if (!other->job) |
1475 | 0 | continue; |
1476 | 0 | |
1477 | 0 | if (other->job->ignore_order) |
1478 | 0 | continue; |
1479 | 0 | |
1480 | 0 | return false; |
1481 | 0 | } |
1482 | 0 |
|
1483 | 0 | /* The logic above is kinda the inverse of the job_is_runnable() logic. Specifically, if the job "we" is |
1484 | 0 | * ordered before the job "other": |
1485 | 0 | * |
1486 | 0 | * we start + other start → stay |
1487 | 0 | * we start + other stop → gc |
1488 | 0 | * we stop + other start → stay |
1489 | 0 | * we stop + other stop → gc |
1490 | 0 | * |
1491 | 0 | * "we" are ordered after "other": |
1492 | 0 | * |
1493 | 0 | * we start + other start → gc |
1494 | 0 | * we start + other stop → gc |
1495 | 0 | * we stop + other start → stay |
1496 | 0 | * we stop + other stop → stay |
1497 | 0 | */ |
1498 | 0 |
|
1499 | 0 | return true; |
1500 | 0 | } |
1501 | | |
1502 | 0 | void job_add_to_gc_queue(Job *j) { |
1503 | 0 | assert(j); |
1504 | 0 |
|
1505 | 0 | if (j->in_gc_queue) |
1506 | 0 | return; |
1507 | 0 | |
1508 | 0 | if (!job_may_gc(j)) |
1509 | 0 | return; |
1510 | 0 | |
1511 | 0 | LIST_PREPEND(gc_queue, j->unit->manager->gc_job_queue, j); |
1512 | 0 | j->in_gc_queue = true; |
1513 | 0 | } |
1514 | | |
1515 | 0 | static int job_compare(Job * const *a, Job * const *b) { |
1516 | 0 | return CMP((*a)->id, (*b)->id); |
1517 | 0 | } |
1518 | | |
1519 | 0 | static size_t sort_job_list(Job **list, size_t n) { |
1520 | 0 | Job *previous = NULL; |
1521 | 0 | size_t a, b; |
1522 | 0 |
|
1523 | 0 | /* Order by numeric IDs */ |
1524 | 0 | typesafe_qsort(list, n, job_compare); |
1525 | 0 |
|
1526 | 0 | /* Filter out duplicates */ |
1527 | 0 | for (a = 0, b = 0; a < n; a++) { |
1528 | 0 |
|
1529 | 0 | if (previous == list[a]) |
1530 | 0 | continue; |
1531 | 0 | |
1532 | 0 | previous = list[b++] = list[a]; |
1533 | 0 | } |
1534 | 0 |
|
1535 | 0 | return b; |
1536 | 0 | } |
1537 | | |
1538 | 0 | int job_get_before(Job *j, Job*** ret) { |
1539 | 0 | _cleanup_free_ Job** list = NULL; |
1540 | 0 | size_t n = 0, n_allocated = 0; |
1541 | 0 | Unit *other = NULL; |
1542 | 0 | Iterator i; |
1543 | 0 | void *v; |
1544 | 0 |
|
1545 | 0 | /* Returns a list of all pending jobs that need to finish before this job may be started. */ |
1546 | 0 |
|
1547 | 0 | assert(j); |
1548 | 0 | assert(ret); |
1549 | 0 |
|
1550 | 0 | if (j->ignore_order) { |
1551 | 0 | *ret = NULL; |
1552 | 0 | return 0; |
1553 | 0 | } |
1554 | 0 | |
1555 | 0 | if (IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) { |
1556 | 0 |
|
1557 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) { |
1558 | 0 | if (!other->job) |
1559 | 0 | continue; |
1560 | 0 | |
1561 | 0 | if (!GREEDY_REALLOC(list, n_allocated, n+1)) |
1562 | 0 | return -ENOMEM; |
1563 | 0 | list[n++] = other->job; |
1564 | 0 | } |
1565 | 0 | } |
1566 | 0 |
|
1567 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i) { |
1568 | 0 | if (!other->job) |
1569 | 0 | continue; |
1570 | 0 | |
1571 | 0 | if (!IN_SET(other->job->type, JOB_STOP, JOB_RESTART)) |
1572 | 0 | continue; |
1573 | 0 | |
1574 | 0 | if (!GREEDY_REALLOC(list, n_allocated, n+1)) |
1575 | 0 | return -ENOMEM; |
1576 | 0 | list[n++] = other->job; |
1577 | 0 | } |
1578 | 0 |
|
1579 | 0 | n = sort_job_list(list, n); |
1580 | 0 |
|
1581 | 0 | *ret = TAKE_PTR(list); |
1582 | 0 |
|
1583 | 0 | return (int) n; |
1584 | 0 | } |
1585 | | |
1586 | 0 | int job_get_after(Job *j, Job*** ret) { |
1587 | 0 | _cleanup_free_ Job** list = NULL; |
1588 | 0 | size_t n = 0, n_allocated = 0; |
1589 | 0 | Unit *other = NULL; |
1590 | 0 | void *v; |
1591 | 0 | Iterator i; |
1592 | 0 |
|
1593 | 0 | assert(j); |
1594 | 0 | assert(ret); |
1595 | 0 |
|
1596 | 0 | /* Returns a list of all pending jobs that are waiting for this job to finish. */ |
1597 | 0 |
|
1598 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i) { |
1599 | 0 | if (!other->job) |
1600 | 0 | continue; |
1601 | 0 | |
1602 | 0 | if (other->job->ignore_order) |
1603 | 0 | continue; |
1604 | 0 | |
1605 | 0 | if (!IN_SET(other->job->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) |
1606 | 0 | continue; |
1607 | 0 | |
1608 | 0 | if (!GREEDY_REALLOC(list, n_allocated, n+1)) |
1609 | 0 | return -ENOMEM; |
1610 | 0 | list[n++] = other->job; |
1611 | 0 | } |
1612 | 0 |
|
1613 | 0 | if (IN_SET(j->type, JOB_STOP, JOB_RESTART)) { |
1614 | 0 |
|
1615 | 0 | HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) { |
1616 | 0 | if (!other->job) |
1617 | 0 | continue; |
1618 | 0 | |
1619 | 0 | if (other->job->ignore_order) |
1620 | 0 | continue; |
1621 | 0 | |
1622 | 0 | if (!GREEDY_REALLOC(list, n_allocated, n+1)) |
1623 | 0 | return -ENOMEM; |
1624 | 0 | list[n++] = other->job; |
1625 | 0 | } |
1626 | 0 | } |
1627 | 0 |
|
1628 | 0 | n = sort_job_list(list, n); |
1629 | 0 |
|
1630 | 0 | *ret = TAKE_PTR(list); |
1631 | 0 |
|
1632 | 0 | return (int) n; |
1633 | 0 | } |
1634 | | |
1635 | | static const char* const job_state_table[_JOB_STATE_MAX] = { |
1636 | | [JOB_WAITING] = "waiting", |
1637 | | [JOB_RUNNING] = "running", |
1638 | | }; |
1639 | | |
1640 | | DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); |
1641 | | |
1642 | | static const char* const job_type_table[_JOB_TYPE_MAX] = { |
1643 | | [JOB_START] = "start", |
1644 | | [JOB_VERIFY_ACTIVE] = "verify-active", |
1645 | | [JOB_STOP] = "stop", |
1646 | | [JOB_RELOAD] = "reload", |
1647 | | [JOB_RELOAD_OR_START] = "reload-or-start", |
1648 | | [JOB_RESTART] = "restart", |
1649 | | [JOB_TRY_RESTART] = "try-restart", |
1650 | | [JOB_TRY_RELOAD] = "try-reload", |
1651 | | [JOB_NOP] = "nop", |
1652 | | }; |
1653 | | |
1654 | | DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); |
1655 | | |
1656 | | static const char* const job_mode_table[_JOB_MODE_MAX] = { |
1657 | | [JOB_FAIL] = "fail", |
1658 | | [JOB_REPLACE] = "replace", |
1659 | | [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", |
1660 | | [JOB_ISOLATE] = "isolate", |
1661 | | [JOB_FLUSH] = "flush", |
1662 | | [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", |
1663 | | [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", |
1664 | | }; |
1665 | | |
1666 | | DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); |
1667 | | |
1668 | | static const char* const job_result_table[_JOB_RESULT_MAX] = { |
1669 | | [JOB_DONE] = "done", |
1670 | | [JOB_CANCELED] = "canceled", |
1671 | | [JOB_TIMEOUT] = "timeout", |
1672 | | [JOB_FAILED] = "failed", |
1673 | | [JOB_DEPENDENCY] = "dependency", |
1674 | | [JOB_SKIPPED] = "skipped", |
1675 | | [JOB_INVALID] = "invalid", |
1676 | | [JOB_ASSERT] = "assert", |
1677 | | [JOB_UNSUPPORTED] = "unsupported", |
1678 | | [JOB_COLLECTED] = "collected", |
1679 | | [JOB_ONCE] = "once", |
1680 | | }; |
1681 | | |
1682 | | DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); |
1683 | | |
1684 | 0 | const char* job_type_to_access_method(JobType t) { |
1685 | 0 | assert(t >= 0); |
1686 | 0 | assert(t < _JOB_TYPE_MAX); |
1687 | 0 |
|
1688 | 0 | if (IN_SET(t, JOB_START, JOB_RESTART, JOB_TRY_RESTART)) |
1689 | 0 | return "start"; |
1690 | 0 | else if (t == JOB_STOP) |
1691 | 0 | return "stop"; |
1692 | 0 | else |
1693 | 0 | return "reload"; |
1694 | 0 | } |