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