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 : }
|