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