]>
Commit | Line | Data |
---|---|---|
32c21c76 KZ |
1 | /* |
2 | * No copyright is claimed. This code is in the public domain; do with | |
3 | * it what you wish. | |
4 | * | |
79feaa60 | 5 | * Copyright (C) 2021 Karel Zak <kzak@redhat.com> |
32c21c76 KZ |
6 | */ |
7 | #include <ctype.h> | |
8 | #include <unistd.h> | |
32c21c76 KZ |
9 | #include <errno.h> |
10 | ||
3671d4a8 KZ |
11 | #ifdef HAVE_SYS_VFS_H |
12 | # include <sys/vfs.h> | |
13 | # include "statfs_magic.h" | |
14 | #endif | |
15 | ||
32c21c76 KZ |
16 | #include "c.h" |
17 | #include "pathnames.h" | |
18 | #include "procfs.h" | |
19 | #include "fileutils.h" | |
20 | #include "all-io.h" | |
21 | #include "debug.h" | |
22 | #include "strutils.h" | |
32c21c76 KZ |
23 | |
24 | static void procfs_process_deinit_path(struct path_cxt *pc); | |
25 | ||
26 | /* | |
27 | * Debug stuff (based on include/debug.h) | |
28 | */ | |
29 | static UL_DEBUG_DEFINE_MASK(ulprocfs); | |
30 | UL_DEBUG_DEFINE_MASKNAMES(ulprocfs) = UL_DEBUG_EMPTY_MASKNAMES; | |
31 | ||
32 | #define ULPROCFS_DEBUG_INIT (1 << 1) | |
33 | #define ULPROCFS_DEBUG_CXT (1 << 2) | |
34 | ||
35 | #define DBG(m, x) __UL_DBG(ulprocfs, ULPROCFS_DEBUG_, m, x) | |
36 | #define ON_DBG(m, x) __UL_DBG_CALL(ulprocfs, ULPROCFS_DEBUG_, m, x) | |
37 | ||
38 | #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulprocfs) | |
39 | #include "debugobj.h" | |
40 | ||
41 | void ul_procfs_init_debug(void) | |
42 | { | |
43 | if (ulprocfs_debug_mask) | |
44 | return; | |
45 | __UL_INIT_DEBUG_FROM_ENV(ulprocfs, ULPROCFS_DEBUG_, 0, ULPROCFS_DEBUG); | |
46 | } | |
47 | ||
48 | struct path_cxt *ul_new_procfs_path(pid_t pid, const char *prefix) | |
49 | { | |
50 | struct path_cxt *pc = ul_new_path(NULL); | |
51 | ||
52 | if (!pc) | |
53 | return NULL; | |
54 | if (prefix) | |
55 | ul_path_set_prefix(pc, prefix); | |
56 | ||
57 | if (procfs_process_init_path(pc, pid) != 0) { | |
58 | ul_unref_path(pc); | |
59 | return NULL; | |
60 | } | |
61 | ||
62 | DBG(CXT, ul_debugobj(pc, "alloc")); | |
63 | return pc; | |
64 | } | |
65 | ||
66 | /* | |
67 | * procfs_blkdev_* is procfs extension to ul_path_* API to read info about process. | |
68 | * | |
69 | * The function is possible to call in loop and without sysfs_procfs_deinit_path(). | |
70 | * The procfs_process_deinit_path() is automatically called by ul_unref_path(). | |
71 | * | |
72 | */ | |
73 | int procfs_process_init_path(struct path_cxt *pc, pid_t pid) | |
74 | { | |
75 | struct procfs_process *prc; | |
76 | int rc; | |
77 | char buf[sizeof(_PATH_PROC) + sizeof(stringify_value(UINT32_MAX)) + 2]; | |
78 | ||
79 | /* define path to pid stuff */ | |
80 | snprintf(buf, sizeof(buf), _PATH_PROC "/%zu", (size_t) pid); | |
81 | rc = ul_path_set_dir(pc, buf); | |
82 | if (rc) | |
83 | return rc; | |
84 | ||
85 | /* make sure path exists */ | |
86 | rc = ul_path_get_dirfd(pc); | |
87 | if (rc < 0) | |
88 | return rc; | |
89 | ||
90 | /* initialize procfs specific stuff */ | |
91 | prc = ul_path_get_dialect(pc); | |
92 | if (!prc) { | |
93 | DBG(CXT, ul_debugobj(pc, "alloc new procfs handler")); | |
94 | prc = calloc(1, sizeof(struct procfs_process)); | |
95 | if (!prc) | |
96 | return -ENOMEM; | |
97 | ||
98 | ul_path_set_dialect(pc, prc, procfs_process_deinit_path); | |
99 | } | |
100 | ||
101 | DBG(CXT, ul_debugobj(pc, "init procfs stuff")); | |
102 | ||
103 | prc->pid = pid; | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static void procfs_process_deinit_path(struct path_cxt *pc) | |
108 | { | |
109 | struct procfs_process *prc; | |
110 | ||
111 | if (!pc) | |
112 | return; | |
113 | ||
114 | DBG(CXT, ul_debugobj(pc, "deinit")); | |
115 | ||
116 | prc = ul_path_get_dialect(pc); | |
117 | if (!prc) | |
118 | return; | |
119 | ||
120 | free(prc); | |
121 | ul_path_set_dialect(pc, NULL, NULL); | |
122 | } | |
123 | ||
124 | static ssize_t read_procfs_file(int fd, char *buf, size_t bufsz) | |
125 | { | |
126 | ssize_t sz = 0; | |
127 | size_t i; | |
128 | ||
129 | if (fd < 0) | |
130 | return -EINVAL; | |
131 | ||
132 | sz = read_all(fd, buf, bufsz); | |
133 | if (sz <= 0) | |
134 | return sz; | |
135 | ||
136 | for (i = 0; i < (size_t) sz; i++) { | |
137 | if (buf[i] == '\0') | |
138 | buf[i] = ' '; | |
139 | } | |
140 | buf[sz - 1] = '\0'; | |
141 | return sz; | |
142 | } | |
143 | ||
93c0d607 | 144 | static ssize_t procfs_process_get_data_for(struct path_cxt *pc, char *buf, size_t bufsz, |
f26ddd08 | 145 | const char *fname) |
32c21c76 | 146 | { |
f26ddd08 | 147 | int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, fname); |
32c21c76 KZ |
148 | |
149 | if (fd >= 0) { | |
150 | ssize_t sz = read_procfs_file(fd, buf, bufsz); | |
151 | close(fd); | |
152 | return sz; | |
153 | } | |
154 | return -errno; | |
155 | } | |
156 | ||
f26ddd08 MY |
157 | ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz) |
158 | { | |
93c0d607 | 159 | return procfs_process_get_data_for(pc, buf, bufsz, "cmdline"); |
f26ddd08 MY |
160 | } |
161 | ||
32c21c76 KZ |
162 | ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz) |
163 | { | |
93c0d607 | 164 | return procfs_process_get_data_for(pc, buf, bufsz, "comm"); |
f26ddd08 | 165 | } |
32c21c76 | 166 | |
f26ddd08 MY |
167 | ssize_t procfs_process_get_stat(struct path_cxt *pc, char *buf, size_t bufsz) |
168 | { | |
93c0d607 | 169 | return procfs_process_get_data_for(pc, buf, bufsz, "stat"); |
32c21c76 KZ |
170 | } |
171 | ||
41f2a508 MY |
172 | ssize_t procfs_process_get_syscall(struct path_cxt *pc, char *buf, size_t bufsz) |
173 | { | |
174 | return procfs_process_get_data_for(pc, buf, bufsz, "syscall"); | |
175 | } | |
176 | ||
c2ece66e KZ |
177 | int procfs_process_get_stat_nth(struct path_cxt *pc, int n, uintmax_t *re) |
178 | { | |
179 | ssize_t rc; | |
180 | char *key = NULL, *tok, *p; | |
181 | char buf[BUFSIZ]; | |
182 | int i; | |
183 | ||
184 | if (n == 2 || n == 3) /* process name and status (strings) */ | |
185 | return -EINVAL; | |
186 | ||
93c0d607 | 187 | rc = procfs_process_get_data_for(pc, buf, sizeof(buf), "stat"); |
c2ece66e KZ |
188 | if (rc < 0) |
189 | return rc; | |
190 | ||
191 | for (i = 0, tok = strtok_r(buf, " ", &key); tok; | |
192 | tok = strtok_r(NULL, " ", &key)) { | |
193 | ||
194 | i++; | |
195 | if (i == n) | |
196 | return ul_strtou64(tok, re, 10); | |
197 | ||
198 | /* skip rest of the process name */ | |
4848c9c0 | 199 | if (i == 2 && (p = strrchr(key, ')'))) |
c2ece66e KZ |
200 | key = p + 2; |
201 | } | |
202 | ||
203 | return -EINVAL; | |
204 | } | |
205 | ||
32c21c76 KZ |
206 | int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid) |
207 | { | |
208 | struct stat sb; | |
209 | int rc; | |
210 | ||
db9ad223 | 211 | if ((rc = ul_path_stat(pc, &sb, 0, NULL)) == 0) |
32c21c76 KZ |
212 | *uid = sb.st_uid; |
213 | return rc; | |
214 | } | |
215 | ||
216 | /* | |
217 | * returns the next task TID, the @sub is automatically initialized | |
218 | * when called first time and closed after last call or you can | |
219 | * call closedir()* when you need to break the loop. | |
220 | * | |
221 | * Returns: <0 on error, 0 on success, >1 done | |
222 | * | |
223 | * Example: | |
224 | * | |
225 | * pid_t tid; | |
226 | * DIR *sub = NULL; | |
227 | * path_cxt *pc = ul_new_procfs_path(123, NULL); | |
228 | * | |
229 | * while (procfs_process_next_tid(pc, &sub, &tid) == 0) | |
230 | * printf("task: %d", (int) tid); | |
231 | * | |
232 | */ | |
233 | int procfs_process_next_tid(struct path_cxt *pc, DIR **sub, pid_t *tid) | |
234 | { | |
235 | struct dirent *d; | |
236 | ||
237 | if (!pc || !sub || !tid) | |
238 | return -EINVAL; | |
239 | ||
240 | if (!*sub) { | |
241 | *sub = ul_path_opendir(pc, "task"); | |
242 | if (!*sub) | |
243 | return -errno; | |
244 | } | |
245 | ||
246 | while ((d = xreaddir(*sub))) { | |
247 | if (procfs_dirent_get_pid(d, tid) == 0) | |
248 | return 0; | |
249 | } | |
250 | ||
251 | closedir(*sub); | |
252 | *sub = NULL; | |
253 | return 1; | |
254 | } | |
255 | ||
256 | int procfs_process_next_fd(struct path_cxt *pc, DIR **sub, int *fd) | |
257 | { | |
258 | struct dirent *d; | |
259 | ||
260 | if (!pc || !sub || !fd) | |
261 | return -EINVAL; | |
262 | ||
263 | if (!*sub) { | |
264 | *sub = ul_path_opendir(pc, "fd"); | |
265 | if (!*sub) | |
266 | return -errno; | |
267 | } | |
268 | ||
269 | while ((d = xreaddir(*sub))) { | |
270 | uint64_t num; | |
271 | #ifdef _DIRENT_HAVE_D_TYPE | |
272 | if (d->d_type != DT_LNK && d->d_type != DT_UNKNOWN) | |
273 | continue; | |
274 | #endif | |
275 | if (ul_strtou64(d->d_name, &num, 10) < 0) | |
276 | continue; | |
277 | *fd = num; | |
278 | return 0; | |
279 | } | |
280 | ||
281 | closedir(*sub); | |
282 | *sub = NULL; | |
283 | return 1; | |
284 | } | |
285 | ||
286 | /* | |
287 | * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill | |
288 | */ | |
289 | ||
290 | /* stupid, but good enough as a basic filter */ | |
291 | int procfs_dirent_is_process(struct dirent *d) | |
292 | { | |
293 | #ifdef _DIRENT_HAVE_D_TYPE | |
294 | if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN) | |
295 | return 0; | |
296 | #endif | |
297 | if (!isdigit((unsigned char) *d->d_name)) | |
298 | return 0; | |
299 | ||
300 | return 1; | |
301 | } | |
302 | ||
303 | int procfs_dirent_get_pid(struct dirent *d, pid_t *pid) | |
304 | { | |
305 | uint64_t num; | |
306 | ||
307 | if (!procfs_dirent_is_process(d)) | |
308 | return -EINVAL; | |
309 | ||
310 | if (ul_strtou64(d->d_name, &num, 10) < 0) | |
311 | return -EINVAL; | |
312 | ||
313 | *pid = (pid_t) num; | |
314 | return 0; | |
315 | } | |
316 | ||
317 | int procfs_dirent_get_uid(DIR *procfs, struct dirent *d, uid_t *uid) | |
318 | { | |
319 | struct stat st; | |
320 | ||
321 | if (!procfs_dirent_is_process(d)) | |
322 | return -EINVAL; | |
323 | ||
324 | if (fstatat(dirfd(procfs), d->d_name, &st, 0)) | |
325 | return -EINVAL; | |
326 | ||
327 | *uid = st.st_uid; | |
328 | return 0; | |
329 | } | |
330 | ||
331 | int procfs_dirent_match_uid(DIR *procfs, struct dirent *d, uid_t uid) | |
332 | { | |
333 | uid_t x; | |
334 | ||
335 | if (procfs_dirent_get_uid(procfs, d, &x) == 0) | |
336 | return x == uid; | |
337 | ||
338 | return 0; | |
339 | } | |
340 | ||
341 | /* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME. | |
342 | * The minimal of the @buf has to be 32 bytes. */ | |
343 | int procfs_dirent_get_name(DIR *procfs, struct dirent *d, char *buf, size_t bufsz) | |
344 | { | |
345 | FILE *f; | |
346 | size_t sz; | |
347 | char tmp[1024], *p, *end = NULL; | |
348 | ||
349 | if (bufsz < 32) | |
350 | return -EINVAL; | |
351 | if (!procfs_dirent_is_process(d)) | |
352 | return -EINVAL; | |
353 | ||
354 | snprintf(tmp, sizeof(tmp), "%s/stat", d->d_name); | |
355 | f = fopen_at(dirfd(procfs), tmp, O_CLOEXEC|O_RDONLY, "r"); | |
356 | if (!f) | |
357 | return -errno; | |
358 | ||
359 | p = fgets(tmp, sizeof(tmp), f); | |
360 | fclose(f); | |
361 | if (!p) | |
362 | return -errno; | |
363 | ||
364 | /* skip PID */ | |
365 | while (*p && *p != '(') | |
366 | p++; | |
367 | ||
368 | /* skip extra '(' */ | |
369 | while (*p && *p == '(') | |
370 | p++; | |
371 | ||
372 | end = p; | |
373 | while (*end && *end != ')') | |
374 | end++; | |
375 | ||
376 | sz = end - p; | |
d8493779 | 377 | if (sz >= bufsz) |
32c21c76 KZ |
378 | sz = bufsz - 1; |
379 | ||
380 | memcpy(buf, p, sz); | |
381 | buf[sz] = '\0'; | |
382 | ||
383 | return 0; | |
384 | } | |
385 | ||
386 | int procfs_dirent_match_name(DIR *procfs, struct dirent *d, const char *name) | |
387 | { | |
388 | char buf[33]; | |
389 | ||
390 | if (procfs_dirent_get_name(procfs, d, buf, sizeof(buf)) == 0) | |
391 | return strcmp(name, buf) == 0; | |
392 | ||
393 | return 0; | |
394 | } | |
395 | ||
3671d4a8 | 396 | #ifdef HAVE_SYS_VFS_H |
32c21c76 KZ |
397 | /* checks if fd is file in a procfs; |
398 | * returns 1 if true, 0 if false or couldn't determine */ | |
399 | int fd_is_procfs(int fd) | |
400 | { | |
401 | struct statfs st; | |
402 | int ret; | |
403 | ||
404 | do { | |
405 | errno = 0; | |
406 | ret = fstatfs(fd, &st); | |
407 | ||
408 | if (ret < 0) { | |
409 | if (errno != EINTR && errno != EAGAIN) | |
410 | return 0; | |
411 | xusleep(250000); | |
412 | } | |
413 | } while (ret != 0); | |
414 | ||
415 | return st.f_type == STATFS_PROC_MAGIC; | |
3671d4a8 | 416 | return 0; |
32c21c76 | 417 | } |
3671d4a8 KZ |
418 | #else |
419 | int fd_is_procfs(int fd __attribute__((__unused__))) | |
420 | { | |
421 | return 0; | |
422 | } | |
423 | #endif | |
32c21c76 KZ |
424 | |
425 | static char *strdup_procfs_file(pid_t pid, const char *name) | |
426 | { | |
427 | char buf[BUFSIZ]; | |
428 | char *re = NULL; | |
429 | int fd; | |
430 | ||
431 | snprintf(buf, sizeof(buf), _PATH_PROC "/%d/%s", (int) pid, name); | |
432 | fd = open(buf, O_CLOEXEC|O_RDONLY); | |
433 | if (fd < 0) | |
434 | return NULL; | |
435 | ||
436 | if (read_procfs_file(fd, buf, sizeof(buf)) > 0) | |
437 | re = strdup(buf); | |
438 | close(fd); | |
439 | return re; | |
440 | } | |
441 | ||
442 | char *pid_get_cmdname(pid_t pid) | |
443 | { | |
444 | return strdup_procfs_file(pid, "comm"); | |
445 | } | |
446 | ||
447 | char *pid_get_cmdline(pid_t pid) | |
448 | { | |
449 | return strdup_procfs_file(pid, "cmdline"); | |
450 | } | |
451 | ||
452 | #ifdef TEST_PROGRAM_PROCFS | |
453 | ||
a446842f | 454 | static int test_tasks(int argc, char *argv[], const char *prefix) |
32c21c76 KZ |
455 | { |
456 | DIR *sub = NULL; | |
457 | struct path_cxt *pc; | |
458 | pid_t tid = 0, pid; | |
459 | ||
460 | if (argc != 2) | |
461 | return EXIT_FAILURE; | |
462 | ||
463 | pid = strtol(argv[1], (char **) NULL, 10); | |
464 | printf("PID=%d, TIDs:", pid); | |
465 | ||
a446842f | 466 | pc = ul_new_procfs_path(pid, prefix); |
32c21c76 KZ |
467 | if (!pc) |
468 | err(EXIT_FAILURE, "alloc procfs handler failed"); | |
469 | ||
470 | while (procfs_process_next_tid(pc, &sub, &tid) == 0) | |
471 | printf(" %d", tid); | |
472 | ||
473 | printf("\n"); | |
474 | ul_unref_path(pc); | |
475 | return EXIT_SUCCESS; | |
476 | } | |
477 | ||
a446842f | 478 | static int test_fds(int argc, char *argv[], const char *prefix) |
32c21c76 KZ |
479 | { |
480 | DIR *sub = NULL; | |
481 | struct path_cxt *pc; | |
482 | pid_t pid; | |
483 | int fd = -1; | |
484 | ||
485 | if (argc != 2) | |
486 | return EXIT_FAILURE; | |
487 | ||
488 | pid = strtol(argv[1], (char **) NULL, 10); | |
489 | printf("PID=%d, FDs:", pid); | |
490 | ||
a446842f | 491 | pc = ul_new_procfs_path(pid, prefix); |
32c21c76 KZ |
492 | if (!pc) |
493 | err(EXIT_FAILURE, "alloc procfs handler failed"); | |
494 | ||
495 | while (procfs_process_next_fd(pc, &sub, &fd) == 0) | |
496 | printf(" %d", fd); | |
497 | ||
498 | fputc('\n', stdout); | |
499 | ul_unref_path(pc); | |
500 | return EXIT_SUCCESS; | |
501 | } | |
502 | ||
503 | static int test_processes(int argc, char *argv[]) | |
504 | { | |
505 | DIR *dir; | |
506 | struct dirent *d; | |
507 | char *name = NULL; | |
508 | uid_t uid = (uid_t) -1; | |
509 | char buf[128]; | |
510 | ||
511 | if (argc >= 3 && strcmp(argv[1], "--name") == 0) | |
512 | name = argv[2]; | |
513 | if (argc >= 3 && strcmp(argv[1], "--uid") == 0) | |
514 | uid = (uid_t) atol(argv[2]); | |
515 | ||
516 | dir = opendir(_PATH_PROC); | |
517 | if (!dir) | |
518 | err(EXIT_FAILURE, "cannot open proc"); | |
519 | ||
520 | while ((d = xreaddir(dir))) { | |
521 | pid_t pid = 0; | |
522 | ||
523 | if (procfs_dirent_get_pid(d, &pid) != 0) | |
524 | continue; | |
525 | if (name && !procfs_dirent_match_name(dir, d, name)) | |
526 | continue; | |
527 | if (uid != (uid_t) -1 && !procfs_dirent_match_uid(dir, d, uid)) | |
528 | continue; | |
529 | procfs_dirent_get_name(dir, d, buf, sizeof(buf)); | |
530 | printf(" %d [%s]", pid, buf); | |
531 | } | |
532 | ||
533 | fputc('\n', stdout); | |
534 | closedir(dir); | |
535 | return EXIT_SUCCESS; | |
536 | } | |
537 | ||
a446842f | 538 | static int test_one_process(int argc, char *argv[], const char *prefix) |
32c21c76 KZ |
539 | { |
540 | pid_t pid; | |
541 | struct path_cxt *pc; | |
542 | char buf[BUFSIZ]; | |
543 | uid_t uid = (uid_t) -1; | |
544 | ||
545 | if (argc != 2) | |
546 | return EXIT_FAILURE; | |
547 | pid = strtol(argv[1], (char **) NULL, 10); | |
548 | ||
a446842f | 549 | pc = ul_new_procfs_path(pid, prefix); |
32c21c76 KZ |
550 | if (!pc) |
551 | err(EXIT_FAILURE, "cannot alloc procfs handler"); | |
552 | ||
553 | printf("%d\n", (int) pid); | |
554 | ||
555 | procfs_process_get_uid(pc, &uid); | |
556 | printf(" UID: %zu\n", (size_t) uid); | |
557 | ||
558 | procfs_process_get_cmdline(pc, buf, sizeof(buf)); | |
559 | printf(" CMDLINE: '%s'\n", buf); | |
560 | ||
561 | procfs_process_get_cmdname(pc, buf, sizeof(buf)); | |
562 | printf(" COMM: '%s'\n", buf); | |
563 | ||
564 | ul_unref_path(pc); | |
565 | return EXIT_SUCCESS; | |
566 | } | |
567 | ||
568 | static int test_isprocfs(int argc, char *argv[]) | |
569 | { | |
570 | const char *name = argc > 1 ? argv[1] : "/proc"; | |
571 | int fd = open(name, O_RDONLY); | |
572 | int is = 0; | |
573 | ||
574 | if (fd >= 0) { | |
575 | is = fd_is_procfs(fd); | |
576 | close(fd); | |
577 | } else | |
578 | err(EXIT_FAILURE, "cannot open %s", name); | |
579 | ||
580 | printf("%s: %s procfs\n", name, is ? "is" : "is NOT"); | |
581 | return is ? EXIT_SUCCESS : EXIT_FAILURE; | |
582 | } | |
583 | ||
a446842f | 584 | static int test_process_stat_nth(int argc, char *argv[], const char *prefix) |
c2ece66e KZ |
585 | { |
586 | pid_t pid; | |
587 | struct path_cxt *pc; | |
588 | uintmax_t num = 0; | |
758730c0 | 589 | int n, ret; |
c2ece66e KZ |
590 | |
591 | if (argc != 3) | |
592 | return EXIT_FAILURE; | |
593 | pid = strtol(argv[1], (char **) NULL, 10); | |
594 | n = strtol(argv[2], (char **) NULL, 10); | |
595 | ||
a446842f | 596 | pc = ul_new_procfs_path(pid, prefix); |
c2ece66e KZ |
597 | if (!pc) |
598 | err(EXIT_FAILURE, "cannot alloc procfs handler"); | |
599 | ||
758730c0 TW |
600 | ret = procfs_process_get_stat_nth(pc, n, &num); |
601 | if (ret) | |
602 | errx(EXIT_FAILURE, "read %dth number failed: %s", n, strerror(-ret)); | |
c2ece66e KZ |
603 | |
604 | printf("%d: %dth %ju\n", (int) pid, n, num); | |
605 | ul_unref_path(pc); | |
606 | return EXIT_SUCCESS; | |
607 | } | |
608 | ||
32c21c76 KZ |
609 | int main(int argc, char *argv[]) |
610 | { | |
a446842f TW |
611 | const char *prefix = NULL; |
612 | ||
613 | if (argc > 2 && strcmp(argv[1], "--prefix") == 0) { | |
614 | prefix = argv[2]; | |
615 | argc -= 2; | |
616 | argv += 2; | |
617 | } | |
618 | ||
32c21c76 | 619 | if (argc < 2) { |
a446842f TW |
620 | fprintf(stderr, "usage: %1$s [--prefix <prefix>] --tasks <pid>\n" |
621 | " %1$s [--prefix <prefix>] --fds <pid>\n" | |
32c21c76 | 622 | " %1$s --is-procfs [<dir>]\n" |
6f5cb4cb | 623 | " %1$s --processes [--name <name>] [--uid <uid>]\n" |
a446842f TW |
624 | " %1$s [--prefix <prefix>] --one <pid>\n" |
625 | " %1$s [--prefix <prefix>] --stat-nth <pid> <n>\n", | |
32c21c76 KZ |
626 | program_invocation_short_name); |
627 | return EXIT_FAILURE; | |
628 | } | |
629 | ||
630 | if (strcmp(argv[1], "--tasks") == 0) | |
a446842f | 631 | return test_tasks(argc - 1, argv + 1, prefix); |
32c21c76 | 632 | if (strcmp(argv[1], "--fds") == 0) |
a446842f | 633 | return test_fds(argc - 1, argv + 1, prefix); |
32c21c76 KZ |
634 | if (strcmp(argv[1], "--processes") == 0) |
635 | return test_processes(argc - 1, argv + 1); | |
636 | if (strcmp(argv[1], "--is-procfs") == 0) | |
637 | return test_isprocfs(argc - 1, argv + 1); | |
638 | if (strcmp(argv[1], "--one") == 0) | |
a446842f | 639 | return test_one_process(argc - 1, argv + 1, prefix); |
c2ece66e | 640 | if (strcmp(argv[1], "--stat-nth") == 0) |
a446842f | 641 | return test_process_stat_nth(argc - 1, argv + 1, prefix); |
32c21c76 KZ |
642 | |
643 | return EXIT_FAILURE; | |
644 | } | |
645 | #endif /* TEST_PROGRAM_PROCUTILS */ |