Merge tag 'pidfd-v5.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 7 May 2019 19:30:24 +0000 (12:30 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 7 May 2019 19:30:24 +0000 (12:30 -0700)
Pull pidfd updates from Christian Brauner:
 "This patchset makes it possible to retrieve pidfds at process creation
  time by introducing the new flag CLONE_PIDFD to the clone() system
  call. Linus originally suggested to implement this as a new flag to
  clone() instead of making it a separate system call.

  After a thorough review from Oleg CLONE_PIDFD returns pidfds in the
  parent_tidptr argument. This means we can give back the associated pid
  and the pidfd at the same time. Access to process metadata information
  thus becomes rather trivial.

  As has been agreed, CLONE_PIDFD creates file descriptors based on
  anonymous inodes similar to the new mount api. They are made
  unconditional by this patchset as they are now needed by core kernel
  code (vfs, pidfd) even more than they already were before (timerfd,
  signalfd, io_uring, epoll etc.). The core patchset is rather small.
  The bulky looking changelist is caused by David's very simple changes
  to Kconfig to make anon inodes unconditional.

  A pidfd comes with additional information in fdinfo if the kernel
  supports procfs. The fdinfo file contains the pid of the process in
  the callers pid namespace in the same format as the procfs status
  file, i.e. "Pid:\t%d".

  To remove worries about missing metadata access this patchset comes
  with a sample/test program that illustrates how a combination of
  CLONE_PIDFD and pidfd_send_signal() can be used to gain race-free
  access to process metadata through /proc/<pid>.

  Further work based on this patchset has been done by Joel. His work
  makes pidfds pollable. It finished too late for this merge window. I
  would prefer to have it sitting in linux-next for a while and send it
  for inclusion during the 5.3 merge window"

* tag 'pidfd-v5.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux:
  samples: show race-free pidfd metadata access
  signal: support CLONE_PIDFD with pidfd_send_signal
  clone: add CLONE_PIDFD
  Make anon_inodes unconditional

26 files changed:
arch/arm/kvm/Kconfig
arch/arm64/kvm/Kconfig
arch/mips/kvm/Kconfig
arch/powerpc/kvm/Kconfig
arch/s390/kvm/Kconfig
arch/x86/Kconfig
arch/x86/kvm/Kconfig
drivers/base/Kconfig
drivers/char/tpm/Kconfig
drivers/dma-buf/Kconfig
drivers/gpio/Kconfig
drivers/iio/Kconfig
drivers/infiniband/Kconfig
drivers/vfio/Kconfig
fs/Makefile
fs/notify/fanotify/Kconfig
fs/notify/inotify/Kconfig
include/linux/pid.h
include/uapi/linux/sched.h
init/Kconfig
kernel/fork.c
kernel/signal.c
kernel/sys_ni.c
samples/Makefile
samples/pidfd/Makefile [new file with mode: 0644]
samples/pidfd/pidfd-metadata.c [new file with mode: 0644]

index 3f5320f46de2df1251b840680f3a8e75179027d1..f591026347a5083b58c4c97dcec052026c337b7e 100644 (file)
@@ -22,7 +22,6 @@ config KVM
        bool "Kernel-based Virtual Machine (KVM) support"
        depends on MMU && OF
        select PREEMPT_NOTIFIERS
-       select ANON_INODES
        select ARM_GIC
        select ARM_GIC_V3
        select ARM_GIC_V3_ITS
index a3f85624313ea58b42dabc548c459ba7ebc0adb3..a67121d419a2fc5635dcc10a2c0018fc16a216a7 100644 (file)
@@ -23,7 +23,6 @@ config KVM
        depends on OF
        select MMU_NOTIFIER
        select PREEMPT_NOTIFIERS
-       select ANON_INODES
        select HAVE_KVM_CPU_RELAX_INTERCEPT
        select HAVE_KVM_ARCH_TLB_FLUSH_ALL
        select KVM_MMIO
