/*
 * DF-0911 - procfs ATTACH skips saving p_oppid for already-owned children
 *
 * Trigger: a process forks a child, then ATTACHes to it via /proc/<pid>/ctl
 * and DETACHes. Because the tracer is already the actual parent of the
 * target, procfs_control() takes the `p->p_pptr != curp` branch FALSE at
 * sys/vfs/procfs/procfs_ctl.c:153 and skips saving p_oppid (which stays 0).
 *
 * On DETACH (procfs_ctl.c:211) the check `p_oppid != p_pptr->p_pid` is
 * therefore TRUE (0 != parent's pid), so pfs_pfind(0) is called, which
 * returns &proc0 (procfs_subr.c:285-287), and proc_reparent() hands the
 * child to proc0 -- the child is now orphaned to the kernel swapper.
 *
 * Observable effect: after DETACH the child's getppid() returns 0 and the
 * parent's waitpid(child) returns ECHILD.  Compare to ptrace(2) PT_ATTACH
 * (sys_process.c:314) which ALWAYS saves p_oppid -- procfs is inconsistent.
 *
 * Build: cc -o df0911 df0911.c
 * Run:   ./df0911
 * Expected on buggy kernel: child ppid after detach = 0, waitpid=ECHILD.
 * Expected on fixed kernel: child ppid after detach = parent's pid, waitpid ok.
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void write_ctl(pid_t pid, const char *cmd)
{
	char path[64];
	int fd, n;
	ssize_t w;

	snprintf(path, sizeof(path), "/proc/%d/ctl", pid);
	fd = open(path, O_WRONLY, 0);
	if (fd < 0)
		err(1, "open %s", path);
	w = write(fd, cmd, strlen(cmd));
	if (w < 0)
		err(1, "write %s", path);
	n = (int)w;
	close(fd);
	fprintf(stderr, "[parent] wrote '%s' (%d bytes) to %s\n", cmd, n, path);
}

int
main(void)
{
	pid_t child, wp;
	int status;
	int pipefd[2];
	char buf[64];
	ssize_t r;

	if (pipe(pipefd) < 0)
		err(1, "pipe");

	child = fork();
	if (child < 0)
		err(1, "fork");
	if (child == 0) {
		/* child: close write end, then loop reporting our ppid */
		close(pipefd[0]);
		for (;;) {
			pid_t pp = getppid();
			snprintf(buf, sizeof(buf), "child ppid=%d\n", pp);
			(void)write(pipefd[1], buf, strlen(buf));
			sleep(1);
		}
		/* not reached */
		_exit(0);
	}

	close(pipefd[1]);

	/* read one line before attach: should report parent pid */
	r = read(pipefd[0], buf, sizeof(buf) - 1);
	if (r > 0) {
		buf[r] = 0;
		fprintf(stderr, "[parent] before attach: %s", buf);
	}

	/* ATTACH via procfs ctl. Tracer is the actual parent of the target,
	 * so procfs_control() takes the FALSE branch at procfs_ctl.c:153 and
	 * does NOT save p_oppid (stays 0). */
	write_ctl(child, "attach");
	usleep(200000);

	/* DETACH. p_oppid=0 != parent's pid -> pfs_pfind(0) -> proc0 ->
	 * proc_reparent(child, &proc0). Child is orphaned to the swapper. */
	write_ctl(child, "detach");
	usleep(500000);

	/* read child's report after detach: should now report ppid=0 */
	r = read(pipefd[0], buf, sizeof(buf) - 1);
	if (r > 0) {
		buf[r] = 0;
		fprintf(stderr, "[parent] after detach:  %s", buf);
		if (strncmp(buf, "child ppid=0", 12) == 0) {
			printf("RESULT: BUG REPRODUCED -- child orphaned to "
			       "proc0 (ppid=0) after procfs attach/detach\n");
		} else {
			printf("RESULT: not reproduced -- child ppid after "
			       "detach is non-zero\n");
		}
	} else {
		printf("RESULT: no child report (read=%zd)\n", r);
	}

	/* Try to wait for the child.  If the child was reparented away from
	 * us, waitpid must return ECHILD. */
	errno = 0;
	wp = waitpid(child, &status, WNOHANG);
	printf("waitpid(%d)=%d errno=%d (%s)\n", child, (int)wp, errno,
	       errno == ECHILD ? "ECHILD" : strerror(errno));

	/* Also dump /proc/<child>/status for an authoritative kernel view */
	{
		char path[64];
		int fd;
		snprintf(path, sizeof(path), "/proc/%d/status", child);
		fd = open(path, O_RDONLY, 0);
		if (fd >= 0) {
			char s[1024];
			ssize_t n = read(fd, s, sizeof(s)-1);
			if (n > 0) { s[n] = 0; printf("---- /proc/%d/status ----\n%s", child, s); }
			close(fd);
		}
	}

	/* clean up */
	(void)kill(child, SIGKILL);
	(void)waitpid(child, &status, 0);

	return 0;
}
