blob: 9850739eb6c6345355b71c7329cf6306c9b50679 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/native_support/process_impl.h"
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <algorithm>
#include <limits>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "build/build_config.h"
#include "mojo/services/files/interfaces/types.mojom.h"
#include "services/native_support/make_pty_pair.h"
#include "services/native_support/process_controller_impl.h"
#include "services/native_support/process_io_redirection.h"
namespace native_support {
namespace {
class SetsidPreExecDelegate : public base::LaunchOptions::PreExecDelegate {
public:
SetsidPreExecDelegate() {}
~SetsidPreExecDelegate() override {}
void RunAsyncSafe() override {
static const char kErrorMessage[] = "setsid() failed";
// Note: |setsid()| and |write()| are both async-signal-safe.
if (setsid() == static_cast<pid_t>(-1))
write(STDERR_FILENO, kErrorMessage, sizeof(kErrorMessage) - 1);
}
private:
DISALLOW_COPY_AND_ASSIGN(SetsidPreExecDelegate);
};
} // namespace
ProcessImpl::ProcessImpl(scoped_refptr<base::TaskRunner> worker_runner,
mojo::ApplicationConnection* connection,
mojo::InterfaceRequest<Process> request)
: worker_runner_(worker_runner.Pass()), binding_(this, request.Pass()) {}
ProcessImpl::~ProcessImpl() {}
void ProcessImpl::Spawn(
const mojo::String& path,
mojo::Array<mojo::String> argv,
mojo::Array<mojo::String> envp,
mojo::files::FilePtr stdin_file,
mojo::files::FilePtr stdout_file,
mojo::files::FilePtr stderr_file,
mojo::InterfaceRequest<ProcessController> process_controller,
const SpawnCallback& callback) {
std::vector<int> fds_to_inherit(3, -1);
// stdin:
base::ScopedFD stdin_fd;
base::ScopedFD stdin_parent_fd;
if (stdin_file) {
int stdin_pipe_fds[2] = {-1, -1};
CHECK_EQ(pipe(stdin_pipe_fds), 0);
stdin_fd.reset(stdin_pipe_fds[0]);
stdin_parent_fd.reset(stdin_pipe_fds[1]);
} else {
stdin_fd.reset(HANDLE_EINTR(open("/dev/null", O_RDONLY)));
}
fds_to_inherit[STDIN_FILENO] = stdin_fd.get();
// stdout:
base::ScopedFD stdout_fd;
base::ScopedFD stdout_parent_fd;
if (stdout_file) {
int stdout_pipe_fds[2] = {-1, -1};
CHECK_EQ(pipe(stdout_pipe_fds), 0);
stdout_fd.reset(stdout_pipe_fds[1]);
stdout_parent_fd.reset(stdout_pipe_fds[0]);
} else {
stdout_fd.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
}
fds_to_inherit[STDOUT_FILENO] = stdout_fd.get();
// stderr:
base::ScopedFD stderr_fd;
base::ScopedFD stderr_parent_fd;
if (stderr_file) {
int stderr_pipe_fds[2] = {-1, -1};
CHECK_EQ(pipe(stderr_pipe_fds), 0);
stderr_fd.reset(stderr_pipe_fds[1]);
stderr_parent_fd.reset(stderr_pipe_fds[0]);
} else {
stderr_fd.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
}
fds_to_inherit[STDERR_FILENO] = stderr_fd.get();
std::unique_ptr<ProcessIORedirection> process_io_redirection(
new ProcessIORedirectionForStdIO(
stdin_file.Pass(), stdout_file.Pass(), stderr_file.Pass(),
stdin_parent_fd.Pass(), stdout_parent_fd.Pass(),
stderr_parent_fd.Pass()));
SpawnImpl(path, argv.Pass(), envp.Pass(), std::move(process_io_redirection),
fds_to_inherit, process_controller.Pass(), callback);
}
void ProcessImpl::SpawnWithTerminal(
const mojo::String& path,
mojo::Array<mojo::String> argv,
mojo::Array<mojo::String> envp,
mojo::files::FilePtr terminal_file,
mojo::InterfaceRequest<ProcessController> process_controller,
const SpawnWithTerminalCallback& callback) {
DCHECK(terminal_file);
std::vector<int> fds_to_inherit(3, -1);
base::ScopedFD master_fd;
base::ScopedFD slave_fd;
int errno_value = 0;
if (!MakePtyPair(&master_fd, &slave_fd, &errno_value)) {
// TODO(vtl): Well, this is dumb (we should use errno_value).
callback.Run(mojo::files::Error::UNKNOWN);
return;
}
// stdin:
base::ScopedFD stdin_fd(slave_fd.Pass());
fds_to_inherit[STDIN_FILENO] = stdin_fd.get();
// stdout:
base::ScopedFD stdout_fd(HANDLE_EINTR(dup(stdin_fd.get())));
fds_to_inherit[STDOUT_FILENO] = stdout_fd.get();
// stderr:
base::ScopedFD stderr_fd(HANDLE_EINTR(dup(stdin_fd.get())));
fds_to_inherit[STDERR_FILENO] = stderr_fd.get();
std::unique_ptr<ProcessIORedirection> process_io_redirection(
new ProcessIORedirectionForTerminal(terminal_file.Pass(),
master_fd.Pass()));
SpawnImpl(path, argv.Pass(), envp.Pass(), std::move(process_io_redirection),
fds_to_inherit, process_controller.Pass(), callback);
}
void ProcessImpl::SpawnImpl(
const mojo::String& path,
mojo::Array<mojo::String> argv,
mojo::Array<mojo::String> envp,
std::unique_ptr<ProcessIORedirection> process_io_redirection,
const std::vector<int>& fds_to_inherit,
mojo::InterfaceRequest<ProcessController> process_controller,
const SpawnCallback& callback) {
DCHECK(!path.is_null());
DCHECK(process_controller.is_pending());
size_t argc = std::max(argv.size(), static_cast<size_t>(1));
std::vector<const char*> argv_ptrs(argc);
if (argv.is_null()) {
argv_ptrs[0] = path.data();
} else {
if (!argv.size() ||
argv.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
callback.Run(mojo::files::Error::INVALID_ARGUMENT);
return;
}
// TODO(vtl): Currently, we don't support setting argv[0], due to
// |base::CommandLine| limitations.
argv_ptrs[0] = path.data();
for (size_t i = 1; i < argv.size(); i++)
argv_ptrs[i] = argv[i].data();
}
base::CommandLine command_line(static_cast<int>(argc), argv_ptrs.data());
bool inherit_environment = true;
base::EnvironmentMap environment_map;
if (!envp.is_null()) {
inherit_environment = false;
for (size_t i = 0; i < envp.size(); i++) {
std::string s(envp[i].data());
size_t equals_pos = s.find_first_of('=');
environment_map[s.substr(0, equals_pos)] =
(equals_pos == std::string::npos) ? std::string()
: s.substr(equals_pos + 1);
}
}
base::FileHandleMappingVector fd_mapping;
DCHECK(fds_to_inherit.size() >= 3);
for (size_t i = 0; i < fds_to_inherit.size(); i++) {
DCHECK_GE(fds_to_inherit[i], 0);
fd_mapping.push_back(
std::make_pair(fds_to_inherit[i], static_cast<int>(i)));
}
SetsidPreExecDelegate pre_exec_delegate;
base::LaunchOptions launch_options;
launch_options.wait = false;
launch_options.environ.swap(environment_map);
launch_options.clear_environ = !inherit_environment;
launch_options.fds_to_remap = &fd_mapping;
// launch_options.maximize_rlimits
launch_options.new_process_group = false;
// launch_options.clone_flags = 0;
#if defined(OS_LINUX)
launch_options.allow_new_privs = true;
#endif
// launch_options.kill_on_parent_death = true;
// launch_options.current_directory
launch_options.pre_exec_delegate = &pre_exec_delegate;
base::Process process = LaunchProcess(command_line, launch_options);
// Note: Failure is extremely unusual. E.g., it won't fail even if |path|
// doesn't exist (since fork succeeds; it's the exec that fails).
if (!process.IsValid()) {
// TODO(vtl): Well, this is dumb (can we check errno?).
callback.Run(mojo::files::Error::UNKNOWN);
return;
}
new ProcessControllerImpl(worker_runner_, process_controller.Pass(),
process.Pass(), std::move(process_io_redirection));
callback.Run(mojo::files::Error::OK);
}
} // namespace native_support