index 4528bc9c3cb1695d3c4a6cc8e20ea9dd4b8afdd4..eac25aef21e03dd85fa1d64bdcf2b138cdfaaa2a 100644 (file)
@@ -21,7 +21,6 @@ config KVM
        depends on MIPS_FP_SUPPORT
        select EXPORT_UASM
        select PREEMPT_NOTIFIERS
-       select ANON_INODES
        select KVM_GENERIC_DIRTYLOG_READ_PROTECT
        select HAVE_KVM_VCPU_ASYNC_IOCTL
        select KVM_MMIO
index bfdde04e49051473c126ff0819f981403216a9ef..f53997a8ca624c453c954a00af287e405d43266d 100644 (file)
@@ -20,7 +20,6 @@ if VIRTUALIZATION
 config KVM
        bool
        select PREEMPT_NOTIFIERS
-       select ANON_INODES
        select HAVE_KVM_EVENTFD
        select HAVE_KVM_VCPU_ASYNC_IOCTL
        select SRCU
index 767453faacfcd575ba858235cd6f896bebfca162..1816ee48eadd6e80baf170a1985d9c7e545e3916 100644 (file)
@@ -21,7 +21,6 @@ config KVM
        prompt "Kernel-based Virtual Machine (KVM) support"
        depends on HAVE_KVM
        select PREEMPT_NOTIFIERS
-       select ANON_INODES
        select HAVE_KVM_CPU_RELAX_INTERCEPT
        select HAVE_KVM_VCPU_ASYNC_IOCTL
        select HAVE_KVM_EVENTFD
index 0a3cc347143f87fc870e5363e9cab46bd6dfd48a..e7212731cffb74c1da98cdbbb15cad5946b20849 100644 (file)
@@ -44,7 +44,6 @@ config X86
        #
        select ACPI_LEGACY_TABLES_LOOKUP        if ACPI
        select ACPI_SYSTEM_POWER_STATES_SUPPORT if ACPI
-       select ANON_INODES
        select ARCH_32BIT_OFF_T                 if X86_32
        select ARCH_CLOCKSOURCE_DATA
        select ARCH_CLOCKSOURCE_INIT
index 72fa955f4a15ee27609200e6e483b28e4a1c3925..fc042419e670b10199808251d8f7310454c283e2 100644 (file)
@@ -27,7 +27,6 @@ config KVM
        depends on X86_LOCAL_APIC
        select PREEMPT_NOTIFIERS
        select MMU_NOTIFIER
-       select ANON_INODES
        select HAVE_KVM_IRQCHIP
        select HAVE_KVM_IRQFD
        select IRQ_BYPASS_MANAGER
index 059700ea352170bb10d3adc8a464744bc9dd8f51..03f067da12ee01569acd141abc6686398ea531e5 100644 (file)
@@ -174,7 +174,6 @@ source "drivers/base/regmap/Kconfig"
 config DMA_SHARED_BUFFER
        bool
        default n
-       select ANON_INODES
        select IRQ_WORK
        help
          This option enables the framework for buffer-sharing between
index 536e55d3919fd2f6a359c769844e684617dfd1d2..f3e4bc490cf05067b388e3677941229bff6e4c20 100644 (file)
@@ -157,7 +157,6 @@ config TCG_CRB
 config TCG_VTPM_PROXY
        tristate "VTPM Proxy Interface"
        depends on TCG_TPM
-       select ANON_INODES
        ---help---
          This driver proxies for an emulated TPM (vTPM) running in userspace.
          A device /dev/vtpmx is provided that creates a device pair
index 2e5a0faa2cb10d58c0c071f8a5c588769e0f82e2..3fc9c2efc5830fa3595aa8ccdea5c3000cbf12fe 100644 (file)
@@ -3,7 +3,6 @@ menu "DMABUF options"
 config SYNC_FILE
        bool "Explicit Synchronization Framework"
        default n
