]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-unit-procs.c
hibernate-resume: add resumeflags= kernel option
[thirdparty/systemd.git] / src / shared / bus-unit-procs.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "bus-unit-procs.h"
4 #include "hashmap.h"
5 #include "list.h"
6 #include "locale-util.h"
7 #include "macro.h"
8 #include "path-util.h"
9 #include "process-util.h"
10 #include "sort-util.h"
11 #include "string-util.h"
12 #include "terminal-util.h"
13
14 struct CGroupInfo {
15 char *cgroup_path;
16 bool is_const; /* If false, cgroup_path should be free()'d */
17
18 Hashmap *pids; /* PID → process name */
19 bool done;
20
21 struct CGroupInfo *parent;
22 LIST_FIELDS(struct CGroupInfo, siblings);
23 LIST_HEAD(struct CGroupInfo, children);
24 size_t n_children;
25 };
26
27 static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) {
28 struct CGroupInfo *parent = NULL, *cg;
29 int r;
30
31 assert(cgroups);
32 assert(ret);
33
34 if (empty_or_root(path))
35 path = "/";
36
37 cg = hashmap_get(cgroups, path);
38 if (cg) {
39 *ret = cg;
40 return 0;
41 }
42
43 if (!empty_or_root(path)) {
44 const char *e, *pp;
45
46 e = strrchr(path, '/');
47 if (!e)
48 return -EINVAL;
49
50 pp = strndupa(path, e - path);
51 if (!pp)
52 return -ENOMEM;
53
54 r = add_cgroup(cgroups, pp, false, &parent);
55 if (r < 0)
56 return r;
57 }
58
59 cg = new0(struct CGroupInfo, 1);
60 if (!cg)
61 return -ENOMEM;
62
63 if (is_const)
64 cg->cgroup_path = (char*) path;
65 else {
66 cg->cgroup_path = strdup(path);
67 if (!cg->cgroup_path) {
68 free(cg);
69 return -ENOMEM;
70 }
71 }
72
73 cg->is_const = is_const;
74 cg->parent = parent;
75
76 r = hashmap_put(cgroups, cg->cgroup_path, cg);
77 if (r < 0) {
78 if (!is_const)
79 free(cg->cgroup_path);
80 free(cg);
81 return r;
82 }
83
84 if (parent) {
85 LIST_PREPEND(siblings, parent->children, cg);
86 parent->n_children++;
87 }
88
89 *ret = cg;
90 return 1;
91 }
92
93 static int add_process(
94 Hashmap *cgroups,
95 const char *path,
96 pid_t pid,
97 const char *name) {
98
99 struct CGroupInfo *cg;
100 int r;
101
102 assert(cgroups);
103 assert(name);
104 assert(pid > 0);
105
106 r = add_cgroup(cgroups, path, true, &cg);
107 if (r < 0)
108 return r;
109
110 r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops);
111 if (r < 0)
112 return r;
113
114 return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name);
115 }
116
117 static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) {
118 assert(cgroups);
119 assert(cg);
120
121 while (cg->children)
122 remove_cgroup(cgroups, cg->children);
123
124 hashmap_remove(cgroups, cg->cgroup_path);
125
126 if (!cg->is_const)
127 free(cg->cgroup_path);
128
129 hashmap_free(cg->pids);
130
131 if (cg->parent)
132 LIST_REMOVE(siblings, cg->parent->children, cg);
133
134 free(cg);
135 }
136
137 static int cgroup_info_compare_func(struct CGroupInfo * const *a, struct CGroupInfo * const *b) {
138 return strcmp((*a)->cgroup_path, (*b)->cgroup_path);
139 }
140
141 static int dump_processes(
142 Hashmap *cgroups,
143 const char *cgroup_path,
144 const char *prefix,
145 unsigned n_columns,
146 OutputFlags flags) {
147
148 struct CGroupInfo *cg;
149 int r;
150
151 assert(prefix);
152
153 if (empty_or_root(cgroup_path))
154 cgroup_path = "/";
155
156 cg = hashmap_get(cgroups, cgroup_path);
157 if (!cg)
158 return 0;
159
160 if (!hashmap_isempty(cg->pids)) {
161 const char *name;
162 size_t n = 0, i;
163 pid_t *pids;
164 void *pidp;
165 Iterator j;
166 int width;
167
168 /* Order processes by their PID */
169 pids = newa(pid_t, hashmap_size(cg->pids));
170
171 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j)
172 pids[n++] = PTR_TO_PID(pidp);
173
174 assert(n == hashmap_size(cg->pids));
175 typesafe_qsort(pids, n, pid_compare_func);
176
177 width = DECIMAL_STR_WIDTH(pids[n-1]);
178
179 for (i = 0; i < n; i++) {
180 _cleanup_free_ char *e = NULL;
181 const char *special;
182 bool more;
183
184 name = hashmap_get(cg->pids, PID_TO_PTR(pids[i]));
185 assert(name);
186
187 if (n_columns != 0) {
188 unsigned k;
189
190 k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
191
192 e = ellipsize(name, k, 100);
193 if (e)
194 name = e;
195 }
196
197 more = i+1 < n || cg->children;
198 special = special_glyph(more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT);
199
200 fprintf(stdout, "%s%s%*"PID_PRI" %s\n",
201 prefix,
202 special,
203 width, pids[i],
204 name);
205 }
206 }
207
208 if (cg->children) {
209 struct CGroupInfo **children, *child;
210 size_t n = 0, i;
211
212 /* Order subcgroups by their name */
213 children = newa(struct CGroupInfo*, cg->n_children);
214 LIST_FOREACH(siblings, child, cg->children)
215 children[n++] = child;
216 assert(n == cg->n_children);
217 typesafe_qsort(children, n, cgroup_info_compare_func);
218
219 if (n_columns != 0)
220 n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
221
222 for (i = 0; i < n; i++) {
223 _cleanup_free_ char *pp = NULL;
224 const char *name, *special;
225 bool more;
226
227 child = children[i];
228
229 name = strrchr(child->cgroup_path, '/');
230 if (!name)
231 return -EINVAL;
232 name++;
233
234 more = i+1 < n;
235 special = special_glyph(more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT);
236
237 fputs(prefix, stdout);
238 fputs(special, stdout);
239 fputs(name, stdout);
240 fputc('\n', stdout);
241
242 special = special_glyph(more ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE);
243
244 pp = strappend(prefix, special);
245 if (!pp)
246 return -ENOMEM;
247
248 r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags);
249 if (r < 0)
250 return r;
251 }
252 }
253
254 cg->done = true;
255 return 0;
256 }
257
258 static int dump_extra_processes(
259 Hashmap *cgroups,
260 const char *prefix,
261 unsigned n_columns,
262 OutputFlags flags) {
263
264 _cleanup_free_ pid_t *pids = NULL;
265 _cleanup_hashmap_free_ Hashmap *names = NULL;
266 struct CGroupInfo *cg;
267 size_t n_allocated = 0, n = 0, k;
268 Iterator i;
269 int width, r;
270
271 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
272 * combined, sorted, linear list. */
273
274 HASHMAP_FOREACH(cg, cgroups, i) {
275 const char *name;
276 void *pidp;
277 Iterator j;
278
279 if (cg->done)
280 continue;
281
282 if (hashmap_isempty(cg->pids))
283 continue;
284
285 r = hashmap_ensure_allocated(&names, &trivial_hash_ops);
286 if (r < 0)
287 return r;
288
289 if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids)))
290 return -ENOMEM;
291
292 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) {
293 pids[n++] = PTR_TO_PID(pidp);
294
295 r = hashmap_put(names, pidp, (void*) name);
296 if (r < 0)
297 return r;
298 }
299 }
300
301 if (n == 0)
302 return 0;
303
304 typesafe_qsort(pids, n, pid_compare_func);
305 width = DECIMAL_STR_WIDTH(pids[n-1]);
306
307 for (k = 0; k < n; k++) {
308 _cleanup_free_ char *e = NULL;
309 const char *name;
310
311 name = hashmap_get(names, PID_TO_PTR(pids[k]));
312 assert(name);
313
314 if (n_columns != 0) {
315 unsigned z;
316
317 z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
318
319 e = ellipsize(name, z, 100);
320 if (e)
321 name = e;
322 }
323
324 fprintf(stdout, "%s%s %*" PID_PRI " %s\n",
325 prefix,
326 special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET),
327 width, pids[k],
328 name);
329 }
330
331 return 0;
332 }
333
334 int unit_show_processes(
335 sd_bus *bus,
336 const char *unit,
337 const char *cgroup_path,
338 const char *prefix,
339 unsigned n_columns,
340 OutputFlags flags,
341 sd_bus_error *error) {
342
343 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
344 Hashmap *cgroups = NULL;
345 struct CGroupInfo *cg;
346 int r;
347
348 assert(bus);
349 assert(unit);
350
351 if (flags & OUTPUT_FULL_WIDTH)
352 n_columns = 0;
353 else if (n_columns <= 0)
354 n_columns = columns();
355
356 prefix = strempty(prefix);
357
358 r = sd_bus_call_method(
359 bus,
360 "org.freedesktop.systemd1",
361 "/org/freedesktop/systemd1",
362 "org.freedesktop.systemd1.Manager",
363 "GetUnitProcesses",
364 error,
365 &reply,
366 "s",
367 unit);
368 if (r < 0)
369 return r;
370
371 cgroups = hashmap_new(&path_hash_ops);
372 if (!cgroups)
373 return -ENOMEM;
374
375 r = sd_bus_message_enter_container(reply, 'a', "(sus)");
376 if (r < 0)
377 goto finish;
378
379 for (;;) {
380 const char *path = NULL, *name = NULL;
381 uint32_t pid;
382
383 r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name);
384 if (r < 0)
385 goto finish;
386 if (r == 0)
387 break;
388
389 r = add_process(cgroups, path, pid, name);
390 if (r < 0)
391 goto finish;
392 }
393
394 r = sd_bus_message_exit_container(reply);
395 if (r < 0)
396 goto finish;
397
398 r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags);
399 if (r < 0)
400 goto finish;
401
402 r = dump_extra_processes(cgroups, prefix, n_columns, flags);
403
404 finish:
405 while ((cg = hashmap_first(cgroups)))
406 remove_cgroup(cgroups, cg);
407
408 hashmap_free(cgroups);
409
410 return r;
411 }