]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/procfs: add functions to read /proc/#/ stuff
authorKarel Zak <kzak@redhat.com>
Tue, 7 Sep 2021 15:32:01 +0000 (17:32 +0200)
committerKarel Zak <kzak@redhat.com>
Wed, 6 Oct 2021 09:01:54 +0000 (11:01 +0200)
This is extension to lib/path.c (the same as lib/sysfs.c for block
devices) to read info about processes.

* replaces code from lib/procutils.c
* remove proc_{open,close}_processes(), opendir and readdir is good enough
* add procfs_dirent_*() functions to work with readdir() from /proc
* add new path_cxt based API (will be extended in next commits) for
  complex tasks

Signed-off-by: Karel Zak <kzak@redhat.com>
include/Makemodule.am
include/pathnames.h
include/procfs.h [new file with mode: 0644]
lib/Makemodule.am
lib/meson.build
lib/procfs.c [new file with mode: 0644]

index a20a3f84abfd78ab05fe5fc191faf5dc45461a54..f22e37e626fba3891d6a7137ed93b51acb8a211a 100644 (file)
@@ -49,6 +49,7 @@ dist_noinst_HEADERS += \
        include/pidfd-utils.h \
        include/plymouth-ctrl.h \
        include/procutils.h \
+       include/procfs.h \
        include/pt-bsd.h \
        include/pt-mbr.h \
        include/pt-mbr-partnames.h \
index 9be2baa837dedb17735ed918d2776e717231a7d2..17e4b940f561a4e3a2c740fe21960ab314b1ffa7 100644 (file)
@@ -88,6 +88,7 @@
 
 /* mount paths */
 #define _PATH_FILESYSTEMS      "/etc/filesystems"
+#define _PATH_PROC             "/proc"
 #define _PATH_PROC_SWAPS       "/proc/swaps"
 #define _PATH_PROC_FILESYSTEMS "/proc/filesystems"
 #define _PATH_PROC_MOUNTS      "/proc/mounts"
diff --git a/include/procfs.h b/include/procfs.h
new file mode 100644 (file)
index 0000000..55cebce
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 Karel Zak <kzak@redhat.com>
+ */
+#ifndef UTIL_LINUX_PROCFS_H
+#define UTIL_LINUX_PROCFS_H
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <inttypes.h>
+#include <dirent.h>
+
+#include "path.h"
+
+struct procfs_process {
+       pid_t pid;
+};
+
+extern void ul_procfs_init_debug(void);
+extern struct path_cxt *ul_new_procfs_path(pid_t pid, const char *prefix);
+extern int procfs_process_init_path(struct path_cxt *pc, pid_t pid);
+
+extern int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid);
+extern ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz);
+extern ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz);
+
+
+static inline ssize_t procfs_process_get_exe(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+       return ul_path_readlink(pc, buf, bufsz, "exe");
+}
+
+static inline ssize_t procfs_process_get_root(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+       return ul_path_readlink(pc, buf, bufsz, "root");
+}
+
+static inline ssize_t procfs_process_get_cwd(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+       return ul_path_readlink(pc, buf, bufsz, "cwd");
+}
+
+extern int procfs_process_next_tid(struct path_cxt *pc, DIR **sub, pid_t *tid);
+extern int procfs_process_next_fd(struct path_cxt *pc, DIR **sub, int *fd);
+
+extern int procfs_dirent_is_process(struct dirent *d);
+extern int procfs_dirent_get_pid(struct dirent *d, pid_t *pid);
+extern int procfs_dirent_get_uid(DIR *procfs, struct dirent *d, uid_t *uid);
+extern int procfs_dirent_match_uid(DIR *procfs, struct dirent *d, uid_t uid);
+extern int procfs_dirent_get_name(DIR *procfs, struct dirent *d, char *buf, size_t bufsz);
+extern int procfs_dirent_match_name(DIR *procfs, struct dirent *d, const char *name);
+
+extern int fd_is_procfs(int fd);
+extern char *pid_get_cmdname(pid_t pid);
+extern char *pid_get_cmdline(pid_t pid);
+
+#endif /* UTIL_LINUX_PROCFS_H */
index d32a20a2c40fb9c42e569cfe01424de03b2b21f1..6971773c207f837652aa47c9f679a78c8d79f24f 100644 (file)
@@ -58,6 +58,7 @@ if HAVE_OPENAT
 if HAVE_DIRFD
 libcommon_la_SOURCES += lib/path.c
 libcommon_la_SOURCES += lib/sysfs.c