-       select ANON_INODES
        select DMA_SHARED_BUFFER
        ---help---
          The Sync File Framework adds explicit syncronization via
index 3f50526a771fe8e21349b0d3c00efefd6cdb4a36..0f91600c27aeb5a8c8ac250f85aaf00948708b1b 100644 (file)
@@ -12,7 +12,6 @@ config ARCH_HAVE_CUSTOM_GPIO_H
 
 menuconfig GPIOLIB
        bool "GPIO Support"
-       select ANON_INODES
        help
          This enables GPIO support through the generic GPIO library.
          You only need to enable this, if you also want to enable
index d08aeb41cd078527cf2ae4ca6ee2d6aee26d65bc..1dec0fecb6efa940175074fd04223b48fa6be1f5 100644 (file)
@@ -4,7 +4,6 @@
 
 menuconfig IIO
        tristate "Industrial I/O support"
-       select ANON_INODES
        help
          The industrial I/O subsystem provides a unified framework for
          drivers for many different types of embedded sensors using a
index a1fb840de45d7e4c52d4cb05273d81b676737187..d318bab25860fb603fdff7fbe5ece8509593801f 100644 (file)
@@ -25,7 +25,6 @@ config INFINIBAND_USER_MAD
 
 config INFINIBAND_USER_ACCESS
        tristate "InfiniBand userspace access (verbs and CM)"
-       select ANON_INODES
        depends on MMU
        ---help---
          Userspace InfiniBand access support.  This enables the
index 9de5ed38da830a9140a373a4ba0f56b7e0f65ee7..3798d77d131c4b0a7581a85058bebb48c1c5adc1 100644 (file)
@@ -22,7 +22,6 @@ menuconfig VFIO
        tristate "VFIO Non-Privileged userspace driver framework"
        depends on IOMMU_API
        select VFIO_IOMMU_TYPE1 if (X86 || S390 || ARM || ARM64)
-       select ANON_INODES
        help
          VFIO provides a framework for secure userspace device drivers.
          See Documentation/vfio.txt for more details.
index 427fec226faebf93e74bbbd95445c476c9871655..35945f8139e61b3c12597211b0a47a011270925f 100644 (file)
@@ -25,7 +25,7 @@ obj-$(CONFIG_PROC_FS) += proc_namespace.o
 
 obj-y                          += notify/
 obj-$(CONFIG_EPOLL)            += eventpoll.o
-obj-$(CONFIG_ANON_INODES)      += anon_inodes.o
+obj-y                          += anon_inodes.o
 obj-$(CONFIG_SIGNALFD)         += signalfd.o
 obj-$(CONFIG_TIMERFD)          += timerfd.o
 obj-$(CONFIG_EVENTFD)          += eventfd.o
index 735bfb2e9190eedb6ee68886cfc37033fb0abf57..521dc91d2cb5597c97ef6f3a02609249e0baf9a9 100644 (file)
@@ -1,7 +1,6 @@
 config FANOTIFY
        bool "Filesystem wide access notification"
        select FSNOTIFY
-       select ANON_INODES
        select EXPORTFS
        default n
        ---help---
index b981fc0c8379d143eee7a0207ca6cd0fc69a3329..0161c74e76e260c5a9cf221250bed53d6346add5 100644 (file)
@@ -1,6 +1,5 @@
 config INOTIFY_USER
        bool "Inotify support for userspace"
-       select ANON_INODES
        select FSNOTIFY
        default y
        ---help---
index b6f4ba16065a02a930784ac42d056a14629bf8c8..3c8ef5a199ca8d59c777e7e0e40a5c61ddfee94a 100644 (file)
@@ -66,6 +66,8 @@ struct pid
 
 extern struct pid init_struct_pid;
 
