chiark / gitweb /
acff91f5e5bfb6057d518d10c2f84c2dbe5519a0
[elogind.git] / src / shared / bus-unit-util.c
1 /***
2   This file is part of elogind.
3
4   Copyright 2016 Lennart Poettering
5
6   elogind is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   elogind is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with elogind; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "alloc-util.h"
21 #include "bus-internal.h"
22 #include "bus-unit-util.h"
23 #include "bus-util.h"
24 #include "cgroup-util.h"
25 #include "env-util.h"
26 #include "escape.h"
27 #include "hashmap.h"
28 #include "list.h"
29 #include "locale-util.h"
30 #include "parse-util.h"
31 #include "path-util.h"
32 #include "process-util.h"
33 #include "rlimit-util.h"
34 #include "signal-util.h"
35 #include "string-util.h"
36 #include "syslog-util.h"
37 #include "terminal-util.h"
38 #include "utf8.h"
39 #include "util.h"
40
41 struct CGroupInfo {
42         char *cgroup_path;
43         bool is_const; /* If false, cgroup_path should be free()'d */
44
45         Hashmap *pids; /* PID → process name */
46         bool done;
47
48         struct CGroupInfo *parent;
49         LIST_FIELDS(struct CGroupInfo, siblings);
50         LIST_HEAD(struct CGroupInfo, children);
51         size_t n_children;
52 };
53
54 static bool IS_ROOT(const char *p) {
55         return isempty(p) || streq(p, "/");
56 }
57
58 static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) {
59         struct CGroupInfo *parent = NULL, *cg;
60         int r;
61
62         assert(cgroups);
63         assert(ret);
64
65         if (IS_ROOT(path))
66                 path = "/";
67
68         cg = hashmap_get(cgroups, path);
69         if (cg) {
70                 *ret = cg;
71                 return 0;
72         }
73
74         if (!IS_ROOT(path)) {
75                 const char *e, *pp;
76
77                 e = strrchr(path, '/');
78                 if (!e)
79                         return -EINVAL;
80
81                 pp = strndupa(path, e - path);
82                 if (!pp)
83                         return -ENOMEM;
84
85                 r = add_cgroup(cgroups, pp, false, &parent);
86                 if (r < 0)
87                         return r;
88         }
89
90         cg = new0(struct CGroupInfo, 1);
91         if (!cg)
92                 return -ENOMEM;
93
94         if (is_const)
95                 cg->cgroup_path = (char*) path;
96         else {
97                 cg->cgroup_path = strdup(path);
98                 if (!cg->cgroup_path) {
99                         free(cg);
100                         return -ENOMEM;
101                 }
102         }
103
104         cg->is_const = is_const;
105         cg->parent = parent;
106
107         r = hashmap_put(cgroups, cg->cgroup_path, cg);
108         if (r < 0) {
109                 if (!is_const)
110                         free(cg->cgroup_path);
111                 free(cg);
112                 return r;
113         }
114
115         if (parent) {
116                 LIST_PREPEND(siblings, parent->children, cg);
117                 parent->n_children++;
118         }
119
120         *ret = cg;
121         return 1;
122 }
123
124 static int add_process(
125                 Hashmap *cgroups,
126                 const char *path,
127                 pid_t pid,
128                 const char *name) {
129
130         struct CGroupInfo *cg;
131         int r;
132
133         assert(cgroups);
134         assert(name);
135         assert(pid > 0);
136
137         r = add_cgroup(cgroups, path, true, &cg);
138         if (r < 0)
139                 return r;
140
141         r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops);
142         if (r < 0)
143                 return r;
144
145         return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name);
146 }
147
148 static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) {
149         assert(cgroups);
150         assert(cg);
151
152         while (cg->children)
153                 remove_cgroup(cgroups, cg->children);
154
155         hashmap_remove(cgroups, cg->cgroup_path);
156
157         if (!cg->is_const)
158                 free(cg->cgroup_path);
159
160         hashmap_free(cg->pids);
161
162         if (cg->parent)
163                 LIST_REMOVE(siblings, cg->parent->children, cg);
164
165         free(cg);
166 }
167
168 static int cgroup_info_compare_func(const void *a, const void *b) {
169         const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b;
170
171         assert(x);
172         assert(y);
173
174         return strcmp(x->cgroup_path, y->cgroup_path);
175 }
176
177 static int dump_processes(
178                 Hashmap *cgroups,
179                 const char *cgroup_path,
180                 const char *prefix,
181                 unsigned n_columns,
182                 OutputFlags flags) {
183
184         struct CGroupInfo *cg;
185         int r;
186
187         assert(prefix);
188
189         if (IS_ROOT(cgroup_path))
190                 cgroup_path = "/";
191
192         cg = hashmap_get(cgroups, cgroup_path);
193         if (!cg)
194                 return 0;
195
196         if (!hashmap_isempty(cg->pids)) {
197                 const char *name;
198                 size_t n = 0, i;
199                 pid_t *pids;
200                 void *pidp;
201                 Iterator j;
202                 int width;
203
204                 /* Order processes by their PID */
205                 pids = newa(pid_t, hashmap_size(cg->pids));
206
207                 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j)
208                         pids[n++] = PTR_TO_PID(pidp);
209
210                 assert(n == hashmap_size(cg->pids));
211                 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
212
213                 width = DECIMAL_STR_WIDTH(pids[n-1]);
214
215                 for (i = 0; i < n; i++) {
216                         _cleanup_free_ char *e = NULL;
217                         const char *special;
218                         bool more;
219
220                         name = hashmap_get(cg->pids, PID_TO_PTR(pids[i]));
221                         assert(name);
222
223                         if (n_columns != 0) {
224                                 unsigned k;
225
226                                 k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
227
228                                 e = ellipsize(name, k, 100);
229                                 if (e)
230                                         name = e;
231                         }
232
233                         more = i+1 < n || cg->children;
234                         special = draw_special_char(more ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT);
235
236                         fprintf(stdout, "%s%s%*"PID_PRI" %s\n",
237                                 prefix,
238                                 special,
239                                 width, pids[i],
240                                 name);
241                 }
242         }
243
244         if (cg->children) {
245                 struct CGroupInfo **children, *child;
246                 size_t n = 0, i;
247
248                 /* Order subcgroups by their name */
249                 children = newa(struct CGroupInfo*, cg->n_children);
250                 LIST_FOREACH(siblings, child, cg->children)
251                         children[n++] = child;
252                 assert(n == cg->n_children);
253                 qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func);
254
255                 n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
256
257                 for (i = 0; i < n; i++) {
258                         _cleanup_free_ char *pp = NULL;
259                         const char *name, *special;
260                         bool more;
261
262                         child = children[i];
263
264                         name = strrchr(child->cgroup_path, '/');
265                         if (!name)
266                                 return -EINVAL;
267                         name++;
268
269                         more = i+1 < n;
270                         special = draw_special_char(more ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT);
271
272                         fputs(prefix, stdout);
273                         fputs(special, stdout);
274                         fputs(name, stdout);
275                         fputc('\n', stdout);
276
277                         special = draw_special_char(more ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE);
278
279                         pp = strappend(prefix, special);
280                         if (!pp)
281                                 return -ENOMEM;
282
283                         r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags);
284                         if (r < 0)
285                                 return r;
286                 }
287         }
288
289         cg->done = true;
290         return 0;
291 }
292
293 static int dump_extra_processes(
294                 Hashmap *cgroups,
295                 const char *prefix,
296                 unsigned n_columns,
297                 OutputFlags flags) {
298
299         _cleanup_free_ pid_t *pids = NULL;
300         _cleanup_hashmap_free_ Hashmap *names = NULL;
301         struct CGroupInfo *cg;
302         size_t n_allocated = 0, n = 0, k;
303         Iterator i;
304         int width, r;
305
306         /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
307          * combined, sorted, linear list. */
308
309         HASHMAP_FOREACH(cg, cgroups, i) {
310                 const char *name;
311                 void *pidp;
312                 Iterator j;
313
314                 if (cg->done)
315                         continue;
316
317                 if (hashmap_isempty(cg->pids))
318                         continue;
319
320                 r = hashmap_ensure_allocated(&names, &trivial_hash_ops);
321                 if (r < 0)
322                         return r;
323
324                 if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids)))
325                         return -ENOMEM;
326
327                 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) {
328                         pids[n++] = PTR_TO_PID(pidp);
329
330                         r = hashmap_put(names, pidp, (void*) name);
331                         if (r < 0)
332                                 return r;
333                 }
334         }
335
336         if (n == 0)
337                 return 0;
338
339         qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
340         width = DECIMAL_STR_WIDTH(pids[n-1]);
341
342         for (k = 0; k < n; k++) {
343                 _cleanup_free_ char *e = NULL;
344                 const char *name;
345
346                 name = hashmap_get(names, PID_TO_PTR(pids[k]));
347                 assert(name);
348
349                 if (n_columns != 0) {
350                         unsigned z;
351
352                         z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
353
354                         e = ellipsize(name, z, 100);
355                         if (e)
356                                 name = e;
357                 }
358
359                 fprintf(stdout, "%s%s %*" PID_PRI " %s\n",
360                         prefix,
361                         draw_special_char(DRAW_TRIANGULAR_BULLET),
362                         width, pids[k],
363                         name);
364         }
365
366         return 0;
367 }
368
369 int unit_show_processes(
370                 sd_bus *bus,
371                 const char *unit,
372                 const char *cgroup_path,
373                 const char *prefix,
374                 unsigned n_columns,
375                 OutputFlags flags,
376                 sd_bus_error *error) {
377
378         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
379         Hashmap *cgroups = NULL;
380         struct CGroupInfo *cg;
381         int r;
382
383         assert(bus);
384         assert(unit);
385
386         if (flags & OUTPUT_FULL_WIDTH)
387                 n_columns = 0;
388         else if (n_columns <= 0)
389                 n_columns = columns();
390
391         prefix = strempty(prefix);
392
393         r = sd_bus_call_method(
394                         bus,
395                         "org.freedesktop.elogind1",
396                         "/org/freedesktop/elogind1",
397                         "org.freedesktop.elogind1.Manager",
398                         "GetUnitProcesses",
399                         error,
400                         &reply,
401                         "s",
402                         unit);
403         if (r < 0)
404                 return r;
405
406         cgroups = hashmap_new(&string_hash_ops);
407         if (!cgroups)
408                 return -ENOMEM;
409
410         r = sd_bus_message_enter_container(reply, 'a', "(sus)");
411         if (r < 0)
412                 goto finish;
413
414         for (;;) {
415                 const char *path = NULL, *name = NULL;
416                 uint32_t pid;
417
418                 r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name);
419                 if (r < 0)
420                         goto finish;
421                 if (r == 0)
422                         break;
423
424                 r = add_process(cgroups, path, pid, name);
425                 if (r < 0)
426                         goto finish;
427         }
428
429         r = sd_bus_message_exit_container(reply);
430         if (r < 0)
431                 goto finish;
432
433         r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags);
434         if (r < 0)
435                 goto finish;
436
437         r = dump_extra_processes(cgroups, prefix, n_columns, flags);
438
439 finish:
440         while ((cg = hashmap_first(cgroups)))
441                remove_cgroup(cgroups, cg);
442
443         hashmap_free(cgroups);
444
445         return r;
446 }