+libcommon_la_SOURCES += lib/procfs.c
 endif
 endif
 
@@ -102,6 +103,7 @@ check_PROGRAMS += test_cpuset
 endif
 check_PROGRAMS += \
        test_sysfs \
+       test_procfs \
        test_pager \
        test_caputils \
        test_loopdev \
@@ -110,7 +112,6 @@ endif
 
 if HAVE_OPENAT
 if HAVE_DIRFD
-check_PROGRAMS += test_procutils
 check_PROGRAMS += test_path
 endif
 endif
@@ -149,9 +150,6 @@ test_randutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_RANDUTILS
 
 if HAVE_OPENAT
 if HAVE_DIRFD
-test_procutils_SOURCES = lib/procutils.c
-test_procutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCUTILS
-
 test_path_SOURCES = lib/path.c lib/fileutils.c
 if HAVE_CPU_SET_T
 test_path_SOURCES += lib/cpuset.c
@@ -181,6 +179,10 @@ endif
 test_sysfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_SYSFS
 test_sysfs_LDADD = $(LDADD)
 
+test_procfs_SOURCES = lib/procfs.c lib/path.c lib/fileutils.c lib/strutils.c
+test_procfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCFS
+test_procfs_LDADD = $(LDADD)
+
 test_pager_SOURCES = lib/pager.c
 test_pager_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PAGER
 
index dcdbdf07dca15251c77a3f7b5bd4b3c22583d844..26e5ceebdaab5b81b3c98881c2bb2958074485d9 100644 (file)
@@ -17,7 +17,7 @@ lib_common_sources = '''
        mbsedit.c
        md5.c
        pager.c
-       procutils.c
+       procfs.c
        pwdutils.c
        randutils.c
        sha1.c
@@ -74,7 +74,7 @@ endif
 if conf.get('HAVE_OPENAT') in [1] and conf.get('HAVE_DIRFD') in [1]
   lib_common_sources += '''
     path.c