+extern const struct file_operations pidfd_fops;
+
 static inline struct pid *get_pid(struct pid *pid)
 {
        if (pid)
index 22627f80063e3a0bcd36cbaf978c28fe6e631740..ed4ee170bee2a5e7dc0c3cde35e1c5549799c427 100644 (file)
@@ -10,6 +10,7 @@
 #define CLONE_FS       0x00000200      /* set if fs info shared between processes */
 #define CLONE_FILES    0x00000400      /* set if open files shared between processes */
 #define CLONE_SIGHAND  0x00000800      /* set if signal handlers and blocked signals shared */
+#define CLONE_PIDFD    0x00001000      /* set if a pidfd should be placed in parent */
 #define CLONE_PTRACE   0x00002000      /* set if we want to let tracing continue on the child too */
 #define CLONE_VFORK    0x00004000      /* set if the parent wants the child to wake it up on mm_release */
 #define CLONE_PARENT   0x00008000      /* set if we want to have the same parent as the cloner */
index 4592bf7997c000dc7a5537ca4c28b308713b9d65..be8f97e37a7678955b93375fd38d96e15b3451c9 100644 (file)
@@ -1171,9 +1171,6 @@ config LD_DEAD_CODE_DATA_ELIMINATION
 config SYSCTL
        bool
 
-config ANON_INODES
-       bool
-
 config HAVE_UID16
        bool
 
@@ -1378,14 +1375,12 @@ config HAVE_FUTEX_CMPXCHG
 config EPOLL
        bool "Enable eventpoll support" if EXPERT
        default y
-       select ANON_INODES
        help
          Disabling this option will cause the kernel to be built without
          support for epoll family of system calls.
 
 config SIGNALFD
        bool "Enable signalfd() system call" if EXPERT
-       select ANON_INODES
        default y
        help
          Enable the signalfd() system call that allows to receive signals
@@ -1395,7 +1390,6 @@ config SIGNALFD
 
 config TIMERFD
        bool "Enable timerfd() system call" if EXPERT
-       select ANON_INODES
        default y
        help
          Enable the timerfd() system call that allows to receive timer
@@ -1405,7 +1399,6 @@ config TIMERFD
 
 config EVENTFD
        bool "Enable eventfd() system call" if EXPERT
-       select ANON_INODES
        default y
        help
          Enable the eventfd() system call that allows to receive both
@@ -1516,7 +1509,6 @@ config KALLSYMS_BASE_RELATIVE
 # syscall, maps, verifier
 config BPF_SYSCALL
        bool "Enable bpf() system call"
-       select ANON_INODES
        select BPF
        select IRQ_WORK
        default n
@@ -1533,7 +1525,6 @@ config BPF_JIT_ALWAYS_ON
 
 config USERFAULTFD
        bool "Enable userfaultfd() system call"
-       select ANON_INODES
        depends on MMU
        help
          Enable the userfaultfd() system call that allows to intercept and
@@ -1600,7 +1591,6 @@ config PERF_EVENTS
        bool "Kernel performance events and counters"
        default y if PROFILING
        depends on HAVE_PERF_EVENTS
-       select ANON_INODES
        select IRQ_WORK
        select SRCU
        help
index fbe9dfcd8680ec9b6d485e65596b228ec77a6da0..8b03d93ba06828a50fcdd747a3abb829f6ba4a28 100644 (file)
@@ -11,6 +11,7 @@
  * management can be a bitch. See 'mm/memory.c': 'copy_page_range()'
  */
 
+#include <linux/anon_inodes.h>
 #include <linux/slab.h>
 #include <linux/sched/autogroup.h>
 #include <linux/sched/mm.h>
@@ -21,6 +22,7 @@
 #include <linux/sched/task.h>
 #include <linux/sched/task_stack.h>
 #include <linux/sched/cputime.h>
+#include <linux/seq_file.h>
 #include <linux/rtmutex.h>
 #include <linux/init.h>
 #include <linux/unistd.h>
@@ -1670,6 +1672,58 @@ static inline void rcu_copy_process(struct task_struct *p)
 #endif /* #ifdef CONFIG_TASKS_RCU */
 }
 
