]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
ab35fb1b | 2 | |
fa776d8e LP |
3 | #include <dirent.h> |
4 | #include <errno.h> | |
a8fbdf54 | 5 | #include <stddef.h> |
07630cea | 6 | #include <stdio.h> |
a8fbdf54 | 7 | #include <stdlib.h> |
ab35fb1b | 8 | |
b5efdb8a | 9 | #include "alloc-util.h" |
049cb73b ZJS |
10 | #include "bus-error.h" |
11 | #include "bus-util.h" | |
3ffd4af2 | 12 | #include "cgroup-show.h" |
07630cea | 13 | #include "cgroup-util.h" |
686d13b9 | 14 | #include "env-file.h" |
5a5a5d29 | 15 | #include "escape.h" |
3ffd4af2 | 16 | #include "fd-util.h" |
f97b34a6 | 17 | #include "format-util.h" |
40d4320d | 18 | #include "hostname-util.h" |
8752c575 | 19 | #include "locale-util.h" |
ab35fb1b | 20 | #include "macro.h" |
5a5a5d29 | 21 | #include "nulstr-util.h" |
a8fbdf54 | 22 | #include "output-mode.h" |
74d8ccd4 | 23 | #include "parse-util.h" |
9eb977db | 24 | #include "path-util.h" |
07630cea | 25 | #include "process-util.h" |
760877e9 | 26 | #include "sort-util.h" |
07630cea | 27 | #include "string-util.h" |
288a74cc | 28 | #include "terminal-util.h" |
049cb73b | 29 | #include "unit-name.h" |
74d8ccd4 | 30 | #include "xattr-util.h" |
ab35fb1b | 31 | |
0ff308c8 LP |
32 | static void show_pid_array( |
33 | pid_t pids[], | |
34 | unsigned n_pids, | |
35 | const char *prefix, | |
bc28751e | 36 | size_t n_columns, |
0ff308c8 LP |
37 | bool extra, |
38 | bool more, | |
39 | OutputFlags flags) { | |
40 | ||
3da7a50f | 41 | unsigned i, j, pid_width; |
b69d29ce | 42 | |
da41abc5 | 43 | if (n_pids == 0) |
3637713a LP |
44 | return; |
45 | ||
93bab288 | 46 | typesafe_qsort(pids, n_pids, pid_compare_func); |
b69d29ce | 47 | |
3da7a50f ZJS |
48 | /* Filter duplicates */ |
49 | for (j = 0, i = 1; i < n_pids; i++) { | |
50 | if (pids[i] == pids[j]) | |
51 | continue; | |
52 | pids[++j] = pids[i]; | |
b69d29ce | 53 | } |
3da7a50f ZJS |
54 | n_pids = j + 1; |
55 | pid_width = DECIMAL_STR_WIDTH(pids[j]); | |
b69d29ce | 56 | |
b47d419c | 57 | if (flags & OUTPUT_FULL_WIDTH) |
bc28751e | 58 | n_columns = SIZE_MAX; |
9bdbc2e2 | 59 | else { |
31c294dc ZJS |
60 | if (n_columns > pid_width + 3) /* something like "├─1114784 " */ |
61 | n_columns -= pid_width + 3; | |
9bdbc2e2 LN |
62 | else |
63 | n_columns = 20; | |
64 | } | |
b69d29ce | 65 | for (i = 0; i < n_pids; i++) { |
b47d419c | 66 | _cleanup_free_ char *t = NULL; |
b69d29ce | 67 | |
e3b4efd2 ZJS |
68 | (void) get_process_cmdline(pids[i], n_columns, |
69 | PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_USE_LOCALE, | |
70 | &t); | |
b69d29ce | 71 | |
6b01f1d3 | 72 | if (extra) |
9a6f746f | 73 | printf("%s%s ", prefix, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); |
6b01f1d3 | 74 | else |
9a6f746f | 75 | printf("%s%s", prefix, special_glyph(((more || i < n_pids-1) ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT))); |
6b01f1d3 | 76 | |
f996072f | 77 | printf("%s%*"PID_PRI" %s%s\n", ansi_grey(), (int) pid_width, pids[i], strna(t), ansi_normal()); |
b69d29ce LP |
78 | } |
79 | } | |
80 | ||
291d565a LP |
81 | static int show_cgroup_one_by_path( |
82 | const char *path, | |
83 | const char *prefix, | |
bc28751e | 84 | size_t n_columns, |
291d565a | 85 | bool more, |
291d565a | 86 | OutputFlags flags) { |
b69d29ce | 87 | |
7027ff61 | 88 | _cleanup_free_ pid_t *pids = NULL; |
319a4f4b | 89 | _cleanup_fclose_ FILE *f = NULL; |
b47d419c | 90 | _cleanup_free_ char *p = NULL; |
319a4f4b | 91 | size_t n = 0; |
319a4f4b | 92 | char *fn; |
fa776d8e | 93 | int r; |
ab35fb1b | 94 | |
7027ff61 | 95 | r = cg_mangle_path(path, &p); |
b69d29ce | 96 | if (r < 0) |
35d2e7ec | 97 | return r; |
ab35fb1b | 98 | |
63c372cb | 99 | fn = strjoina(p, "/cgroup.procs"); |
fa776d8e | 100 | f = fopen(fn, "re"); |
ab35fb1b | 101 | if (!f) |
fa776d8e | 102 | return -errno; |
ab35fb1b | 103 | |
1fb50408 YW |
104 | for (;;) { |
105 | pid_t pid; | |
106 | ||
107 | /* libvirt / qemu uses threaded mode and cgroup.procs cannot be read at the lower levels. | |
0e685823 | 108 | * From https://docs.kernel.org/admin-guide/cgroup-v2.html#threads, |
1fb50408 YW |
109 | * “cgroup.procs” in a threaded domain cgroup contains the PIDs of all processes in |
110 | * the subtree and is not readable in the subtree proper. */ | |
111 | r = cg_read_pid(f, &pid); | |
112 | if (IN_SET(r, 0, -EOPNOTSUPP)) | |
113 | break; | |
114 | if (r < 0) | |
115 | return r; | |
ab35fb1b | 116 | |
0ff308c8 | 117 | if (!(flags & OUTPUT_KERNEL_THREADS) && is_kernel_thread(pid) > 0) |
1e5678d0 LP |
118 | continue; |
119 | ||
319a4f4b | 120 | if (!GREEDY_REALLOC(pids, n + 1)) |
3da7a50f | 121 | return -ENOMEM; |
ab35fb1b | 122 | |
c6c18be3 | 123 | pids[n++] = pid; |
ab35fb1b LP |
124 | } |
125 | ||
0ff308c8 | 126 | show_pid_array(pids, n, prefix, n_columns, false, more, flags); |
ab35fb1b | 127 | |
7027ff61 | 128 | return 0; |
fa776d8e LP |
129 | } |
130 | ||
bde26075 LP |
131 | static int is_delegated(int cgfd, const char *path) { |
132 | _cleanup_free_ char *b = NULL; | |
133 | int r; | |
134 | ||
135 | assert(cgfd >= 0 || path); | |
136 | ||
137 | r = getxattr_malloc(cgfd < 0 ? path : FORMAT_PROC_FD_PATH(cgfd), "trusted.delegate", &b); | |
00675c36 | 138 | if (r < 0 && ERRNO_IS_XATTR_ABSENT(r)) { |
d9bc1c36 LP |
139 | /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the |
140 | * assumption that whoever is trusted enough to own the cgroup, is also trusted enough to | |
141 | * decide if it is delegated or not this should be safe. */ | |
142 | r = getxattr_malloc(cgfd < 0 ? path : FORMAT_PROC_FD_PATH(cgfd), "user.delegate", &b); | |
00675c36 | 143 | if (r < 0 && ERRNO_IS_XATTR_ABSENT(r)) |
bde26075 | 144 | return false; |
bde26075 | 145 | } |
d9bc1c36 LP |
146 | if (r < 0) |
147 | return log_debug_errno(r, "Failed to read delegate xattr, ignoring: %m"); | |
bde26075 LP |
148 | |
149 | r = parse_boolean(b); | |
150 | if (r < 0) | |
d9bc1c36 | 151 | return log_debug_errno(r, "Failed to parse delegate xattr boolean value, ignoring: %m"); |
bde26075 LP |
152 | |
153 | return r; | |
154 | } | |
155 | ||
74d8ccd4 LP |
156 | static int show_cgroup_name( |
157 | const char *path, | |
158 | const char *prefix, | |
5a5a5d29 LP |
159 | SpecialGlyph glyph, |
160 | OutputFlags flags) { | |
74d8ccd4 | 161 | |
5a5a5d29 | 162 | uint64_t cgroupid = UINT64_MAX; |
74d8ccd4 | 163 | _cleanup_free_ char *b = NULL; |
254d1313 | 164 | _cleanup_close_ int fd = -EBADF; |
bde26075 | 165 | bool delegate; |
74d8ccd4 LP |
166 | int r; |
167 | ||
5a5a5d29 LP |
168 | if (FLAGS_SET(flags, OUTPUT_CGROUP_XATTRS) || FLAGS_SET(flags, OUTPUT_CGROUP_ID)) { |
169 | fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY, 0); | |
170 | if (fd < 0) | |
171 | log_debug_errno(errno, "Failed to open cgroup '%s', ignoring: %m", path); | |
172 | } | |
173 | ||
bde26075 | 174 | delegate = is_delegated(fd, path) > 0; |
74d8ccd4 | 175 | |
5a5a5d29 LP |
176 | if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) { |
177 | cg_file_handle fh = CG_FILE_HANDLE_INIT; | |
178 | int mnt_id = -1; | |
179 | ||
180 | if (name_to_handle_at( | |
181 | fd < 0 ? AT_FDCWD : fd, | |
182 | fd < 0 ? path : "", | |
183 | &fh.file_handle, | |
184 | &mnt_id, | |
185 | fd < 0 ? 0 : AT_EMPTY_PATH) < 0) | |
186 | log_debug_errno(errno, "Failed to determine cgroup ID of %s, ignoring: %m", path); | |
187 | else | |
188 | cgroupid = CG_FILE_HANDLE_CGROUPID(fh); | |
189 | } | |
190 | ||
191 | r = path_extract_filename(path, &b); | |
192 | if (r < 0) | |
193 | return log_error_errno(r, "Failed to extract filename from cgroup path: %m"); | |
74d8ccd4 | 194 | |
5a5a5d29 LP |
195 | printf("%s%s%s%s%s", |
196 | prefix, special_glyph(glyph), | |
74d8ccd4 LP |
197 | delegate ? ansi_underline() : "", |
198 | cg_unescape(b), | |
74d8ccd4 | 199 | delegate ? ansi_normal() : ""); |
5a5a5d29 LP |
200 | |
201 | if (delegate) | |
202 | printf(" %s%s%s", | |
203 | ansi_highlight(), | |
204 | special_glyph(SPECIAL_GLYPH_ELLIPSIS), | |
205 | ansi_normal()); | |
206 | ||
207 | if (cgroupid != UINT64_MAX) | |
208 | printf(" %s(#%" PRIu64 ")%s", ansi_grey(), cgroupid, ansi_normal()); | |
209 | ||
210 | printf("\n"); | |
211 | ||
212 | if (FLAGS_SET(flags, OUTPUT_CGROUP_XATTRS) && fd >= 0) { | |
213 | _cleanup_free_ char *nl = NULL; | |
5a5a5d29 LP |
214 | |
215 | r = flistxattr_malloc(fd, &nl); | |
216 | if (r < 0) | |
217 | log_debug_errno(r, "Failed to enumerate xattrs on '%s', ignoring: %m", path); | |
218 | ||
219 | NULSTR_FOREACH(xa, nl) { | |
220 | _cleanup_free_ char *x = NULL, *y = NULL, *buf = NULL; | |
221 | int n; | |
222 | ||
223 | if (!STARTSWITH_SET(xa, "user.", "trusted.")) | |
224 | continue; | |
225 | ||
226 | n = fgetxattr_malloc(fd, xa, &buf); | |
227 | if (n < 0) { | |
228 | log_debug_errno(r, "Failed to read xattr '%s' off '%s', ignoring: %m", xa, path); | |
229 | continue; | |
230 | } | |
231 | ||
232 | x = cescape(xa); | |
233 | if (!x) | |
234 | return -ENOMEM; | |
235 | ||
236 | y = cescape_length(buf, n); | |
237 | if (!y) | |
238 | return -ENOMEM; | |
239 | ||
240 | printf("%s%s%s %s%s%s: %s\n", | |
241 | prefix, | |
242 | glyph == SPECIAL_GLYPH_TREE_BRANCH ? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL) : " ", | |
fc03e80c | 243 | special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), |
5a5a5d29 LP |
244 | ansi_blue(), x, ansi_normal(), |
245 | y); | |
246 | } | |
247 | } | |
248 | ||
74d8ccd4 LP |
249 | return 0; |
250 | } | |
251 | ||
291d565a LP |
252 | int show_cgroup_by_path( |
253 | const char *path, | |
254 | const char *prefix, | |
bc28751e | 255 | size_t n_columns, |
291d565a LP |
256 | OutputFlags flags) { |
257 | ||
7027ff61 LP |
258 | _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; |
259 | _cleanup_closedir_ DIR *d = NULL; | |
fa776d8e | 260 | bool shown_pids = false; |
74d8ccd4 | 261 | char *gn = NULL; |
fa776d8e LP |
262 | int r; |
263 | ||
b69d29ce LP |
264 | assert(path); |
265 | ||
fa776d8e LP |
266 | if (n_columns <= 0) |
267 | n_columns = columns(); | |
268 | ||
291d565a | 269 | prefix = strempty(prefix); |
fa776d8e | 270 | |
7027ff61 | 271 | r = cg_mangle_path(path, &fn); |
c3175a7f | 272 | if (r < 0) |
35d2e7ec | 273 | return r; |
fa776d8e | 274 | |
c3175a7f | 275 | d = opendir(fn); |
7027ff61 | 276 | if (!d) |
fa776d8e | 277 | return -errno; |
fa776d8e | 278 | |
35d2e7ec | 279 | while ((r = cg_read_subgroup(d, &gn)) > 0) { |
7027ff61 | 280 | _cleanup_free_ char *k = NULL; |
c3175a7f | 281 | |
657ee2d8 | 282 | k = path_join(fn, gn); |
c3175a7f | 283 | free(gn); |
7027ff61 LP |
284 | if (!k) |
285 | return -ENOMEM; | |
c3175a7f | 286 | |
6f883237 | 287 | if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0) |
c3175a7f | 288 | continue; |
fa776d8e LP |
289 | |
290 | if (!shown_pids) { | |
0ff308c8 | 291 | show_cgroup_one_by_path(path, prefix, n_columns, true, flags); |
fa776d8e LP |
292 | shown_pids = true; |
293 | } | |
294 | ||
295 | if (last) { | |
5a5a5d29 | 296 | r = show_cgroup_name(last, prefix, SPECIAL_GLYPH_TREE_BRANCH, flags); |
74d8ccd4 LP |
297 | if (r < 0) |
298 | return r; | |
fa776d8e | 299 | |
c3175a7f | 300 | if (!p1) { |
b910cc72 | 301 | p1 = strjoin(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL)); |
7027ff61 LP |
302 | if (!p1) |
303 | return -ENOMEM; | |
c3175a7f | 304 | } |
fa776d8e | 305 | |
0ff308c8 | 306 | show_cgroup_by_path(last, p1, n_columns-2, flags); |
fa776d8e | 307 | free(last); |
fa776d8e LP |
308 | } |
309 | ||
ae2a15bc | 310 | last = TAKE_PTR(k); |
fa776d8e LP |
311 | } |
312 | ||
35d2e7ec | 313 | if (r < 0) |
7027ff61 | 314 | return r; |
35d2e7ec | 315 | |
fa776d8e | 316 | if (!shown_pids) |
0ff308c8 | 317 | show_cgroup_one_by_path(path, prefix, n_columns, !!last, flags); |
fa776d8e LP |
318 | |
319 | if (last) { | |
5a5a5d29 | 320 | r = show_cgroup_name(last, prefix, SPECIAL_GLYPH_TREE_RIGHT, flags); |
74d8ccd4 LP |
321 | if (r < 0) |
322 | return r; | |
fa776d8e | 323 | |
c3175a7f | 324 | if (!p2) { |
b910cc72 | 325 | p2 = strjoin(prefix, " "); |
7027ff61 LP |
326 | if (!p2) |
327 | return -ENOMEM; | |
c3175a7f | 328 | } |
fa776d8e | 329 | |
0ff308c8 | 330 | show_cgroup_by_path(last, p2, n_columns-2, flags); |
fa776d8e LP |
331 | } |
332 | ||
7027ff61 | 333 | return 0; |
ab35fb1b | 334 | } |
35d2e7ec | 335 | |
291d565a LP |
336 | int show_cgroup(const char *controller, |
337 | const char *path, | |
338 | const char *prefix, | |
bc28751e | 339 | size_t n_columns, |
291d565a | 340 | OutputFlags flags) { |
7027ff61 | 341 | _cleanup_free_ char *p = NULL; |
35d2e7ec LP |
342 | int r; |
343 | ||
35d2e7ec LP |
344 | assert(path); |
345 | ||
1e5678d0 LP |
346 | r = cg_get_path(controller, path, NULL, &p); |
347 | if (r < 0) | |
35d2e7ec LP |
348 | return r; |
349 | ||
0ff308c8 | 350 | return show_cgroup_by_path(p, prefix, n_columns, flags); |
35d2e7ec | 351 | } |
b69d29ce | 352 | |
291d565a LP |
353 | static int show_extra_pids( |
354 | const char *controller, | |
355 | const char *path, | |
356 | const char *prefix, | |
bc28751e | 357 | size_t n_columns, |
291d565a LP |
358 | const pid_t pids[], |
359 | unsigned n_pids, | |
360 | OutputFlags flags) { | |
361 | ||
7fd1b19b | 362 | _cleanup_free_ pid_t *copy = NULL; |
b69d29ce LP |
363 | unsigned i, j; |
364 | int r; | |
365 | ||
b69d29ce LP |
366 | assert(path); |
367 | ||
368 | if (n_pids <= 0) | |
369 | return 0; | |
370 | ||
371 | if (n_columns <= 0) | |
372 | n_columns = columns(); | |
373 | ||
7027ff61 | 374 | prefix = strempty(prefix); |
b69d29ce LP |
375 | |
376 | copy = new(pid_t, n_pids); | |
377 | if (!copy) | |
378 | return -ENOMEM; | |
379 | ||
380 | for (i = 0, j = 0; i < n_pids; i++) { | |
7fd1b19b | 381 | _cleanup_free_ char *k = NULL; |
b69d29ce | 382 | |
7027ff61 | 383 | r = cg_pid_get_path(controller, pids[i], &k); |
050fbdb8 | 384 | if (r < 0) |
b69d29ce | 385 | return r; |
b69d29ce LP |
386 | |
387 | if (path_startswith(k, path)) | |
388 | continue; | |
389 | ||
390 | copy[j++] = pids[i]; | |
391 | } | |
392 | ||
0ff308c8 | 393 | show_pid_array(copy, j, prefix, n_columns, true, false, flags); |
b69d29ce | 394 | |
b69d29ce LP |
395 | return 0; |
396 | } | |
397 | ||
291d565a LP |
398 | int show_cgroup_and_extra( |
399 | const char *controller, | |
400 | const char *path, | |
401 | const char *prefix, | |
bc28751e | 402 | size_t n_columns, |
291d565a LP |
403 | const pid_t extra_pids[], |
404 | unsigned n_extra_pids, | |
405 | OutputFlags flags) { | |
406 | ||
b69d29ce LP |
407 | int r; |
408 | ||
b69d29ce LP |
409 | assert(path); |
410 | ||
0ff308c8 | 411 | r = show_cgroup(controller, path, prefix, n_columns, flags); |
b69d29ce LP |
412 | if (r < 0) |
413 | return r; | |
414 | ||
9bdbc2e2 | 415 | return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); |
b69d29ce LP |
416 | } |
417 | ||
bc06be75 ZJS |
418 | int show_cgroup_get_unit_path_and_warn( |
419 | sd_bus *bus, | |
420 | const char *unit, | |
421 | char **ret) { | |
422 | ||
423 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
424 | _cleanup_free_ char *path = NULL; | |
425 | int r; | |
426 | ||
427 | path = unit_dbus_path_from_name(unit); | |
428 | if (!path) | |
429 | return log_oom(); | |
430 | ||
431 | r = sd_bus_get_property_string( | |
432 | bus, | |
433 | "org.freedesktop.systemd1", | |
434 | path, | |
435 | unit_dbus_interface_from_name(unit), | |
436 | "ControlGroup", | |
437 | &error, | |
438 | ret); | |
439 | if (r < 0) | |
440 | return log_error_errno(r, "Failed to query unit control group path: %s", | |
441 | bus_error_message(&error, r)); | |
442 | ||
443 | return 0; | |
444 | } | |
445 | ||
d3e8277d | 446 | int show_cgroup_get_path_and_warn( |
049cb73b | 447 | const char *machine, |
d3e8277d | 448 | const char *prefix, |
049cb73b ZJS |
449 | char **ret) { |
450 | ||
d3e8277d | 451 | _cleanup_free_ char *root = NULL; |
40d4320d | 452 | int r; |
d3e8277d ZJS |
453 | |
454 | if (machine) { | |
d3e8277d | 455 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; |
bc06be75 | 456 | _cleanup_free_ char *unit = NULL; |
d3e8277d ZJS |
457 | const char *m; |
458 | ||
40d4320d LP |
459 | if (!hostname_is_valid(machine, 0)) |
460 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine name is not valid: %s", machine); | |
461 | ||
d3e8277d | 462 | m = strjoina("/run/systemd/machines/", machine); |
13df9c39 | 463 | r = parse_env_file(NULL, m, "SCOPE", &unit); |
d3e8277d ZJS |
464 | if (r < 0) |
465 | return log_error_errno(r, "Failed to load machine data: %m"); | |
049cb73b | 466 | |
d3e8277d ZJS |
467 | r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); |
468 | if (r < 0) | |
10a7340a | 469 | return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL); |
d3e8277d | 470 | |
bc06be75 | 471 | r = show_cgroup_get_unit_path_and_warn(bus, unit, &root); |
d3e8277d | 472 | if (r < 0) |
bc06be75 | 473 | return r; |
d3e8277d ZJS |
474 | } else { |
475 | r = cg_get_root_path(&root); | |
049cb73b ZJS |
476 | if (r == -ENOMEDIUM) |
477 | return log_error_errno(r, "Failed to get root control group path.\n" | |
478 | "No cgroup filesystem mounted on /sys/fs/cgroup"); | |
aa13d384 | 479 | if (r < 0) |
049cb73b | 480 | return log_error_errno(r, "Failed to get root control group path: %m"); |
049cb73b ZJS |
481 | } |
482 | ||
d3e8277d ZJS |
483 | if (prefix) { |
484 | char *t; | |
049cb73b | 485 | |
aa13d384 | 486 | t = path_join(root, prefix); |
d3e8277d ZJS |
487 | if (!t) |
488 | return log_oom(); | |
049cb73b | 489 | |
d3e8277d | 490 | *ret = t; |
1cc6c93a YW |
491 | } else |
492 | *ret = TAKE_PTR(root); | |
049cb73b ZJS |
493 | |
494 | return 0; | |
495 | } |