]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/procfs.c
Merge branch 'meson-build-dmesg' of https://github.com/jwillikers/util-linux
[thirdparty/util-linux.git] / lib / procfs.c
1 /*
2 * No copyright is claimed. This code is in the public domain; do with
3 * it what you wish.
4 *
5 * Copyright (C) 2021 Karel Zak <kzak@redhat.com>
6 */
7 #include <ctype.h>
8 #include <unistd.h>
9 #include <errno.h>
10
11 #ifdef HAVE_SYS_VFS_H
12 # include <sys/vfs.h>
13 # include "statfs_magic.h"
14 #endif
15
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"
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
144 static ssize_t procfs_process_get_data_for(struct path_cxt *pc, char *buf, size_t bufsz,
145 const char *fname)
146 {
147 int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, fname);
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
157 ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz)
158 {
159 return procfs_process_get_data_for(pc, buf, bufsz, "cmdline");
160 }
161
162 ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz)
163 {
164 return procfs_process_get_data_for(pc, buf, bufsz, "comm");
165 }
166
167 ssize_t procfs_process_get_stat(struct path_cxt *pc, char *buf, size_t bufsz)
168 {
169 return procfs_process_get_data_for(pc, buf, bufsz, "stat");
170 }
171
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
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
187 rc = procfs_process_get_data_for(pc, buf, sizeof(buf), "stat");
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 */
199 if (i == 2 && (p = strrchr(key, ')')))
200 key = p + 2;
201 }
202
203 return -EINVAL;
204 }
205
206 int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid)
207 {
208 struct stat sb;
209 int rc;
210
211 if ((rc = ul_path_stat(pc, &sb, 0, NULL)) == 0)
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;
377 if (sz >= bufsz)
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
396 #ifdef HAVE_SYS_VFS_H
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;
416 return 0;
417 }
418 #else
419 int fd_is_procfs(int fd __attribute__((__unused__)))
420 {
421 return 0;
422 }
423 #endif
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
454 static int test_tasks(int argc, char *argv[], const char *prefix)
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
466 pc = ul_new_procfs_path(pid, prefix);
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
478 static int test_fds(int argc, char *argv[], const char *prefix)
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
491 pc = ul_new_procfs_path(pid, prefix);
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
538 static int test_one_process(int argc, char *argv[], const char *prefix)
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
549 pc = ul_new_procfs_path(pid, prefix);
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
584 static int test_process_stat_nth(int argc, char *argv[], const char *prefix)
585 {
586 pid_t pid;
587 struct path_cxt *pc;
588 uintmax_t num = 0;
589 int n, ret;
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
596 pc = ul_new_procfs_path(pid, prefix);
597 if (!pc)
598 err(EXIT_FAILURE, "cannot alloc procfs handler");
599
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));
603
604 printf("%d: %dth %ju\n", (int) pid, n, num);
605 ul_unref_path(pc);
606 return EXIT_SUCCESS;
607 }
608
609 int main(int argc, char *argv[])
610 {
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
619 if (argc < 2) {
620 fprintf(stderr, "usage: %1$s [--prefix <prefix>] --tasks <pid>\n"
621 " %1$s [--prefix <prefix>] --fds <pid>\n"
622 " %1$s --is-procfs [<dir>]\n"
623 " %1$s --processes [--name <name>] [--uid <uid>]\n"
624 " %1$s [--prefix <prefix>] --one <pid>\n"
625 " %1$s [--prefix <prefix>] --stat-nth <pid> <n>\n",
626 program_invocation_short_name);
627 return EXIT_FAILURE;
628 }
629
630 if (strcmp(argv[1], "--tasks") == 0)
631 return test_tasks(argc - 1, argv + 1, prefix);
632 if (strcmp(argv[1], "--fds") == 0)
633 return test_fds(argc - 1, argv + 1, prefix);
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)
639 return test_one_process(argc - 1, argv + 1, prefix);
640 if (strcmp(argv[1], "--stat-nth") == 0)
641 return test_process_stat_nth(argc - 1, argv + 1, prefix);
642
643 return EXIT_FAILURE;
644 }
645 #endif /* TEST_PROGRAM_PROCUTILS */