+static int pidfd_release(struct inode *inode, struct file *file)
+{
+       struct pid *pid = file->private_data;
+
+       file->private_data = NULL;
+       put_pid(pid);
+       return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
+{
+       struct pid_namespace *ns = proc_pid_ns(file_inode(m->file));
+       struct pid *pid = f->private_data;
+
+       seq_put_decimal_ull(m, "Pid:\t", pid_nr_ns(pid, ns));
+       seq_putc(m, '\n');
+}
+#endif
+
+const struct file_operations pidfd_fops = {
+       .release = pidfd_release,
+#ifdef CONFIG_PROC_FS
+       .show_fdinfo = pidfd_show_fdinfo,
+#endif
+};
+
+/**
+ * pidfd_create() - Create a new pid file descriptor.
+ *
+ * @pid:  struct pid that the pidfd will reference
+ *
+ * This creates a new pid file descriptor with the O_CLOEXEC flag set.
+ *
+ * Note, that this function can only be called after the fd table has
+ * been unshared to avoid leaking the pidfd to the new process.
+ *
+ * Return: On success, a cloexec pidfd is returned.
+ *         On error, a negative errno number will be returned.
+ */
+static int pidfd_create(struct pid *pid)
+{
+       int fd;
+
+       fd = anon_inode_getfd("[pidfd]", &pidfd_fops, get_pid(pid),
+                             O_RDWR | O_CLOEXEC);
+       if (fd < 0)
+               put_pid(pid);
+
+       return fd;
+}
+
 /*
  * This creates a new process as a copy of the old one,
  * but does not actually start it yet.
@@ -1682,13 +1736,14 @@ static __latent_entropy struct task_struct *copy_process(
                                        unsigned long clone_flags,
                                        unsigned long stack_start,
                                        unsigned long stack_size,
+                                       int __user *parent_tidptr,
                                        int __user *child_tidptr,
                                        struct pid *pid,
                                        int trace,
                                        unsigned long tls,
                                        int node)
 {
-       int retval;
+       int pidfd = -1, retval;
        struct task_struct *p;
        struct multiprocess_signals delayed;
 
@@ -1738,6 +1793,31 @@ static __latent_entropy struct task_struct *copy_process(
                        return ERR_PTR(-EINVAL);
        }
 
+       if (clone_flags & CLONE_PIDFD) {
+               int reserved;
+
+               /*
+                * - CLONE_PARENT_SETTID is useless for pidfds and also
+                *   parent_tidptr is used to return pidfds.
+                * - CLONE_DETACHED is blocked so that we can potentially
+                *   reuse it later for CLONE_PIDFD.
+                * - CLONE_THREAD is blocked until someone really needs it.
+                */
+               if (clone_flags &
+                   (CLONE_DETACHED | CLONE_PARENT_SETTID | CLONE_THREAD))
+                       return ERR_PTR(-EINVAL);
+
+               /*
+                * Verify that parent_tidptr is sane so we can potentially
+                * reuse it later.
+                */
+               if (get_user(reserved, parent_tidptr))
+                       return ERR_PTR(-EFAULT);
+
+               if (reserved != 0)
+                       return ERR_PTR(-EINVAL);
+       }
+
        /*
         * Force any signals received before this point to be delivered
         * before the fork happens.  Collect up signals sent to multiple
@@ -1944,6 +2024,22 @@ static __latent_entropy struct task_struct *copy_process(
                }
        }
 
+       /*
+        * This has to happen after we've potentially unshared the file
+        * descriptor table (so that the pidfd doesn't leak into the child
+        * if the fd table isn't shared).
+        */
+       if (clone_flags & CLONE_PIDFD) {
+               retval = pidfd_create(pid);
+               if (retval < 0)
+                       goto bad_fork_free_pid;
+
+               pidfd = retval;
+               retval = put_user(pidfd, parent_tidptr);
+               if (retval)
+                       goto bad_fork_put_pidfd;
+       }
+
 #ifdef CONFIG_BLOCK
        p->plug = NULL;
 #endif