-    procutils.c
+    procfs.c
     sysfs.c
 '''.split()
 endif
diff --git a/lib/procfs.c b/lib/procfs.c
new file mode 100644 (file)
index 0000000..ec55fb4
--- /dev/null
@@ -0,0 +1,560 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/vfs.h>
+#include <errno.h>
+
+#include "c.h"
+#include "pathnames.h"
+#include "procfs.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "debug.h"
+#include "strutils.h"
+#include "statfs_magic.h"
+
+static void procfs_process_deinit_path(struct path_cxt *pc);
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(ulprocfs);
+UL_DEBUG_DEFINE_MASKNAMES(ulprocfs) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULPROCFS_DEBUG_INIT    (1 << 1)
+#define ULPROCFS_DEBUG_CXT     (1 << 2)
+
+#define DBG(m, x)       __UL_DBG(ulprocfs, ULPROCFS_DEBUG_, m, x)
+#define ON_DBG(m, x)    __UL_DBG_CALL(ulprocfs, ULPROCFS_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK  UL_DEBUG_MASK(ulprocfs)
+#include "debugobj.h"
+
+void ul_procfs_init_debug(void)
+{
+       if (ulprocfs_debug_mask)
+               return;
+       __UL_INIT_DEBUG_FROM_ENV(ulprocfs, ULPROCFS_DEBUG_, 0, ULPROCFS_DEBUG);
+}
+
+struct path_cxt *ul_new_procfs_path(pid_t pid, const char *prefix)
+{
+       struct path_cxt *pc = ul_new_path(NULL);
+
+       if (!pc)
+               return NULL;
+       if (prefix)
+               ul_path_set_prefix(pc, prefix);
+
+       if (procfs_process_init_path(pc, pid) != 0) {
+               ul_unref_path(pc);
+               return NULL;
+       }
+
+       DBG(CXT, ul_debugobj(pc, "alloc"));
+       return pc;
+}
+
+/*
+ * procfs_blkdev_* is procfs extension to ul_path_* API to read info about process.
+ *
+ * The function is possible to call in loop and without sysfs_procfs_deinit_path().
+ * The procfs_process_deinit_path() is automatically called by ul_unref_path().
+ *
+ */
+int procfs_process_init_path(struct path_cxt *pc, pid_t pid)
+{
+       struct procfs_process *prc;
+       int rc;
+       char buf[sizeof(_PATH_PROC) + sizeof(stringify_value(UINT32_MAX)) + 2];
+
+       /* define path to pid stuff */
+       snprintf(buf, sizeof(buf), _PATH_PROC "/%zu", (size_t) pid);
+       rc = ul_path_set_dir(pc, buf);
+       if (rc)
+               return rc;
+
+       /* make sure path exists */
+       rc = ul_path_get_dirfd(pc);
+       if (rc < 0)
+               return rc;
+
+       /* initialize procfs specific stuff */
+       prc = ul_path_get_dialect(pc);
+       if (!prc) {
+               DBG(CXT, ul_debugobj(pc, "alloc new procfs handler"));
+               prc = calloc(1, sizeof(struct procfs_process));
+               if (!prc)
+                       return -ENOMEM;
+
+               ul_path_set_dialect(pc, prc, procfs_process_deinit_path);
+       }
+
+       DBG(CXT, ul_debugobj(pc, "init procfs stuff"));
+
+       prc->pid = pid;
+       return 0;
+}
+
+static void procfs_process_deinit_path(struct path_cxt *pc)
+{
+       struct procfs_process *prc;
+
+       if (!pc)
+               return;
+
+       DBG(CXT, ul_debugobj(pc, "deinit"));
+
+       prc = ul_path_get_dialect(pc);
+       if (!prc)
+               return;
+
+       free(prc);
+       ul_path_set_dialect(pc, NULL, NULL);
+}
+
+static ssize_t read_procfs_file(int fd, char *buf, size_t bufsz)
+{
+       ssize_t sz = 0;
+       size_t i;
+
+       if (fd < 0)
+               return -EINVAL;
+
+       sz = read_all(fd, buf, bufsz);
+       if (sz <= 0)
+               return sz;
+
+       for (i = 0; i < (size_t) sz; i++) {
+               if (buf[i] == '\0')
+                       buf[i] = ' ';
+       }
+       buf[sz - 1] = '\0';
+       return sz;
+}
+
+ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+       int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, "cmdline");
+
+       if (fd >= 0) {
+               ssize_t sz = read_procfs_file(fd, buf, bufsz);
+               close(fd);
+               return sz;
+       }
+       return -errno;
+}
+
+ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+       int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, "comm");
+
+       if (fd >= 0) {
+               ssize_t sz = read_procfs_file(fd, buf, bufsz);
+               close(fd);
+               return sz;
+       }
+       return -errno;
+}
+
+int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid)
+{
+       struct stat sb;
+       int rc;
+
+       if ((rc = ul_path_stat(pc, &sb, NULL)) == 0)
+               *uid = sb.st_uid;
+       return rc;
+}
+
+/*
+ * returns the next task TID, the @sub is automatically initialized
+ * when called first time and closed after last call or you can
+ * call closedir()* when you need to break the loop.
+ *
+ * Returns: <0 on error, 0 on success, >1 done
+ *
+ * Example:
+ *
+ * pid_t tid;
+ * DIR *sub = NULL;
+ * path_cxt *pc = ul_new_procfs_path(123, NULL);
+ *
+ * while (procfs_process_next_tid(pc, &sub, &tid) == 0)
+ *     printf("task: %d", (int) tid);
+ *
+ */
+int procfs_process_next_tid(struct path_cxt *pc, DIR **sub, pid_t *tid)
+{
+       struct dirent *d;
+
+       if (!pc || !sub || !tid)
+               return -EINVAL;
+
+       if (!*sub) {
+               *sub = ul_path_opendir(pc, "task");
+               if (!*sub)
+                       return -errno;
+       }
+
+       while ((d = xreaddir(*sub))) {
+               if (procfs_dirent_get_pid(d, tid) == 0)
+                       return 0;
+       }
+
+       closedir(*sub);
+       *sub = NULL;
+       return 1;
+}
+
+int procfs_process_next_fd(struct path_cxt *pc, DIR **sub, int *fd)
+{
+       struct dirent *d;
+
+       if (!pc || !sub || !fd)
+               return -EINVAL;
+
+       if (!*sub) {
+               *sub = ul_path_opendir(pc, "fd");
+               if (!*sub)
+                       return -errno;
+       }
+
+       while ((d = xreaddir(*sub))) {
+               uint64_t num;
+#ifdef _DIRENT_HAVE_D_TYPE
+               if (d->d_type != DT_LNK && d->d_type != DT_UNKNOWN)
+                       continue;
+#endif
+               if (ul_strtou64(d->d_name, &num, 10) < 0)
+                       continue;
+               *fd = num;
+               return 0;
+       }
+
+       closedir(*sub);
+       *sub = NULL;
+       return 1;
+}
+
+/*
+ * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill
+ */
+
+/* stupid, but good enough as a basic filter */
+int procfs_dirent_is_process(struct dirent *d)
+{
+#ifdef _DIRENT_HAVE_D_TYPE
+       if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN)
+               return 0;
+#endif
+       if (!isdigit((unsigned char) *d->d_name))
+               return 0;
+
+       return 1;
+}
+
+int procfs_dirent_get_pid(struct dirent *d, pid_t *pid)
+{
+       uint64_t num;
+
+       if (!procfs_dirent_is_process(d))
+               return -EINVAL;
+
+       if (ul_strtou64(d->d_name, &num, 10) < 0)
+               return -EINVAL;
+
+       *pid = (pid_t) num;
+       return 0;
+}
+
+int procfs_dirent_get_uid(DIR *procfs, struct dirent *d, uid_t *uid)
+{
+       struct stat st;
+
+       if (!procfs_dirent_is_process(d))
+               return -EINVAL;
+
+       if (fstatat(dirfd(procfs), d->d_name, &st, 0))
+               return -EINVAL;
+
+       *uid = st.st_uid;
+       return 0;
+}
+
+int procfs_dirent_match_uid(DIR *procfs, struct dirent *d, uid_t uid)
+{
+       uid_t x;
+
+       if (procfs_dirent_get_uid(procfs, d, &x) == 0)
+               return x == uid;
+
+       return 0;
+}
+
+/* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME.
+ * The minimal of the @buf has to be 32 bytes. */
+int procfs_dirent_get_name(DIR *procfs, struct dirent *d, char *buf, size_t bufsz)
+{
+       FILE *f;
+       size_t sz;
+       char tmp[1024], *p, *end = NULL;
+
+       if (bufsz < 32)
+               return -EINVAL;
+       if (!procfs_dirent_is_process(d))
+               return -EINVAL;
+
+       snprintf(tmp, sizeof(tmp), "%s/stat", d->d_name);
+       f = fopen_at(dirfd(procfs), tmp, O_CLOEXEC|O_RDONLY, "r");
+       if (!f)
+               return -errno;
+
+       p = fgets(tmp, sizeof(tmp), f);
+       fclose(f);
+       if (!p)
+               return -errno;
+
+       /* skip PID */
+       while (*p && *p != '(')
+               p++;
+
+       /* skip extra '(' */
+       while (*p && *p == '(')
+               p++;
+
+       end = p;
+       while (*end && *end != ')')
+               end++;
+
+       sz = end - p;
+       if (sz > bufsz)
+               sz = bufsz - 1;
+
+       memcpy(buf, p, sz);
+       buf[sz] = '\0';
+
+       return 0;
+}
+
+int procfs_dirent_match_name(DIR *procfs, struct dirent *d, const char *name)
+{
+       char buf[33];
+
+       if (procfs_dirent_get_name(procfs, d, buf, sizeof(buf)) == 0)
+               return strcmp(name, buf) == 0;
+
+       return 0;
+}
+
+/* checks if fd is file in a procfs;
+ * returns 1 if true, 0 if false or couldn't determine */
+int fd_is_procfs(int fd)
+{
+       struct statfs st;
+       int ret;
+
+       do {
+               errno = 0;
+               ret = fstatfs(fd, &st);
+
+               if (ret < 0) {
+                       if (errno != EINTR && errno != EAGAIN)
+                               return 0;
+                       xusleep(250000);
+               }
+       } while (ret != 0);
+
+       return st.f_type == STATFS_PROC_MAGIC;
+}
+
+static char *strdup_procfs_file(pid_t pid, const char *name)
+{
+       char buf[BUFSIZ];
+       char *re = NULL;
+       int fd;
+
+       snprintf(buf, sizeof(buf), _PATH_PROC "/%d/%s", (int) pid, name);
+       fd = open(buf, O_CLOEXEC|O_RDONLY);
+       if (fd < 0)
+               return NULL;
+
+       if (read_procfs_file(fd, buf, sizeof(buf)) > 0)
+               re = strdup(buf);
+       close(fd);
+       return re;
+}
+
+char *pid_get_cmdname(pid_t pid)
+{
+       return strdup_procfs_file(pid, "comm");
+}
+
+char *pid_get_cmdline(pid_t pid)
+{
+       return strdup_procfs_file(pid, "cmdline");
+}
+
+#ifdef TEST_PROGRAM_PROCFS
+
+static int test_tasks(int argc, char *argv[])
+{
+       DIR *sub = NULL;
+       struct path_cxt *pc;
+       pid_t tid = 0, pid;
+
+       if (argc != 2)
+               return EXIT_FAILURE;
+
+       pid = strtol(argv[1], (char **) NULL, 10);
+       printf("PID=%d, TIDs:", pid);
+
+       pc = ul_new_procfs_path(pid, NULL);
+       if (!pc)
+               err(EXIT_FAILURE, "alloc procfs handler failed");
+
+       while (procfs_process_next_tid(pc, &sub, &tid) == 0)
+               printf(" %d", tid);
+
+       printf("\n");
+        ul_unref_path(pc);
+       return EXIT_SUCCESS;
+}
+
+static int test_fds(int argc, char *argv[])
+{
+       DIR *sub = NULL;
+       struct path_cxt *pc;
+       pid_t pid;
+       int fd = -1;
+
+       if (argc != 2)
+               return EXIT_FAILURE;
+
+       pid = strtol(argv[1], (char **) NULL, 10);
+       printf("PID=%d, FDs:", pid);
+
+       pc = ul_new_procfs_path(pid, NULL);
+       if (!pc)
+               err(EXIT_FAILURE, "alloc procfs handler failed");
+
+       while (procfs_process_next_fd(pc, &sub, &fd) == 0)
+               printf(" %d", fd);
+
+       fputc('\n', stdout);
+        ul_unref_path(pc);
+       return EXIT_SUCCESS;
+}
+
+static int test_processes(int argc, char *argv[])
+{
+       DIR *dir;
+       struct dirent *d;
+       char *name = NULL;
+       uid_t uid = (uid_t) -1;
+       char buf[128];
+
+       if (argc >= 3 && strcmp(argv[1], "--name") == 0)
+               name = argv[2];
+       if (argc >= 3 && strcmp(argv[1], "--uid") == 0)
+               uid = (uid_t) atol(argv[2]);
+
+       dir = opendir(_PATH_PROC);
+       if (!dir)
+               err(EXIT_FAILURE, "cannot open proc");
+
+       while ((d = xreaddir(dir))) {
+               pid_t pid = 0;
+
+               if (procfs_dirent_get_pid(d, &pid) != 0)
+                       continue;
+               if (name && !procfs_dirent_match_name(dir, d, name))
+                       continue;
+               if (uid != (uid_t) -1 && !procfs_dirent_match_uid(dir, d, uid))
+                       continue;
+               procfs_dirent_get_name(dir, d, buf, sizeof(buf));
+               printf(" %d [%s]", pid, buf);
+       }
+
+       fputc('\n', stdout);
+       closedir(dir);
+       return EXIT_SUCCESS;
+}
+
+static int test_one_process(int argc, char *argv[])
+{
+       pid_t pid;
+       struct path_cxt *pc;
+       char buf[BUFSIZ];
+       uid_t uid = (uid_t) -1;
+
+       if (argc != 2)
+               return EXIT_FAILURE;
+       pid = strtol(argv[1], (char **) NULL, 10);
+
+       pc = ul_new_procfs_path(pid, NULL);
+       if (!pc)
+               err(EXIT_FAILURE, "cannot alloc procfs handler");
+
+       printf("%d\n", (int) pid);
+
+       procfs_process_get_uid(pc, &uid);
+       printf("   UID: %zu\n", (size_t) uid);
+
+       procfs_process_get_cmdline(pc, buf, sizeof(buf));
+       printf("   CMDLINE: '%s'\n", buf);
+
+       procfs_process_get_cmdname(pc, buf, sizeof(buf));
+       printf("   COMM: '%s'\n", buf);
+
+       ul_unref_path(pc);
+       return EXIT_SUCCESS;
+}
+
+static int test_isprocfs(int argc, char *argv[])
+{
+       const char *name = argc > 1 ? argv[1] : "/proc";
+       int fd = open(name, O_RDONLY);
+       int is = 0;
+
+       if (fd >= 0) {
+               is = fd_is_procfs(fd);
+               close(fd);
+       } else
+               err(EXIT_FAILURE, "cannot open %s", name);
+
+       printf("%s: %s procfs\n", name, is ? "is" : "is NOT");
+       return is ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+int main(int argc, char *argv[])
+{
+       if (argc < 2) {
+               fprintf(stderr, "usage: %1$s --tasks <pid>\n"
+                               "       %1$s --fds <pid>\n"
+                               "       %1$s --is-procfs [<dir>]\n"
+                               "       %1$s --processes [---name <name>] [--uid <uid>]\n"
+                               "       %1$s --one <pid>\n",
+                               program_invocation_short_name);
+               return EXIT_FAILURE;
+       }
+
+       if (strcmp(argv[1], "--tasks") == 0)
+               return test_tasks(argc - 1, argv + 1);
+       if (strcmp(argv[1], "--fds") == 0)
+               return test_fds(argc - 1, argv + 1);
+       if (strcmp(argv[1], "--processes") == 0)
+               return test_processes(argc - 1, argv + 1);
+       if (strcmp(argv[1], "--is-procfs") == 0)
+               return test_isprocfs(argc - 1, argv + 1);
+       if (strcmp(argv[1], "--one") == 0)
+               return test_one_process(argc - 1, argv + 1);
+
+       return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM_PROCUTILS */