posix_spawn: always clear O_CLOEXEC for dup'ed fds.
dup2(2) is a no-op if the new and old fds are equal, but it's pretty
clear that any useful caller in the posix_spawn(3) context wants us to
clear O_CLOEXEC even if we don't actually "move" the fd.
Bug: https://www.austingroupbugs.net/view.php?id=411
Test: treehugger
Change-Id: I5ce1a1f9216df5afd295cc9e35b84527873e9541
diff --git a/tests/spawn_test.cpp b/tests/spawn_test.cpp
index f3c5b9a..a9563b8 100644
--- a/tests/spawn_test.cpp
+++ b/tests/spawn_test.cpp
@@ -508,3 +508,42 @@
AssertChildExited(pid, 99);
}
+
+TEST(spawn, posix_spawn_dup2_CLOEXEC) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ posix_spawn_file_actions_t fa;
+ ASSERT_EQ(0, posix_spawn_file_actions_init(&fa));
+
+ int fd = open("/proc/version", O_RDONLY | O_CLOEXEC);
+ ASSERT_NE(-1, fd);
+
+ ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[0]));
+ ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, fds[1], 1));
+ // dup2() is a no-op when the two fds are the same, so this won't clear
+ // O_CLOEXEC unless we're doing extra work to make that happen.
+ ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, fd, fd));
+
+ // Read /proc/self/fd/<fd> in the child...
+ std::string fdinfo_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
+ ExecTestHelper eth;
+ eth.SetArgs({"cat", fdinfo_path.c_str(), nullptr});
+ pid_t pid;
+ ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), &fa, nullptr, eth.GetArgs(), eth.GetEnv()));
+ ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa));
+ ASSERT_EQ(0, close(fds[1]));
+ std::string content;
+ ASSERT_TRUE(android::base::ReadFdToString(fds[0], &content));
+ ASSERT_EQ(0, close(fds[0]));
+
+ // ...and compare that to the parent. This is overkill really, since the very
+ // fact that the child had a valid file descriptor strongly implies that we
+ // removed O_CLOEXEC, but we may as well check that the child ended up with
+ // the *right* file descriptor :-)
+ std::string expected;
+ ASSERT_TRUE(android::base::ReadFdToString(fd, &expected));
+ ASSERT_EQ(expected, content);
+
+ AssertChildExited(pid, 0);
+}