@@ -2004,7 +2100,7 @@ static __latent_entropy struct task_struct *copy_process(
         */
        retval = cgroup_can_fork(p);
        if (retval)
-               goto bad_fork_free_pid;
+               goto bad_fork_put_pidfd;
 
        /*
         * From this point on we must avoid any synchronous user-space
@@ -2119,6 +2215,9 @@ bad_fork_cancel_cgroup:
        spin_unlock(&current->sighand->siglock);
        write_unlock_irq(&tasklist_lock);
        cgroup_cancel_fork(p);
+bad_fork_put_pidfd:
+       if (clone_flags & CLONE_PIDFD)
+               ksys_close(pidfd);
 bad_fork_free_pid:
        cgroup_threadgroup_change_end(current);
        if (pid != &init_struct_pid)
@@ -2184,7 +2283,7 @@ static inline void init_idle_pids(struct task_struct *idle)
 struct task_struct *fork_idle(int cpu)
 {
        struct task_struct *task;
-       task = copy_process(CLONE_VM, 0, 0, NULL, &init_struct_pid, 0, 0,
+       task = copy_process(CLONE_VM, 0, 0, NULL, NULL, &init_struct_pid, 0, 0,
                            cpu_to_node(cpu));
        if (!IS_ERR(task)) {
                init_idle_pids(task);
@@ -2236,7 +2335,7 @@ long _do_fork(unsigned long clone_flags,
                        trace = 0;
        }
 
-       p = copy_process(clone_flags, stack_start, stack_size,
+       p = copy_process(clone_flags, stack_start, stack_size, parent_tidptr,
                         child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
        add_latent_entropy();
 
index 227ba170298e5b457c9b405c5376c466fe26850b..cd83cc3767670f6fbe43577240922652df66d870 100644 (file)
@@ -3513,7 +3513,6 @@ SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
        return kill_something_info(sig, &info, pid);
 }
 
-#ifdef CONFIG_PROC_FS
 /*
  * Verify that the signaler and signalee either are in the same pid namespace
  * or that the signaler's pid namespace is an ancestor of the signalee's pid
@@ -3550,6 +3549,14 @@ static int copy_siginfo_from_user_any(kernel_siginfo_t *kinfo, siginfo_t *info)
        return copy_siginfo_from_user(kinfo, info);
 }
 
+static struct pid *pidfd_to_pid(const struct file *file)
+{
+       if (file->f_op == &pidfd_fops)
+               return file->private_data;
+
+       return tgid_pidfd_to_pid(file);
+}
+
 /**
  * sys_pidfd_send_signal - send a signal to a process through a task file
  *                          descriptor
@@ -3586,7 +3593,7 @@ SYSCALL_DEFINE4(pidfd_send_signal, int, pidfd, int, sig,
                return -EBADF;
 
        /* Is this a pidfd? */
-       pid = tgid_pidfd_to_pid(f.file);
+       pid = pidfd_to_pid(f.file);
        if (IS_ERR(pid)) {
                ret = PTR_ERR(pid);
                goto err;
@@ -3620,7 +3627,6 @@ err:
        fdput(f);
        return ret;
 }
-#endif /* CONFIG_PROC_FS */
 
 static int
 do_send_specific(pid_t tgid, pid_t pid, int sig, struct kernel_siginfo *info)
index d21f4befaea4dcf25ed18b8163f24415fb5bd010..4d9ae5ea6caff8885c9c0a171a3f84bcfc318301 100644 (file)
@@ -167,9 +167,6 @@ COND_SYSCALL(syslog);
 
 /* kernel/sched/core.c */
 
-/* kernel/signal.c */
-COND_SYSCALL(pidfd_send_signal);
-
 /* kernel/sys.c */
 COND_SYSCALL(setregid);
 COND_SYSCALL(setgid);
index b1142a95881108467c7715f2e30333fb8212ab06..fadadb1c3b05a54c87526205966c9879e55e95cd 100644 (file)
@@ -3,4 +3,4 @@
 obj-$(CONFIG_SAMPLES)  += kobject/ kprobes/ trace_events/ livepatch/ \
                           hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \
                           configfs/ connector/ v4l/ trace_printk/ \
-                          vfio-mdev/ statx/ qmi/ binderfs/
+                          vfio-mdev/ statx/ qmi/ binderfs/ pidfd/
diff --git a/samples/pidfd/Makefile b/samples/pidfd/Makefile
new file mode 100644 (file)
index 0000000..0ff9778
--- /dev/null
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+hostprogs-y := pidfd-metadata
+always := $(hostprogs-y)
+HOSTCFLAGS_pidfd-metadata.o += -I$(objtree)/usr/include
+all: pidfd-metadata
diff --git a/samples/pidfd/pidfd-metadata.c b/samples/pidfd/pidfd-metadata.c
new file mode 100644 (file)
index 0000000..640f5f7
--- /dev/null
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#ifndef CLONE_PIDFD
+#define CLONE_PIDFD 0x00001000
+#endif
+
+static int do_child(void *args)
+{
+       printf("%d\n", getpid());
+       _exit(EXIT_SUCCESS);
+}
+
+static pid_t pidfd_clone(int flags, int *pidfd)
+{
+       size_t stack_size = 1024;
+       char *stack[1024] = { 0 };
+
+#ifdef __ia64__
+       return __clone2(do_child, stack, stack_size, flags | SIGCHLD, NULL, pidfd);
+#else
+       return clone(do_child, stack + stack_size, flags | SIGCHLD, NULL, pidfd);
+#endif
+}
+
+static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
+                                       unsigned int flags)
+{
+       return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
+}
+
+static int pidfd_metadata_fd(pid_t pid, int pidfd)
+{
+       int procfd, ret;
+       char path[100];
+
+       snprintf(path, sizeof(path), "/proc/%d", pid);
+       procfd = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+       if (procfd < 0) {
+               warn("Failed to open %s\n", path);
+               return -1;
+       }
+
+       /*
+        * Verify that the pid has not been recycled and our /proc/<pid> handle
+        * is still valid.
+        */
+       ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
+       if (ret < 0) {
+               switch (errno) {
+               case EPERM:
+                       /* Process exists, just not allowed to signal it. */
+                       break;
+               default:
+                       warn("Failed to signal process\n");
+                       close(procfd);
+                       procfd = -1;
+               }
+       }
+
+       return procfd;
+}
+
+int main(int argc, char *argv[])
+{
+       int pidfd = 0, ret = EXIT_FAILURE;
+       char buf[4096] = { 0 };
+       pid_t pid;
+       int procfd, statusfd;
+       ssize_t bytes;
+
+       pid = pidfd_clone(CLONE_PIDFD, &pidfd);
+       if (pid < 0)
+               exit(ret);
+
+       procfd = pidfd_metadata_fd(pid, pidfd);
+       close(pidfd);
+       if (procfd < 0)
+               goto out;
+
+       statusfd = openat(procfd, "status", O_RDONLY | O_CLOEXEC);
+       close(procfd);
+       if (statusfd < 0)
+               goto out;
+
+       bytes = read(statusfd, buf, sizeof(buf));
+       if (bytes > 0)
+               bytes = write(STDOUT_FILENO, buf, bytes);
+       close(statusfd);
+       ret = EXIT_SUCCESS;
+
+out:
+       (void)wait(NULL);
+
+       exit(ret);
+}