Commit e5f43782 authored by Robert Sesek's avatar Robert Sesek Committed by Commit Bot

Support base::LaunchOptions.current_directory when using posix_spawn on Mac.

This uses the undocumented pthread_chdir_np() to change the launching
thread's PWD for the duration of the posix_spawn() syscall. There is no
other way to change directories when using posix_spawn, other than an
exec trampoline.

Bug: 179923
Change-Id: Ic6ab10cd1cb5be2f008b434b3546c05f080ef9b1
Reviewed-on: https://chromium-review.googlesource.com/c/1311258Reviewed-by: default avatarMark Mentovai <mark@chromium.org>
Commit-Queue: Robert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604799}
parent 0b2ca9cc
......@@ -8,12 +8,23 @@
#include <mach/mach.h>
#include <spawn.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include "base/logging.h"
#include "base/mac/availability.h"
#include "base/posix/eintr_wrapper.h"
#include "base/threading/scoped_blocking_call.h"
// Changes the current thread's directory to a path or directory file
// descriptor. libpthread only exposes a syscall wrapper starting in
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
// the syscall is issued directly.
extern "C" {
int pthread_chdir_np(const char*) API_AVAILABLE(macosx(10.12));
int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
}
namespace base {
namespace {
......@@ -71,6 +82,24 @@ class PosixSpawnFileActions {
DISALLOW_COPY_AND_ASSIGN(PosixSpawnFileActions);
};
int ChangeCurrentThreadDirectory(const char* path) {
if (__builtin_available(macOS 10.12, *)) {
return pthread_chdir_np(path);
} else {
return syscall(SYS___pthread_chdir, path);
}
}
// The recommended way to unset a per-thread cwd is to set a new value to an
// invalid file descriptor, per libpthread-218.1.3/private/private.h.
int ResetCurrentThreadDirectory() {
if (__builtin_available(macOS 10.12, *)) {
return pthread_fchdir_np(-1);
} else {
return syscall(SYS___pthread_fchdir, -1);
}
}
} // namespace
void RestoreDefaultExceptionHandler() {
......@@ -92,9 +121,6 @@ void RestoreDefaultExceptionHandler() {
Process LaunchProcessPosixSpawn(const std::vector<std::string>& argv,
const LaunchOptions& options) {
DCHECK(options.current_directory.empty())
<< "LaunchProcessPosixSpawn does not support current_directory";
PosixSpawnAttr attr;
short flags = POSIX_SPAWN_CLOEXEC_DEFAULT;
......@@ -153,11 +179,27 @@ Process LaunchProcessPosixSpawn(const std::vector<std::string>& argv,
? options.real_path.value().c_str()
: argv_cstr[0];
// If the new program has specified its PWD, change the thread-specific
// working directory. The new process will inherit it during posix_spawn().
if (!options.current_directory.empty()) {
int rv =
ChangeCurrentThreadDirectory(options.current_directory.value().c_str());
if (rv != 0) {
DPLOG(ERROR) << "pthread_chdir_np";
return Process();
}
}
// Use posix_spawnp as some callers expect to have PATH consulted.
pid_t pid;
int rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(),
&argv_cstr[0], new_environ);
// Restore the thread's working directory if it was changed.
if (!options.current_directory.empty()) {
ResetCurrentThreadDirectory();
}
if (rv != 0) {
DLOG(ERROR) << "posix_spawnp(" << executable_path << "): -" << rv << " "
<< strerror(rv);
......
......@@ -303,10 +303,9 @@ Process LaunchProcess(const std::vector<std::string>& argv,
const LaunchOptions& options) {
TRACE_EVENT0("base", "LaunchProcess");
#if defined(OS_MACOSX)
// TODO(rsesek): Remove this feature. https://crbug.com/179923.
if (FeatureList::IsEnabled(kMacLaunchProcessPosixSpawn)) {
// TODO(rsesek): Do this unconditionally. https://crbug.com/179923.
if (options.current_directory.empty())
return LaunchProcessPosixSpawn(argv, options);
return LaunchProcessPosixSpawn(argv, options);
}
#endif
......
......@@ -9,6 +9,7 @@
#include <limits>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/alias.h"
#include "base/debug/stack_trace.h"
......@@ -32,6 +33,7 @@
#include "base/test/scoped_task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -478,19 +480,78 @@ TEST_F(ProcessUtilTest, HandlesToTransferClosedOnBadPathToMapFailure) {
// On Android SpawnProcess() doesn't use LaunchProcess() and doesn't support
// LaunchOptions::current_directory.
#if !defined(OS_ANDROID)
MULTIPROCESS_TEST_MAIN(CheckCwdProcess) {
FilePath expected;
CHECK(GetTempDir(&expected));
expected = MakeAbsoluteFilePath(expected);
CHECK(!expected.empty());
static void CheckCwdIsExpected(FilePath expected) {
FilePath actual;
CHECK(GetCurrentDirectory(&actual));
actual = MakeAbsoluteFilePath(actual);
CHECK(!actual.empty());
CHECK(expected == actual) << "Expected: " << expected.value()
<< " Actual: " << actual.value();
CHECK_EQ(expected, actual);
}
// N.B. This test does extra work to check the cwd on multiple threads, because
// on macOS a per-thread cwd is set when using LaunchProcess().
MULTIPROCESS_TEST_MAIN(CheckCwdProcess) {
// Get the expected cwd.
FilePath temp_dir;
CHECK(GetTempDir(&temp_dir));
temp_dir = MakeAbsoluteFilePath(temp_dir);
CHECK(!temp_dir.empty());
// Test that the main thread has the right cwd.
CheckCwdIsExpected(temp_dir);
// Create a non-main thread.
Thread thread("CheckCwdThread");
thread.Start();
auto task_runner = thread.task_runner();
// A synchronization primitive used to wait for work done on the non-main
// thread.
WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC);
RepeatingClosure signal_event =
BindRepeating(&WaitableEvent::Signal, Unretained(&event));
// Test that a non-main thread has the right cwd.
task_runner->PostTask(FROM_HERE, Bind(&CheckCwdIsExpected, temp_dir));
task_runner->PostTask(FROM_HERE, signal_event);
event.Wait();
// Get a new cwd for the process.
FilePath home_dir;
CHECK(PathService::Get(DIR_HOME, &home_dir));
// Change the cwd on the secondary thread. IgnoreResult is used when setting
// because it is checked immediately after.
task_runner->PostTask(FROM_HERE,
Bind(IgnoreResult(&SetCurrentDirectory), home_dir));
task_runner->PostTask(FROM_HERE, Bind(&CheckCwdIsExpected, home_dir));
task_runner->PostTask(FROM_HERE, signal_event);
event.Wait();
// Make sure the main thread sees the cwd from the secondary thread.
CheckCwdIsExpected(home_dir);
// Change the directory back on the main thread.
CHECK(SetCurrentDirectory(temp_dir));
CheckCwdIsExpected(temp_dir);
// Ensure that the secondary thread sees the new cwd too.
task_runner->PostTask(FROM_HERE, Bind(&CheckCwdIsExpected, temp_dir));
task_runner->PostTask(FROM_HERE, signal_event);
event.Wait();
// Change the cwd on the secondary thread one more time and join the thread.
task_runner->PostTask(FROM_HERE,
Bind(IgnoreResult(&SetCurrentDirectory), home_dir));
thread.Stop();
// Make sure that the main thread picked up the new cwd.
CheckCwdIsExpected(home_dir);
return kSuccess;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment