2006-12-12  Dmitry V. Levin <ldv@altlinux.org>

	Fix -ff -o behaviour.  Fix piping trace output.
	* defs.h (newoutf): Remove.
	(alloctcb): Rename to alloc_tcb.  Add alloctcb() macro wrapper
	around alloc_tcb().
	* process.c [!USE_PROCFS] (internal_clone, internal_fork):
	Remove newoutf() call.
	* strace.c (set_cloexec_flag, strace_fopen, strace_popen,
	swap_uid): New functions.
	(popen_pid): New variable.
	(newoutf): Make static, use strace_fopen().
	(main): Use strace_fopen() and strace_popen(), remove uids
	swapping.  Do not open outfname when followfork > 1.
	Reinitialize tcp->outf properly.
	(alloctcb): Rename to alloc_tcb.  Use newoutf().
	(trace): Check popen_pid.  Remove newoutf() call.
	[USE_PROCFS] (proc_open, proc_poll_open): Use set_cloexec_flag().
	Fixes RH#204950, RH#218435.
diff --git a/strace.c b/strace.c
index 7d5f86b..5e3b875 100644
--- a/strace.c
+++ b/strace.c
@@ -175,6 +175,150 @@
 #endif /* MIPS */
 #endif /* SVR4 */
 
+static int
+set_cloexec_flag(int fd)
+{
+	int     flags, newflags;
+
+	if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+	{
+		fprintf(stderr, "%s: fcntl F_GETFD: %s\n",
+			progname, strerror(errno));
+		return -1;
+	}
+
+	newflags = flags | FD_CLOEXEC;
+	if (flags == newflags)
+		return 0;
+
+	if (fcntl(fd, F_SETFD, newflags) < 0)
+	{
+		fprintf(stderr, "%s: fcntl F_SETFD: %s\n",
+			progname, strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * When strace is setuid executable, we have to swap uids
+ * before and after filesystem and process management operations.
+ */
+static void
+swap_uid(void)
+{
+#ifndef SVR4
+	int euid = geteuid(), uid = getuid();
+
+	if (euid != uid && setreuid(euid, uid) < 0)
+	{
+		fprintf(stderr, "%s: setreuid: %s\n",
+			progname, strerror(errno));
+		exit(1);
+	}
+#endif
+}
+
+static FILE *
+strace_fopen(const char *path, const char *mode)
+{
+	FILE *fp;
+
+	swap_uid();
+	if ((fp = fopen(path, mode)) == NULL)
+		fprintf(stderr, "%s: can't fopen '%s': %s\n",
+			progname, path, strerror(errno));
+	swap_uid();
+	if (fp && set_cloexec_flag(fileno(fp)) < 0)
+	{
+		fclose(fp);
+		fp = NULL;
+	}
+	return fp;
+}
+
+static int popen_pid = -1;
+
+#ifndef _PATH_BSHELL
+# define _PATH_BSHELL "/bin/sh"
+#endif
+
+/*
+ * We cannot use standard popen(3) here because we have to distinguish
+ * popen child process from other processes we trace, and standard popen(3)
+ * does not export its child's pid.
+ */
+static FILE *
+strace_popen(const char *command)
+{
+	int     fds[2];
+
+	swap_uid();
+	if (pipe(fds) < 0)
+	{
+		fprintf(stderr, "%s: pipe: %s\n",
+			progname, strerror(errno));
+		swap_uid();
+		return NULL;
+	}
+
+	if (set_cloexec_flag(fds[1]) < 0)
+	{
+		close(fds[0]);
+		close(fds[1]);
+		swap_uid();
+		return NULL;
+	}
+
+	if ((popen_pid = fork()) == -1)
+	{
+		fprintf(stderr, "%s: fork: %s\n",
+			progname, strerror(errno));
+		close(fds[0]);
+		close(fds[1]);
+		swap_uid();
+		return NULL;
+	}
+
+	if (popen_pid)
+	{
+		/* parent */
+		close(fds[0]);
+		swap_uid();
+		return fdopen(fds[1], "w");
+	} else
+	{
+		/* child */
+		close(fds[1]);
+		if (fds[0] && (dup2(fds[0], 0) || close(fds[0])))
+		{
+			fprintf(stderr, "%s: dup2: %s\n",
+				progname, strerror(errno));
+			_exit(1);
+		}
+		execl(_PATH_BSHELL, "sh", "-c", command, NULL);
+		fprintf(stderr, "%s: execl: %s: %s\n",
+			progname, _PATH_BSHELL, strerror(errno));
+		_exit(1);
+	}
+}
+
+static int
+newoutf(struct tcb *tcp)
+{
+	if (outfname && followfork > 1) {
+		char name[MAXPATHLEN];
+		FILE *fp;
+
+		sprintf(name, "%s.%u", outfname, tcp->pid);
+		if ((fp = strace_fopen(name, "w")) == NULL)
+			return -1;
+		tcp->outf = fp;
+	}
+	return 0;
+}
+
 int
 main(argc, argv)
 int argc;
@@ -274,7 +418,7 @@
 				fprintf(stderr, "%s: I'm sorry, I can't let you do that, Dave.\n", progname);
 				break;
 			}
-			if ((tcp = alloctcb(pid)) == NULL) {
+			if ((tcp = alloc_tcb(pid, 0)) == NULL) {
 				fprintf(stderr, "%s: out of memory\n",
 					progname);
 				exit(1);
@@ -343,14 +487,8 @@
 		run_gid = getgid();
 	}
 
-#ifndef SVR4
-	setreuid(geteuid(), getuid());
-#endif
-
 	/* Check if they want to redirect the output. */
 	if (outfname) {
-		long f;
-
 		/* See if they want to pipe the output. */
 		if (outfname[0] == '|' || outfname[0] == '!') {
 			/*
@@ -364,34 +502,14 @@
 				exit(1);
 			}
 
-			if ((outf = popen(outfname + 1, "w")) == NULL) {
-				fprintf(stderr, "%s: can't popen '%s': %s\n",
-					progname, outfname + 1,
-					strerror(errno));
+			if ((outf = strace_popen(outfname + 1)) == NULL)
 				exit(1);
-			}
 		}
-		else if ((outf = fopen(outfname, "w")) == NULL) {
-			fprintf(stderr, "%s: can't fopen '%s': %s\n",
-				progname, outfname, strerror(errno));
+		else if (followfork <= 1 &&
+			 (outf = strace_fopen(outfname, "w")) == NULL)
 			exit(1);
-		}
-
-		if ((f=fcntl(fileno(outf), F_GETFD)) < 0 ) {
-			perror("failed to get flags for outputfile");
-			exit(1);
-		}
-
-		if (fcntl(fileno(outf), F_SETFD, f|FD_CLOEXEC) < 0 ) {
-			perror("failed to set flags for outputfile");
-			exit(1);
-		}
 	}
 
-#ifndef SVR4
-	setreuid(geteuid(), getuid());
-#endif
-
 	if (!outfname || outfname[0] == '|' || outfname[0] == '!')
 		setvbuf(outf, buf, _IOLBF, BUFSIZ);
 	if (outfname && optind < argc) {
@@ -401,10 +519,17 @@
 
 	for (c = 0; c < tcbtabsize; c++) {
 		tcp = tcbtab[c];
-		/* Reinitialize the output since it may have changed. */
-		tcp->outf = outf;
 		if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED))
 			continue;
+#ifdef LINUX
+		if (tcp->flags & TCB_CLONE_THREAD)
+			continue;
+#endif
+		/* Reinitialize the output since it may have changed. */
+		tcp->outf = outf;
+		if (newoutf(tcp) < 0)
+			exit(1);
+
 #ifdef USE_PROCFS
 		if (proc_open(tcp, 1) < 0) {
 			fprintf(stderr, "trouble opening proc file\n");
@@ -413,11 +538,10 @@
 		}
 #else /* !USE_PROCFS */
 # ifdef LINUX
-		if (tcp->flags & TCB_CLONE_THREAD)
-			continue;
 		if (followfork) {
 			char procdir[MAXPATHLEN];
 			DIR *dir;
+
 			sprintf(procdir, "/proc/%d/task", tcp->pid);
 			dir = opendir(procdir);
 			if (dir != NULL) {
@@ -680,31 +804,6 @@
 	exit(0);
 }
 
-void
-newoutf(tcp)
-struct tcb *tcp;
-{
-	char name[MAXPATHLEN];
-	FILE *fp;
-
-	if (outfname && followfork > 1) {
-		sprintf(name, "%s.%u", outfname, tcp->pid);
-#ifndef SVR4
-		setreuid(geteuid(), getuid());
-#endif
-		fp = fopen(name, "w");
-#ifndef SVR4
-		setreuid(geteuid(), getuid());
-#endif
-		if (fp == NULL) {
-			perror("fopen");
-			return;
-		}
-		tcp->outf = fp;
-	}
-	return;
-}
-
 int
 expand_tcbtab()
 {
@@ -735,8 +834,7 @@
 
 
 struct tcb *
-alloctcb(pid)
-int pid;
+alloc_tcb(int pid, int command_options_parsed)
 {
 	int i;
 	struct tcb *tcp;
@@ -758,10 +856,12 @@
 			tcp->stime.tv_usec = 0;
 			tcp->pfd = -1;
 			nprocs++;
+			if (command_options_parsed)
+				newoutf(tcp);
 			return tcp;
 		}
 	}
-	fprintf(stderr, "%s: alloctcb: tcb table full\n", progname);
+	fprintf(stderr, "%s: alloc_tcb: tcb table full\n", progname);
 	return NULL;
 }
 
@@ -790,12 +890,7 @@
 		perror("strace: open(\"/proc/...\", ...)");
 		return -1;
 	}
-	if ((arg = fcntl(tcp->pfd, F_GETFD)) < 0) {
-		perror("F_GETFD");
-		return -1;
-	}
-	if (fcntl(tcp->pfd, F_SETFD, arg|FD_CLOEXEC) < 0) {
-		perror("F_SETFD");
+	if (set_cloexec_flag(tcp->pfd) < 0) {
 		return -1;
 	}
 	sprintf(proc, "/proc/%d/status", tcp->pid);
@@ -803,12 +898,7 @@
 		perror("strace: open(\"/proc/...\", ...)");
 		return -1;
 	}
-	if ((arg = fcntl(tcp->pfd_stat, F_GETFD)) < 0) {
-		perror("F_GETFD");
-		return -1;
-	}
-	if (fcntl(tcp->pfd_stat, F_SETFD, arg|FD_CLOEXEC) < 0) {
-		perror("F_SETFD");
+	if (set_cloexec_flag(tcp->pfd_stat) < 0) {
 		return -1;
 	}
 	sprintf(proc, "/proc/%d/as", tcp->pid);
@@ -816,12 +906,7 @@
 		perror("strace: open(\"/proc/...\", ...)");
 		return -1;
 	}
-	if ((arg = fcntl(tcp->pfd_as, F_GETFD)) < 0) {
-		perror("F_GETFD");
-		return -1;
-	}
-	if (fcntl(tcp->pfd_as, F_SETFD, arg|FD_CLOEXEC) < 0) {
-		perror("F_SETFD");
+	if (set_cloexec_flag(tcp->pfd_as) < 0) {
 		return -1;
 	}
 #else
@@ -836,12 +921,7 @@
 		perror("strace: open(\"/proc/...\", ...)");
 		return -1;
 	}
-	if ((arg = fcntl(tcp->pfd, F_GETFD)) < 0) {
-		perror("F_GETFD");
-		return -1;
-	}
-	if (fcntl(tcp->pfd, F_SETFD, arg|FD_CLOEXEC) < 0) {
-		perror("F_SETFD");
+	if (set_cloexec_flag(tcp->pfd) < 0) {
 		return -1;
 	}
 #endif
@@ -1529,7 +1609,6 @@
 static void
 proc_poll_open()
 {
-	int arg;
 	int i;
 
 	if (pipe(proc_poll_pipe) < 0) {
@@ -1537,12 +1616,7 @@
 		exit(1);
 	}
 	for (i = 0; i < 2; i++) {
-		if ((arg = fcntl(proc_poll_pipe[i], F_GETFD)) < 0) {
-			perror("F_GETFD");
-			exit(1);
-		}
-		if (fcntl(proc_poll_pipe[i], F_SETFD, arg|FD_CLOEXEC) < 0) {
-			perror("F_SETFD");
+		if (set_cloexec_flag(proc_poll_pipe[i]) < 0) {
 			exit(1);
 		}
 	}
@@ -2087,6 +2161,11 @@
 				return -1;
 			}
 		}
+		if (pid == popen_pid) {
+			if (WIFEXITED(status) || WIFSIGNALED(status))
+				popen_pid = -1;
+			continue;
+		}
 		if (debug)
 			fprintf(stderr, " [wait(%#x) = %u]\n", status, pid);
 
@@ -2113,7 +2192,6 @@
 					return 0;
 				}
 				tcp->flags |= TCB_ATTACHED | TCB_SUSPENDED;
-				newoutf(tcp);
 				if (!qflag)
 					fprintf(stderr, "\
 Process %d attached (waiting for parent)\n",